mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-03 05:51:15 +02:00
Migration to FastAPI
This commit is contained in:
1 parent
c12b675691
commit
eb3cb8a41f
224 files changed
+11254
-13517
No files matched your search
@@ -1,22 +0,0 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from gns3server.handlers.index_handler import IndexHandler
|
||||
|
||||
|
||||
from gns3server.handlers.api.controller import *
|
||||
from gns3server.handlers.api.compute import *
|
||||
Whitespace-only changes.
@@ -1,43 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
from .capabilities_handler import CapabilitiesHandler
|
||||
from .network_handler import NetworkHandler
|
||||
from .project_handler import ProjectHandler
|
||||
from .dynamips_vm_handler import DynamipsVMHandler
|
||||
from .qemu_handler import QEMUHandler
|
||||
from .virtualbox_handler import VirtualBoxHandler
|
||||
from .vpcs_handler import VPCSHandler
|
||||
from .vmware_handler import VMwareHandler
|
||||
from .server_handler import ServerHandler
|
||||
from .notification_handler import NotificationHandler
|
||||
from .cloud_handler import CloudHandler
|
||||
from .nat_handler import NatHandler
|
||||
from .ethernet_hub_handler import EthernetHubHandler
|
||||
from .ethernet_switch_handler import EthernetSwitchHandler
|
||||
from .frame_relay_switch_handler import FrameRelaySwitchHandler
|
||||
from .atm_switch_handler import ATMSwitchHandler
|
||||
from .traceng_handler import TraceNGHandler
|
||||
|
||||
if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1":
|
||||
# IOU & Docker only runs 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
|
||||
@@ -1,312 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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.dynamips import Dynamips
|
||||
|
||||
from gns3server.schemas.atm_switch import (
|
||||
ATM_SWITCH_CREATE_SCHEMA,
|
||||
ATM_SWITCH_OBJECT_SCHEMA,
|
||||
ATM_SWITCH_UPDATE_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
class ATMSwitchHandler:
|
||||
|
||||
"""
|
||||
API entry points for ATM switch.
|
||||
"""
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/atm_switch/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new ATM switch instance",
|
||||
input=ATM_SWITCH_CREATE_SCHEMA,
|
||||
output=ATM_SWITCH_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
# Use the Dynamips ATM switch to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = await dynamips_manager.create_node(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("node_id"),
|
||||
node_type="atm_switch",
|
||||
mappings=request.json.get("mappings"))
|
||||
response.set_status(201)
|
||||
response.json(node)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/atm_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 ATM switch instance",
|
||||
output=ATM_SWITCH_OBJECT_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.json(node)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/atm_switch/nodes/{node_id}/duplicate",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance duplicated",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Duplicate an atm switch instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(
|
||||
request.match_info["node_id"],
|
||||
request.json["destination_node_id"]
|
||||
)
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/atm_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 ATM switch instance",
|
||||
input=ATM_SWITCH_UPDATE_SCHEMA,
|
||||
output=ATM_SWITCH_OBJECT_SCHEMA)
|
||||
async def update(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
if "name" in request.json and node.name != request.json["name"]:
|
||||
await node.set_name(request.json["name"])
|
||||
if "mappings" in request.json:
|
||||
node.mappings = request.json["mappings"]
|
||||
node.updated()
|
||||
response.json(node)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/atm_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 ATM switch instance")
|
||||
async def delete(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
await dynamips_manager.delete_node(request.match_info["node_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/atm_switch/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 an ATM switch")
|
||||
def start(request, response):
|
||||
|
||||
Dynamips.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/atm_switch/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 an ATM switch")
|
||||
def stop(request, response):
|
||||
|
||||
Dynamips.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/atm_switch/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 an ATM Relay switch (does nothing)")
|
||||
def suspend(request, response):
|
||||
|
||||
Dynamips.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/atm_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 ATM switch instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def create_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = await dynamips_manager.create_nio(node, request.json)
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.add_nio(nio, port_number)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/atm_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 ATM switch instance")
|
||||
async def delete_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = await node.remove_nio(port_number)
|
||||
await nio.delete()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/atm_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 ATM switch instance",
|
||||
input=NODE_CAPTURE_SCHEMA)
|
||||
async def start_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_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"])
|
||||
await 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}/atm_relay_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 ATM switch instance")
|
||||
async def stop_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/atm_relay_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the switch"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await dynamips_manager.stream_pcap_file(nio, node.project.id, request, response)
|
||||
@@ -1,47 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import psutil
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.schemas.capabilities import CAPABILITIES_SCHEMA
|
||||
from gns3server.version import __version__
|
||||
from gns3server.compute import MODULES
|
||||
from gns3server.utils.path import get_default_project_directory
|
||||
|
||||
|
||||
class CapabilitiesHandler:
|
||||
|
||||
@Route.get(
|
||||
r"/capabilities",
|
||||
description="Retrieve the capabilities of the server",
|
||||
output=CAPABILITIES_SCHEMA)
|
||||
def get(request, response):
|
||||
|
||||
node_types = []
|
||||
for module in MODULES:
|
||||
node_types.extend(module.node_types())
|
||||
|
||||
response.json({
|
||||
"version": __version__,
|
||||
"platform": sys.platform,
|
||||
"cpus": psutil.cpu_count(logical=True),
|
||||
"memory": psutil.virtual_memory().total,
|
||||
"disk_size": psutil.disk_usage(get_default_project_directory()).total,
|
||||
"node_types": node_types
|
||||
})
|
||||
@@ -1,326 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from aiohttp.web import HTTPConflict
|
||||
|
||||
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.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)
|
||||
async def create(request, response):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = await builtin_manager.create_node(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("node_id"),
|
||||
node_type="cloud",
|
||||
ports=request.json.get("ports_mapping"))
|
||||
|
||||
# add the remote console settings
|
||||
node.remote_console_host = request.json.get("remote_console_host", node.remote_console_host)
|
||||
node.remote_console_port = request.json.get("remote_console_port", node.remote_console_port)
|
||||
node.remote_console_type = request.json.get("remote_console_type", node.remote_console_type)
|
||||
node.remote_console_http_path = request.json.get("remote_console_http_path", node.remote_console_http_path)
|
||||
node.usage = request.json.get("usage", "")
|
||||
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"])
|
||||
for name, value in request.json.items():
|
||||
if hasattr(node, name) and getattr(node, name) != value:
|
||||
setattr(node, name, value)
|
||||
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")
|
||||
async def delete(request, response):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
await 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")
|
||||
async def start(request, response):
|
||||
|
||||
node = Builtin.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await node.start()
|
||||
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_node(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 (does nothing)")
|
||||
def suspend(request, response):
|
||||
|
||||
Builtin.instance().get_node(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)
|
||||
async 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 = builtin_manager.create_nio(request.json)
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.add_nio(nio, port_number)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@Route.put(
|
||||
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": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be updated"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO on a Cloud instance")
|
||||
async def update_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 = node.get_nio(port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await node.update_nio(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@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")
|
||||
async 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"])
|
||||
await node.remove_nio(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/cloud/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 cloud (always 0)",
|
||||
"port_number": "Port on the cloud"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a packet capture on a cloud instance",
|
||||
input=NODE_CAPTURE_SCHEMA)
|
||||
async def start_capture(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"])
|
||||
pcap_file_path = os.path.join(node.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
await 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}/cloud/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 cloud (always 0)",
|
||||
"port_number": "Port on the cloud"
|
||||
},
|
||||
status_codes={
|
||||
204: "Capture stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a packet capture on a cloud instance")
|
||||
async def stop_capture(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"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/cloud/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the cloud"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(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 = node.get_nio(port_number)
|
||||
await builtin_manager.stream_pcap_file(nio, node.project.id, request, response)
|
||||
@@ -1,451 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from aiohttp.web import HTTPConflict
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.compute.docker import Docker
|
||||
from gns3server.schemas.node import NODE_CAPTURE_SCHEMA
|
||||
from gns3server.schemas.nio import NIO_SCHEMA
|
||||
|
||||
from gns3server.schemas.docker import (
|
||||
DOCKER_CREATE_SCHEMA,
|
||||
DOCKER_OBJECT_SCHEMA,
|
||||
DOCKER_LIST_IMAGES_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
class DockerHandler:
|
||||
"""API entry points for Docker containers."""
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new Docker container",
|
||||
input=DOCKER_CREATE_SCHEMA,
|
||||
output=DOCKER_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = await docker_manager.create_node(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("node_id"),
|
||||
image=request.json.pop("image"),
|
||||
start_command=request.json.get("start_command"),
|
||||
environment=request.json.get("environment"),
|
||||
adapters=request.json.get("adapters"),
|
||||
console=request.json.get("console"),
|
||||
console_type=request.json.get("console_type"),
|
||||
console_resolution=request.json.get("console_resolution", "1024x768"),
|
||||
console_http_port=request.json.get("console_http_port", 80),
|
||||
console_http_path=request.json.get("console_http_path", "/"),
|
||||
aux=request.json.get("aux"),
|
||||
aux_type=request.json.pop("aux_type", "none"),
|
||||
extra_hosts=request.json.get("extra_hosts"),
|
||||
extra_volumes=request.json.get("extra_volumes"),
|
||||
memory=request.json.get("memory", 0),
|
||||
cpus=request.json.get("cpus", 0))
|
||||
for name, value in request.json.items():
|
||||
if name != "node_id":
|
||||
if hasattr(container, name) and getattr(container, name) != value:
|
||||
setattr(container, name, value)
|
||||
|
||||
response.set_status(201)
|
||||
response.json(container)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/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 Docker container")
|
||||
async def start(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await container.start()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/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 Docker container")
|
||||
async def stop(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await container.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/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 Docker container")
|
||||
async def suspend(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await container.pause()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/nodes/{node_id}/reload",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance restarted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Restart a Docker container")
|
||||
async def reload(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await container.restart()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/docker/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 Docker container")
|
||||
async def delete(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await container.delete()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/nodes/{node_id}/duplicate",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance duplicated",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Duplicate a Docker instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await Docker.instance().duplicate_node(
|
||||
request.match_info["node_id"],
|
||||
request.json["destination_node_id"]
|
||||
)
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/nodes/{node_id}/pause",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance paused",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Pause a Docker container")
|
||||
async def pause(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await container.pause()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/nodes/{node_id}/unpause",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance unpaused",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Unpause a Docker container")
|
||||
async def unpause(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await container.unpause()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter where the nio should be added",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO created",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Add a NIO to a Docker container",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def create_nio(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio_type = request.json["type"]
|
||||
if nio_type != "nio_udp":
|
||||
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = docker_manager.create_nio(request.json)
|
||||
await container.adapter_add_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/docker/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be updated (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO on a Docker instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = container.get_nio(adapter_number)
|
||||
if "filters" in request.json and nio:
|
||||
nio.filters = request.json["filters"]
|
||||
await container.adapter_update_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/docker/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter where the nio should be added",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
204: "NIO deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Remove a NIO from a Docker container")
|
||||
async def delete_nio(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await container.adapter_remove_nio_binding(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/docker/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 Docker instance",
|
||||
input=DOCKER_OBJECT_SCHEMA,
|
||||
output=DOCKER_OBJECT_SCHEMA)
|
||||
async def update(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
props = [
|
||||
"name", "console", "console_type", "aux", "aux_type", "console_resolution",
|
||||
"console_http_port", "console_http_path", "start_command",
|
||||
"environment", "adapters", "extra_hosts", "extra_volumes",
|
||||
"memory", "cpus"
|
||||
]
|
||||
|
||||
changed = False
|
||||
for prop in props:
|
||||
if prop in request.json and request.json[prop] != getattr(container, prop):
|
||||
setattr(container, prop, request.json[prop])
|
||||
changed = True
|
||||
# We don't call container.update for nothing because it will restart the container
|
||||
if changed:
|
||||
await container.update()
|
||||
container.updated()
|
||||
response.json(container)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/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 to start a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "Node not started"
|
||||
},
|
||||
description="Start a packet capture on a Docker container instance",
|
||||
input=NODE_CAPTURE_SCHEMA)
|
||||
async def start_capture(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
pcap_file_path = os.path.join(container.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
await container.start_capture(adapter_number, pcap_file_path)
|
||||
response.json({"pcap_file_path": str(pcap_file_path)})
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/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 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",
|
||||
409: "Container not started"
|
||||
},
|
||||
description="Stop a packet capture on a Docker container instance")
|
||||
async def stop_capture(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await container.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/docker/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = container.get_nio(adapter_number)
|
||||
await docker_manager.stream_pcap_file(nio, container.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/docker/images",
|
||||
status_codes={
|
||||
200: "Success",
|
||||
},
|
||||
output=DOCKER_LIST_IMAGES_SCHEMA,
|
||||
description="Get all available Docker images")
|
||||
async def show(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
images = await docker_manager.list_images()
|
||||
response.json(images)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/docker/nodes/{node_id}/console/ws",
|
||||
description="WebSocket for console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
})
|
||||
async def console_ws(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
return await container.start_websocket_console(request)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/nodes/{node_id}/console/reset",
|
||||
description="Reset console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Console has been reset",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "Container not started"
|
||||
})
|
||||
async def reset_console(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await container.reset_console()
|
||||
response.set_status(204)
|
||||
@@ -1,548 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import aiohttp
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.schemas.nio import NIO_SCHEMA
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.compute.dynamips.dynamips_error import DynamipsError
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
|
||||
from gns3server.schemas.node import (
|
||||
NODE_CAPTURE_SCHEMA,
|
||||
NODE_LIST_IMAGES_SCHEMA,
|
||||
)
|
||||
|
||||
from gns3server.schemas.dynamips_vm import (
|
||||
VM_CREATE_SCHEMA,
|
||||
VM_UPDATE_SCHEMA,
|
||||
VM_OBJECT_SCHEMA
|
||||
)
|
||||
|
||||
DEFAULT_CHASSIS = {
|
||||
"c1700": "1720",
|
||||
"c2600": "2610",
|
||||
"c3600": "3640"
|
||||
}
|
||||
|
||||
|
||||
class DynamipsVMHandler:
|
||||
|
||||
"""
|
||||
API entry points for Dynamips VMs.
|
||||
"""
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new Dynamips VM instance",
|
||||
input=VM_CREATE_SCHEMA,
|
||||
output=VM_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
platform = request.json.pop("platform")
|
||||
default_chassis = None
|
||||
if platform in DEFAULT_CHASSIS:
|
||||
default_chassis = DEFAULT_CHASSIS[platform]
|
||||
vm = await dynamips_manager.create_node(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("node_id"),
|
||||
dynamips_id=request.json.get("dynamips_id"),
|
||||
platform=platform,
|
||||
console=request.json.get("console"),
|
||||
console_type=request.json.get("console_type", "telnet"),
|
||||
aux=request.json.get("aux"),
|
||||
aux_type=request.json.pop("aux_type", "none"),
|
||||
chassis=request.json.pop("chassis", default_chassis),
|
||||
node_type="dynamips")
|
||||
await dynamips_manager.update_vm_settings(vm, request.json)
|
||||
response.set_status(201)
|
||||
response.json(vm)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/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 Dynamips VM instance",
|
||||
output=VM_OBJECT_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.json(vm)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/dynamips/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 Dynamips VM instance",
|
||||
input=VM_UPDATE_SCHEMA,
|
||||
output=VM_OBJECT_SCHEMA)
|
||||
async def update(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await dynamips_manager.update_vm_settings(vm, request.json)
|
||||
vm.updated()
|
||||
response.json(vm)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/dynamips/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 Dynamips VM instance")
|
||||
async def delete(request, response):
|
||||
|
||||
# check the project_id exists
|
||||
ProjectManager.instance().get_project(request.match_info["project_id"])
|
||||
await Dynamips.instance().delete_node(request.match_info["node_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/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 Dynamips VM instance")
|
||||
async def start(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
try:
|
||||
await dynamips_manager.ghost_ios_support(vm)
|
||||
except GeneratorExit:
|
||||
pass
|
||||
await vm.start()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/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 Dynamips VM instance")
|
||||
async def stop(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/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 Dynamips VM instance")
|
||||
async def suspend(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.suspend()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/resume",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance resumed",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Resume a suspended Dynamips VM instance")
|
||||
async def resume(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.resume()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/reload",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a Dynamips VM instance")
|
||||
async def reload(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.reload()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter where the nio should be added",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO created",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Add a NIO to a Dynamips VM instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def create_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = await dynamips_manager.create_nio(vm, request.json)
|
||||
slot_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.slot_add_nio_binding(slot_number, port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be updated"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO on a Dynamips instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
slot_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(slot_number, port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.slot_update_nio_binding(slot_number, port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter from where the nio should be removed",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
204: "NIO deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Remove a NIO from a Dynamips VM instance")
|
||||
async def delete_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
slot_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = await vm.slot_remove_nio_binding(slot_number, port_number)
|
||||
await nio.delete()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/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 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=NODE_CAPTURE_SCHEMA)
|
||||
async def start_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_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"])
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
# FIXME: Dynamips (Cygwin actually) doesn't like non ascii paths on Windows
|
||||
try:
|
||||
pcap_file_path.encode('ascii')
|
||||
except UnicodeEncodeError:
|
||||
raise DynamipsError('The capture file path "{}" must only contain ASCII (English) characters'.format(pcap_file_path))
|
||||
|
||||
await 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/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 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")
|
||||
async def stop_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
slot_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.stop_capture(slot_number, port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
slot_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(slot_number, port_number)
|
||||
await dynamips_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/idlepc_proposals",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
200: "Idle-PCs retrieved",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Retrieve the idlepc proposals")
|
||||
async def get_idlepcs(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.set_idlepc("0x0")
|
||||
idlepcs = await vm.get_idle_pc_prop()
|
||||
response.set_status(200)
|
||||
response.json(idlepcs)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/auto_idlepc",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
200: "Best Idle-pc value found",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Retrieve the idlepc proposals")
|
||||
async def get_auto_idlepc(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
idlepc = await dynamips_manager.auto_idlepc(vm)
|
||||
response.set_status(200)
|
||||
response.json({"idlepc": idlepc})
|
||||
|
||||
@Route.get(
|
||||
r"/dynamips/images",
|
||||
status_codes={
|
||||
200: "List of Dynamips IOS images",
|
||||
},
|
||||
description="Retrieve the list of Dynamips IOS images",
|
||||
output=NODE_LIST_IMAGES_SCHEMA)
|
||||
async def list_images(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
images = await dynamips_manager.list_images()
|
||||
response.set_status(200)
|
||||
response.json(images)
|
||||
|
||||
@Route.post(
|
||||
r"/dynamips/images/{filename:.+}",
|
||||
parameters={
|
||||
"filename": "Image filename"
|
||||
},
|
||||
status_codes={
|
||||
204: "Upload a Dynamips IOS image",
|
||||
},
|
||||
raw=True,
|
||||
description="Upload a Dynamips IOS image")
|
||||
async def upload_image(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
await dynamips_manager.write_image(request.match_info["filename"], request.content)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/dynamips/images/{filename:.+}",
|
||||
parameters={
|
||||
"filename": "Image filename"
|
||||
},
|
||||
status_codes={
|
||||
200: "Image returned",
|
||||
},
|
||||
raw=True,
|
||||
description="Download a Dynamips IOS image")
|
||||
async def download_image(request, response):
|
||||
filename = request.match_info["filename"]
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
image_path = dynamips_manager.get_abs_image_path(filename)
|
||||
|
||||
# Raise error if user try to escape
|
||||
if filename[0] == ".":
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
|
||||
await response.stream_file(image_path)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/duplicate",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance duplicated",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Duplicate a dynamips instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(request.match_info["node_id"],
|
||||
request.json["destination_node_id"])
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/console/ws",
|
||||
description="WebSocket for console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
})
|
||||
async def console_ws(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
return await vm.start_websocket_console(request)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/nodes/{node_id}/console/reset",
|
||||
description="Reset console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Console has been reset",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "Container not started"
|
||||
})
|
||||
async def reset_console(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.reset_console()
|
||||
response.set_status(204)
|
||||
@@ -1,314 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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.dynamips import Dynamips
|
||||
|
||||
from gns3server.schemas.ethernet_hub import (
|
||||
ETHERNET_HUB_CREATE_SCHEMA,
|
||||
ETHERNET_HUB_UPDATE_SCHEMA,
|
||||
ETHERNET_HUB_OBJECT_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
class EthernetHubHandler:
|
||||
|
||||
"""
|
||||
API entry points for Ethernet hub.
|
||||
"""
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/ethernet_hub/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new Ethernet hub instance",
|
||||
input=ETHERNET_HUB_CREATE_SCHEMA,
|
||||
output=ETHERNET_HUB_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
# Use the Dynamips Ethernet hub to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = await dynamips_manager.create_node(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("node_id"),
|
||||
node_type="ethernet_hub",
|
||||
ports=request.json.get("ports_mapping"))
|
||||
|
||||
response.set_status(201)
|
||||
response.json(node)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/ethernet_hub/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 hub instance",
|
||||
output=ETHERNET_HUB_OBJECT_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
response.json(node)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/ethernet_hub/nodes/{node_id}/duplicate",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance duplicated",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Duplicate an ethernet hub instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(request.match_info["node_id"],
|
||||
request.json["destination_node_id"])
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/ethernet_hub/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 hub instance",
|
||||
input=ETHERNET_HUB_UPDATE_SCHEMA,
|
||||
output=ETHERNET_HUB_OBJECT_SCHEMA)
|
||||
async def update(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
if "name" in request.json and node.name != request.json["name"]:
|
||||
await node.set_name(request.json["name"])
|
||||
if "ports_mapping" in request.json:
|
||||
node.ports_mapping = request.json["ports_mapping"]
|
||||
|
||||
node.updated()
|
||||
response.json(node)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/ethernet_hub/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 hub instance")
|
||||
async def delete(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
await dynamips_manager.delete_node(request.match_info["node_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/ethernet_hub/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 an Ethernet hub")
|
||||
def start(request, response):
|
||||
|
||||
Dynamips.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/ethernet_hub/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 an Ethernet hub")
|
||||
def stop(request, response):
|
||||
|
||||
Dynamips.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/ethernet_hub/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 an Ethernet hub (does nothing)")
|
||||
def suspend(request, response):
|
||||
|
||||
Dynamips.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/ethernet_hub/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 hub (always 0)",
|
||||
"port_number": "Port on the hub"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO created",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Add a NIO to an Ethernet hub instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def create_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = await dynamips_manager.create_nio(node, request.json)
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.add_nio(nio, port_number)
|
||||
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/ethernet_hub/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 hub (always 0)",
|
||||
"port_number": "Port on the hub"
|
||||
},
|
||||
status_codes={
|
||||
204: "NIO deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Remove a NIO from an Ethernet hub instance")
|
||||
async def delete_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = await node.remove_nio(port_number)
|
||||
await nio.delete()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/ethernet_hub/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 hub (always 0)",
|
||||
"port_number": "Port on the hub"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a packet capture on an Ethernet hub instance",
|
||||
input=NODE_CAPTURE_SCHEMA)
|
||||
async def start_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_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"])
|
||||
await 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_hub/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 hub (always 0)",
|
||||
"port_number": "Port on the hub"
|
||||
},
|
||||
status_codes={
|
||||
204: "Capture stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a packet capture on an Ethernet hub instance")
|
||||
async def stop_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/ethernet_hub/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the hub"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await dynamips_manager.stream_pcap_file(nio, node.project.id, request, response)
|
||||
@@ -1,341 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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.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)
|
||||
async def create(request, response):
|
||||
|
||||
# Use the Dynamips Ethernet switch to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = await dynamips_manager.create_node(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("node_id"),
|
||||
console=request.json.get("console"),
|
||||
console_type=request.json.get("console_type"),
|
||||
node_type="ethernet_switch",
|
||||
ports=request.json.get("ports_mapping"))
|
||||
|
||||
# On Linux, use the generic switch
|
||||
# builtin_manager = Builtin.instance()
|
||||
# node = await 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_node(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.post(
|
||||
r"/projects/{project_id}/ethernet_switch/nodes/{node_id}/duplicate",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance duplicated",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Duplicate an ethernet switch instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(request.match_info["node_id"],
|
||||
request.json["destination_node_id"])
|
||||
response.set_status(201)
|
||||
response.json(new_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)
|
||||
async def update(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
if "name" in request.json and node.name != request.json["name"]:
|
||||
await node.set_name(request.json["name"])
|
||||
if "ports_mapping" in request.json:
|
||||
node.ports_mapping = request.json["ports_mapping"]
|
||||
await node.update_port_settings()
|
||||
if "console_type" in request.json:
|
||||
node.console_type = request.json["console_type"]
|
||||
|
||||
# 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")
|
||||
async def delete(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
await dynamips_manager.delete_node(request.match_info["node_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/ethernet_switch/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 an Ethernet switch")
|
||||
def start(request, response):
|
||||
|
||||
Dynamips.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/ethernet_switch/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 an Ethernet switch")
|
||||
def stop(request, response):
|
||||
|
||||
Dynamips.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/ethernet_switch/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 an Ethernet switch (does nothing)")
|
||||
def suspend(request, response):
|
||||
|
||||
Dynamips.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_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)
|
||||
async def create_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = await dynamips_manager.create_nio(node, request.json)
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.add_nio(nio, port_number)
|
||||
|
||||
#builtin_manager = Builtin.instance()
|
||||
#node = builtin_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
#nio = await builtin_manager.create_nio(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")
|
||||
async def delete_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(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 = await node.remove_nio(port_number)
|
||||
await 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)
|
||||
async def start_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(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"])
|
||||
await 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")
|
||||
async def stop_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(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"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/ethernet_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the switch"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await dynamips_manager.stream_pcap_file(nio, node.project.id, request, response)
|
||||
@@ -1,312 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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.dynamips import Dynamips
|
||||
|
||||
from gns3server.schemas.frame_relay_switch import (
|
||||
FRAME_RELAY_SWITCH_CREATE_SCHEMA,
|
||||
FRAME_RELAY_SWITCH_OBJECT_SCHEMA,
|
||||
FRAME_RELAY_SWITCH_UPDATE_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
class FrameRelaySwitchHandler:
|
||||
|
||||
"""
|
||||
API entry points for Frame Relay switch.
|
||||
"""
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/frame_relay_switch/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new Frame Relay switch instance",
|
||||
input=FRAME_RELAY_SWITCH_CREATE_SCHEMA,
|
||||
output=FRAME_RELAY_SWITCH_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
# Use the Dynamips Frame Relay switch to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = await dynamips_manager.create_node(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("node_id"),
|
||||
node_type="frame_relay_switch",
|
||||
mappings=request.json.get("mappings"))
|
||||
response.set_status(201)
|
||||
response.json(node)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/frame_relay_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 a Frame Relay switch instance",
|
||||
output=FRAME_RELAY_SWITCH_OBJECT_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.json(node)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/frame_relay_switch/nodes/{node_id}/duplicate",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance duplicated",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Duplicate a frame relay switch instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await Dynamips.instance().duplicate_node(
|
||||
request.match_info["node_id"],
|
||||
request.json["destination_node_id"]
|
||||
)
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/frame_relay_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 a Frame Relay switch instance",
|
||||
input=FRAME_RELAY_SWITCH_UPDATE_SCHEMA,
|
||||
output=FRAME_RELAY_SWITCH_OBJECT_SCHEMA)
|
||||
async def update(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
if "name" in request.json and node.name != request.json["name"]:
|
||||
await node.set_name(request.json["name"])
|
||||
if "mappings" in request.json:
|
||||
node.mappings = request.json["mappings"]
|
||||
node.updated()
|
||||
response.json(node)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/frame_relay_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 a Frame Relay switch instance")
|
||||
async def delete(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
await dynamips_manager.delete_node(request.match_info["node_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/frame_relay_switch/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 Frame Relay switch")
|
||||
def start(request, response):
|
||||
|
||||
Dynamips.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/frame_relay_switch/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 Frame Relay switch")
|
||||
def stop(request, response):
|
||||
|
||||
Dynamips.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/frame_relay_switch/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 Frame Relay switch (does nothing)")
|
||||
def suspend(request, response):
|
||||
|
||||
Dynamips.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/frame_relay_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 a Frame Relay switch instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def create_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio = await dynamips_manager.create_nio(node, request.json)
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.add_nio(nio, port_number)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/frame_relay_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 a Frame Relay switch instance")
|
||||
async def delete_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = await node.remove_nio(port_number)
|
||||
await nio.delete()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/frame_relay_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 a Frame Relay switch instance",
|
||||
input=NODE_CAPTURE_SCHEMA)
|
||||
async def start_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_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"])
|
||||
await 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}/frame_relay_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 a Frame Relay switch instance")
|
||||
async def stop_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/frame_relay_switch/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the switch"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node = dynamips_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = node.get_nio(port_number)
|
||||
await dynamips_manager.stream_pcap_file(nio, node.project.id, request, response)
|
||||
@@ -1,488 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
|
||||
import aiohttp.web
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.schemas.nio import NIO_SCHEMA
|
||||
from gns3server.compute.iou import IOU
|
||||
|
||||
from gns3server.schemas.node import (
|
||||
NODE_CAPTURE_SCHEMA,
|
||||
NODE_LIST_IMAGES_SCHEMA,
|
||||
)
|
||||
|
||||
from gns3server.schemas.iou import (
|
||||
IOU_CREATE_SCHEMA,
|
||||
IOU_START_SCHEMA,
|
||||
IOU_OBJECT_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
class IOUHandler:
|
||||
|
||||
"""
|
||||
API entry points for IOU.
|
||||
"""
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new IOU instance",
|
||||
input=IOU_CREATE_SCHEMA,
|
||||
output=IOU_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
iou = IOU.instance()
|
||||
vm = await iou.create_node(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("node_id"),
|
||||
application_id=request.json.get("application_id"),
|
||||
path=request.json.get("path"),
|
||||
console=request.json.get("console"),
|
||||
console_type=request.json.get("console_type", "telnet"))
|
||||
|
||||
for name, value in request.json.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
if name == "application_id":
|
||||
continue # we must ignore this to avoid overwriting the application_id allocated by the controller
|
||||
if name == "startup_config_content" and (vm.startup_config_content and len(vm.startup_config_content) > 0):
|
||||
continue
|
||||
if name == "private_config_content" and (vm.private_config_content and len(vm.private_config_content) > 0):
|
||||
continue
|
||||
if request.json.get("use_default_iou_values") and (name == "ram" or name == "nvram"):
|
||||
continue
|
||||
setattr(vm, name, value)
|
||||
response.set_status(201)
|
||||
response.json(vm)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/iou/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 IOU instance",
|
||||
output=IOU_OBJECT_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.json(vm)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/iou/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 IOU instance",
|
||||
input=IOU_OBJECT_SCHEMA,
|
||||
output=IOU_OBJECT_SCHEMA)
|
||||
async def update(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
for name, value in request.json.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
if name == "application_id":
|
||||
continue # we must ignore this to avoid overwriting the application_id allocated by the IOU manager
|
||||
setattr(vm, name, value)
|
||||
|
||||
if vm.use_default_iou_values:
|
||||
# update the default IOU values in case the image or use_default_iou_values have changed
|
||||
# this is important to have the correct NVRAM amount in order to correctly push the configs to the NVRAM
|
||||
await vm.update_default_iou_values()
|
||||
vm.updated()
|
||||
response.json(vm)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/iou/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 IOU instance")
|
||||
async def delete(request, response):
|
||||
|
||||
await IOU.instance().delete_node(request.match_info["node_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/nodes/{node_id}/duplicate",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance duplicated",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Duplicate a IOU instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await IOU.instance().duplicate_node(
|
||||
request.match_info["node_id"],
|
||||
request.json["destination_node_id"]
|
||||
)
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/nodes/{node_id}/start",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "Instance started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
input=IOU_START_SCHEMA,
|
||||
output=IOU_OBJECT_SCHEMA,
|
||||
description="Start an IOU instance")
|
||||
async def start(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
for name, value in request.json.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
await vm.start()
|
||||
response.json(vm)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/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 an IOU instance")
|
||||
async def stop(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/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 an IOU instance (does nothing)")
|
||||
def suspend(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/nodes/{node_id}/reload",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload an IOU instance")
|
||||
async def reload(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.reload()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port where the nio should be added"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO created",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Add a NIO to a IOU instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def create_nio(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio_type = request.json["type"]
|
||||
if nio_type not in ("nio_udp", "nio_tap", "nio_ethernet", "nio_generic_ethernet"):
|
||||
raise aiohttp.web.HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
nio = iou_manager.create_nio(request.json)
|
||||
await vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), int(request.match_info["port_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/iou/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port where the nio should be added"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Update a NIO on an IOU instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def update_nio(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(adapter_number, port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.adapter_update_nio_binding(adapter_number, port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/iou/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be removed"
|
||||
},
|
||||
status_codes={
|
||||
204: "NIO deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Remove a NIO from a IOU instance")
|
||||
async def delete_nio(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]), int(request.match_info["port_number"]))
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/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 to start a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "VM not started"
|
||||
},
|
||||
description="Start a packet capture on an IOU VM instance",
|
||||
input=NODE_CAPTURE_SCHEMA)
|
||||
async def start_capture(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_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"])
|
||||
await vm.start_capture(adapter_number, port_number, pcap_file_path, request.json["data_link_type"])
|
||||
response.json({"pcap_file_path": str(pcap_file_path)})
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/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 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",
|
||||
409: "VM not started"
|
||||
},
|
||||
description="Stop a packet capture on an IOU VM instance")
|
||||
async def stop_capture(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.stop_capture(adapter_number, port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/iou/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(adapter_number, port_number)
|
||||
await iou_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/iou/images",
|
||||
status_codes={
|
||||
200: "List of IOU images",
|
||||
},
|
||||
description="Retrieve the list of IOU images",
|
||||
output=NODE_LIST_IMAGES_SCHEMA)
|
||||
async def list_iou_images(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
images = await iou_manager.list_images()
|
||||
response.set_status(200)
|
||||
response.json(images)
|
||||
|
||||
@Route.post(
|
||||
r"/iou/images/{filename:.+}",
|
||||
parameters={
|
||||
"filename": "Image filename"
|
||||
},
|
||||
status_codes={
|
||||
204: "Image uploaded",
|
||||
},
|
||||
raw=True,
|
||||
description="Upload an IOU image")
|
||||
async def upload_image(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
await iou_manager.write_image(request.match_info["filename"], request.content)
|
||||
response.set_status(204)
|
||||
|
||||
|
||||
@Route.get(
|
||||
r"/iou/images/{filename:.+}",
|
||||
parameters={
|
||||
"filename": "Image filename"
|
||||
},
|
||||
status_codes={
|
||||
200: "Image returned",
|
||||
},
|
||||
raw=True,
|
||||
description="Download an IOU image")
|
||||
async def download_image(request, response):
|
||||
filename = request.match_info["filename"]
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
image_path = iou_manager.get_abs_image_path(filename)
|
||||
|
||||
# Raise error if user try to escape
|
||||
if filename[0] == ".":
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
|
||||
await response.stream_file(image_path)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/iou/nodes/{node_id}/console/ws",
|
||||
description="WebSocket for console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
})
|
||||
async def console_ws(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
return await vm.start_websocket_console(request)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/nodes/{node_id}/console/reset",
|
||||
description="Reset console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Console has been reset",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "Container not started"
|
||||
})
|
||||
async def reset_console(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.reset_console()
|
||||
response.set_status(204)
|
||||
@@ -1,318 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 aiohttp.web import HTTPConflict
|
||||
|
||||
from gns3server.schemas.nat import (
|
||||
NAT_CREATE_SCHEMA,
|
||||
NAT_OBJECT_SCHEMA,
|
||||
NAT_UPDATE_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
class NatHandler:
|
||||
|
||||
"""
|
||||
API entry points for nat
|
||||
"""
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nat/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new nat instance",
|
||||
input=NAT_CREATE_SCHEMA,
|
||||
output=NAT_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node = await builtin_manager.create_node(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("node_id"),
|
||||
node_type="nat",
|
||||
ports=request.json.get("ports_mapping"))
|
||||
response.set_status(201)
|
||||
response.json(node)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/nat/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 nat instance",
|
||||
output=NAT_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}/nat/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 nat instance",
|
||||
input=NAT_UPDATE_SCHEMA,
|
||||
output=NAT_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"])
|
||||
for name, value in request.json.items():
|
||||
if hasattr(node, name) and getattr(node, name) != value:
|
||||
setattr(node, name, value)
|
||||
node.updated()
|
||||
response.json(node)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/nat/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 nat instance")
|
||||
async def delete(request, response):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
await builtin_manager.delete_node(request.match_info["node_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nat/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 nat")
|
||||
def start(request, response):
|
||||
|
||||
Builtin.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nat/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 nat")
|
||||
def stop(request, response):
|
||||
|
||||
Builtin.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nat/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 nat (does nothing)")
|
||||
def suspend(request, response):
|
||||
|
||||
Builtin.instance().get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nat/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 nat (always 0)",
|
||||
"port_number": "Port on the nat"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO created",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Add a NIO to a nat instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async 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 = builtin_manager.create_nio(request.json)
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await node.add_nio(nio, port_number)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/nat/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be updated"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO on a NAT instance")
|
||||
async def update_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 = node.get_nio(port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await node.update_nio(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/nat/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 nat (always 0)",
|
||||
"port_number": "Port on the nat"
|
||||
},
|
||||
status_codes={
|
||||
204: "NIO deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Remove a NIO from a nat instance")
|
||||
async 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"])
|
||||
await node.remove_nio(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nat/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 nat (always 0)",
|
||||
"port_number": "Port on the nat"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a packet capture on a nat instance",
|
||||
input=NODE_CAPTURE_SCHEMA)
|
||||
async def start_capture(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"])
|
||||
pcap_file_path = os.path.join(node.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
await 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}/nat/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 nat (always 0)",
|
||||
"port_number": "Port on the nat"
|
||||
},
|
||||
status_codes={
|
||||
204: "Capture stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a packet capture on a nat instance")
|
||||
async def stop_capture(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"])
|
||||
await node.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/nat/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture (always 0)",
|
||||
"port_number": "Port on the nat"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(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 = node.get_nio(port_number)
|
||||
await builtin_manager.stream_pcap_file(nio, node.project.id, request, response)
|
||||
@@ -1,59 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.compute.port_manager import PortManager
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
from gns3server.utils.interfaces import interfaces
|
||||
|
||||
|
||||
class NetworkHandler:
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/ports/udp",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
201: "UDP port allocated",
|
||||
404: "The project doesn't exist"
|
||||
},
|
||||
description="Allocate an UDP port on the server")
|
||||
def allocate_udp_port(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
m = PortManager.instance()
|
||||
udp_port = m.get_free_udp_port(project)
|
||||
response.set_status(201)
|
||||
response.json({"udp_port": udp_port})
|
||||
|
||||
@Route.get(
|
||||
r"/network/interfaces",
|
||||
description="List all the network interfaces available on the server")
|
||||
def network_interfaces(request, response):
|
||||
|
||||
network_interfaces = interfaces()
|
||||
response.json(network_interfaces)
|
||||
|
||||
@Route.get(
|
||||
r"/network/ports",
|
||||
description="List all the ports used by the server")
|
||||
def network_ports(request, response):
|
||||
|
||||
m = PortManager.instance()
|
||||
response.json(m)
|
||||
@@ -1,64 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import asyncio
|
||||
import aiohttp
|
||||
from aiohttp.web import WebSocketResponse
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.compute.notification_manager import NotificationManager
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def process_websocket(ws):
|
||||
"""
|
||||
Process ping / pong and close message
|
||||
"""
|
||||
try:
|
||||
await ws.receive()
|
||||
except aiohttp.WSServerHandshakeError:
|
||||
pass
|
||||
|
||||
|
||||
class NotificationHandler:
|
||||
|
||||
@Route.get(
|
||||
r"/notifications/ws",
|
||||
description="Send notifications using Websockets")
|
||||
async def notifications(request, response):
|
||||
notifications = NotificationManager.instance()
|
||||
ws = WebSocketResponse()
|
||||
await ws.prepare(request)
|
||||
|
||||
request.app['websockets'].add(ws)
|
||||
asyncio.ensure_future(process_websocket(ws))
|
||||
log.info("New client has connected to compute WebSocket")
|
||||
try:
|
||||
with notifications.queue() as queue:
|
||||
while True:
|
||||
notification = await queue.get_json(1)
|
||||
if ws.closed:
|
||||
break
|
||||
await ws.send_str(notification)
|
||||
finally:
|
||||
log.info("Client has disconnected from compute WebSocket")
|
||||
if not ws.closed:
|
||||
await ws.close()
|
||||
request.app['websockets'].discard(ws)
|
||||
|
||||
return ws
|
||||
@@ -1,302 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import aiohttp
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import psutil
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
from gns3server.compute import MODULES
|
||||
from gns3server.utils.cpu_percent import CpuPercent
|
||||
from gns3server.utils.path import get_default_project_directory
|
||||
|
||||
from gns3server.schemas.project import (
|
||||
PROJECT_OBJECT_SCHEMA,
|
||||
PROJECT_CREATE_SCHEMA,
|
||||
PROJECT_UPDATE_SCHEMA,
|
||||
PROJECT_FILE_LIST_SCHEMA,
|
||||
PROJECT_LIST_SCHEMA
|
||||
)
|
||||
|
||||
import logging
|
||||
log = logging.getLogger()
|
||||
|
||||
CHUNK_SIZE = 1024 * 8 # 8KB
|
||||
|
||||
|
||||
class ProjectHandler:
|
||||
|
||||
# How many clients have subscribed to notifications
|
||||
_notifications_listening = {}
|
||||
|
||||
@Route.get(
|
||||
r"/projects",
|
||||
description="List all projects opened on the server",
|
||||
status_codes={
|
||||
200: "Project list",
|
||||
},
|
||||
output=PROJECT_LIST_SCHEMA
|
||||
)
|
||||
def list_projects(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
response.set_status(200)
|
||||
response.json(list(pm.projects))
|
||||
|
||||
@Route.post(
|
||||
r"/projects",
|
||||
description="Create a new project on the server",
|
||||
status_codes={
|
||||
201: "Project created",
|
||||
403: "Forbidden to create a project",
|
||||
409: "Project already created"
|
||||
},
|
||||
output=PROJECT_OBJECT_SCHEMA,
|
||||
input=PROJECT_CREATE_SCHEMA)
|
||||
def create_project(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
p = pm.create_project(
|
||||
name=request.json.get("name"),
|
||||
path=request.json.get("path"),
|
||||
project_id=request.json.get("project_id"),
|
||||
variables=request.json.get("variables", None)
|
||||
)
|
||||
response.set_status(201)
|
||||
response.json(p)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}",
|
||||
description="Update the project on the server",
|
||||
status_codes={
|
||||
201: "Project updated",
|
||||
403: "Forbidden to update a project"
|
||||
},
|
||||
output=PROJECT_OBJECT_SCHEMA,
|
||||
input=PROJECT_UPDATE_SCHEMA)
|
||||
async def update_project(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
await project.update(
|
||||
variables=request.json.get("variables", None)
|
||||
)
|
||||
response.set_status(200)
|
||||
response.json(project)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}",
|
||||
description="Get project information",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
200: "Success",
|
||||
404: "The project doesn't exist"
|
||||
},
|
||||
output=PROJECT_OBJECT_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
response.json(project)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/close",
|
||||
description="Close a project",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Project closed",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
async def close(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
if ProjectHandler._notifications_listening.setdefault(project.id, 0) <= 1:
|
||||
await project.close()
|
||||
pm.remove_project(project.id)
|
||||
try:
|
||||
del ProjectHandler._notifications_listening[project.id]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
log.warning("Skip project closing, another client is listening for project notifications")
|
||||
response.set_status(204)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}",
|
||||
description="Delete a project from disk",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Changes have been written on disk",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
async def delete(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
await project.delete()
|
||||
pm.remove_project(project.id)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/notifications",
|
||||
description="Receive notifications about the project",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
200: "End of stream",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
async def notification(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
|
||||
response.content_type = "application/json"
|
||||
response.set_status(200)
|
||||
response.enable_chunked_encoding()
|
||||
|
||||
response.start(request)
|
||||
queue = project.get_listen_queue()
|
||||
ProjectHandler._notifications_listening.setdefault(project.id, 0)
|
||||
ProjectHandler._notifications_listening[project.id] += 1
|
||||
await response.write("{}\n".format(json.dumps(ProjectHandler._getPingMessage())).encode("utf-8"))
|
||||
while True:
|
||||
try:
|
||||
(action, msg) = await asyncio.wait_for(queue.get(), 5)
|
||||
if hasattr(msg, "__json__"):
|
||||
msg = json.dumps({"action": action, "event": msg.__json__()}, sort_keys=True)
|
||||
else:
|
||||
msg = json.dumps({"action": action, "event": msg}, sort_keys=True)
|
||||
log.debug("Send notification: %s", msg)
|
||||
await response.write(("{}\n".format(msg)).encode("utf-8"))
|
||||
except asyncio.TimeoutError:
|
||||
await response.write("{}\n".format(json.dumps(ProjectHandler._getPingMessage())).encode("utf-8"))
|
||||
project.stop_listen_queue(queue)
|
||||
if project.id in ProjectHandler._notifications_listening:
|
||||
ProjectHandler._notifications_listening[project.id] -= 1
|
||||
|
||||
def _getPingMessage(cls):
|
||||
"""
|
||||
Ping messages are regularly sent to the client to
|
||||
keep the connection open. We send with it some information about server load.
|
||||
|
||||
:returns: hash
|
||||
"""
|
||||
stats = {}
|
||||
# Non blocking call in order to get cpu usage. First call will return 0
|
||||
stats["cpu_usage_percent"] = CpuPercent.get(interval=None)
|
||||
stats["memory_usage_percent"] = psutil.virtual_memory().percent
|
||||
stats["disk_usage_percent"] = psutil.disk_usage(get_default_project_directory()).percent
|
||||
return {"action": "ping", "event": stats}
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/files",
|
||||
description="List files of a project",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
200: "Return a list of files",
|
||||
404: "The project doesn't exist"
|
||||
},
|
||||
output=PROJECT_FILE_LIST_SCHEMA)
|
||||
async def list_files(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
files = await project.list_files()
|
||||
response.json(files)
|
||||
response.set_status(200)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/files/{path:.+}",
|
||||
description="Get a file from a project",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def get_file(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
path = request.match_info["path"]
|
||||
path = os.path.normpath(path)
|
||||
|
||||
# Raise error if user try to escape
|
||||
if path[0] == ".":
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
path = os.path.join(project.path, path)
|
||||
|
||||
await response.stream_file(path)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/files/{path:.+}",
|
||||
description="Write a file to a project",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
raw=True,
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The path doesn't exist"
|
||||
})
|
||||
async def write_file(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
path = request.match_info["path"]
|
||||
path = os.path.normpath(path)
|
||||
|
||||
# Raise error if user try to escape
|
||||
if path[0] == ".":
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
path = os.path.join(project.path, path)
|
||||
|
||||
response.set_status(200)
|
||||
|
||||
try:
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, 'wb+') as f:
|
||||
while True:
|
||||
try:
|
||||
chunk = await request.content.read(CHUNK_SIZE)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when writing to file '{}'".format(path))
|
||||
if not chunk:
|
||||
break
|
||||
f.write(chunk)
|
||||
|
||||
except FileNotFoundError:
|
||||
raise aiohttp.web.HTTPNotFound()
|
||||
except PermissionError:
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
@@ -1,617 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
import os.path
|
||||
|
||||
import aiohttp.web
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
from gns3server.schemas.nio import NIO_SCHEMA
|
||||
from gns3server.compute.qemu import Qemu
|
||||
from gns3server.config import Config
|
||||
|
||||
from gns3server.schemas.node import (
|
||||
NODE_LIST_IMAGES_SCHEMA,
|
||||
NODE_CAPTURE_SCHEMA
|
||||
)
|
||||
|
||||
from gns3server.schemas.qemu import (
|
||||
QEMU_CREATE_SCHEMA,
|
||||
QEMU_UPDATE_SCHEMA,
|
||||
QEMU_OBJECT_SCHEMA,
|
||||
QEMU_RESIZE_SCHEMA,
|
||||
QEMU_BINARY_LIST_SCHEMA,
|
||||
QEMU_BINARY_FILTER_SCHEMA,
|
||||
QEMU_CAPABILITY_LIST_SCHEMA,
|
||||
QEMU_IMAGE_CREATE_SCHEMA,
|
||||
QEMU_IMAGE_UPDATE_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
class QEMUHandler:
|
||||
|
||||
"""
|
||||
API entry points for QEMU.
|
||||
"""
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new Qemu VM instance",
|
||||
input=QEMU_CREATE_SCHEMA,
|
||||
output=QEMU_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
qemu = Qemu.instance()
|
||||
vm = await qemu.create_node(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.pop("node_id", None),
|
||||
linked_clone=request.json.get("linked_clone", True),
|
||||
qemu_path=request.json.pop("qemu_path", None),
|
||||
console=request.json.pop("console", None),
|
||||
console_type=request.json.pop("console_type", "telnet"),
|
||||
aux=request.json.get("aux"),
|
||||
aux_type=request.json.pop("aux_type", "none"),
|
||||
platform=request.json.pop("platform", None))
|
||||
|
||||
for name, value in request.json.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
response.set_status(201)
|
||||
response.json(vm)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/qemu/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 Qemu VM instance",
|
||||
output=QEMU_OBJECT_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.json(vm)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/qemu/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 Qemu VM instance",
|
||||
input=QEMU_UPDATE_SCHEMA,
|
||||
output=QEMU_OBJECT_SCHEMA)
|
||||
async def update(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
# update the console first to avoid issue if updating console type
|
||||
vm.console = request.json.pop("console", vm.console)
|
||||
for name, value in request.json.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
if name == "cdrom_image":
|
||||
# let the guest know about the new cdrom image
|
||||
await vm.update_cdrom_image()
|
||||
|
||||
vm.updated()
|
||||
response.json(vm)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/qemu/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 Qemu VM instance")
|
||||
async def delete(request, response):
|
||||
|
||||
await Qemu.instance().delete_node(request.match_info["node_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/nodes/{node_id}/duplicate",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance duplicated",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Duplicate a Qemu instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await Qemu.instance().duplicate_node(
|
||||
request.match_info["node_id"],
|
||||
request.json["destination_node_id"]
|
||||
)
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/nodes/{node_id}/resize_disk",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance updated",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Resize a Qemu VM disk image",
|
||||
input=QEMU_RESIZE_SCHEMA)
|
||||
async def resize_disk(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.resize_disk(request.json["drive_name"], request.json["extend"])
|
||||
response.set_status(201)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/nodes/{node_id}/start",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "Instance started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a Qemu VM instance",
|
||||
output=QEMU_OBJECT_SCHEMA)
|
||||
async def start(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
hardware_accel = qemu_manager.config.get_section_config("Qemu").getboolean("enable_hardware_acceleration", True)
|
||||
if sys.platform.startswith("linux"):
|
||||
# the enable_kvm option was used before version 2.0 and has priority
|
||||
enable_kvm = qemu_manager.config.get_section_config("Qemu").getboolean("enable_kvm")
|
||||
if enable_kvm is not None:
|
||||
hardware_accel = enable_kvm
|
||||
if hardware_accel and "-no-kvm" not in vm.options and "-no-hax" not in vm.options:
|
||||
pm = ProjectManager.instance()
|
||||
if pm.check_hardware_virtualization(vm) is False:
|
||||
raise aiohttp.web.HTTPConflict(text="Cannot start VM with hardware acceleration (KVM/HAX) enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox")
|
||||
await vm.start()
|
||||
response.json(vm)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/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 Qemu VM instance")
|
||||
async def stop(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/nodes/{node_id}/reload",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a Qemu VM instance")
|
||||
async def reload(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.reload()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/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 Qemu VM instance")
|
||||
async def suspend(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.suspend()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/nodes/{node_id}/resume",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance resumed",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Resume a Qemu VM instance")
|
||||
async def resume(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.resume()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO created",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Add a NIO to a Qemu VM instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def create_nio(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio_type = request.json["type"]
|
||||
if nio_type not in ("nio_udp"):
|
||||
raise aiohttp.web.HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
nio = qemu_manager.create_nio(request.json)
|
||||
await vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/qemu/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be updated"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO on a Qemu instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
if "suspend" in request.json:
|
||||
nio.suspend = request.json["suspend"]
|
||||
await vm.adapter_update_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/qemu/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
204: "NIO deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Remove a NIO from a Qemu VM instance")
|
||||
async def delete_nio(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.adapter_remove_nio_binding(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/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 to start a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
},
|
||||
description="Start a packet capture on a Qemu VM instance",
|
||||
input=NODE_CAPTURE_SCHEMA)
|
||||
async def start_capture(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
await vm.start_capture(adapter_number, pcap_file_path)
|
||||
response.json({"pcap_file_path": pcap_file_path})
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/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 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 Qemu VM instance")
|
||||
async def stop_capture(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/qemu/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
await qemu_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/qemu/binaries",
|
||||
status_codes={
|
||||
200: "Success",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Get a list of available Qemu binaries",
|
||||
input=QEMU_BINARY_FILTER_SCHEMA,
|
||||
output=QEMU_BINARY_LIST_SCHEMA)
|
||||
async def list_binaries(request, response):
|
||||
|
||||
binaries = await Qemu.binary_list(request.json.get("archs", None))
|
||||
response.json(binaries)
|
||||
|
||||
@Route.get(
|
||||
r"/qemu/img-binaries",
|
||||
status_codes={
|
||||
200: "Success",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Get a list of available Qemu-img binaries",
|
||||
output=QEMU_BINARY_LIST_SCHEMA)
|
||||
async def list_img_binaries(request, response):
|
||||
|
||||
binaries = await Qemu.img_binary_list()
|
||||
response.json(binaries)
|
||||
|
||||
@Route.get(
|
||||
r"/qemu/capabilities",
|
||||
status_codes={
|
||||
200: "Success"
|
||||
},
|
||||
description="Get a list of Qemu capabilities on this server",
|
||||
output=QEMU_CAPABILITY_LIST_SCHEMA
|
||||
)
|
||||
async def get_capabilities(request, response):
|
||||
capabilities = {"kvm": []}
|
||||
kvms = await Qemu.get_kvm_archs()
|
||||
if kvms:
|
||||
capabilities["kvm"] = kvms
|
||||
response.json(capabilities)
|
||||
|
||||
@Route.post(
|
||||
r"/qemu/img",
|
||||
status_codes={
|
||||
201: "Image created",
|
||||
},
|
||||
description="Create a Qemu image",
|
||||
input=QEMU_IMAGE_CREATE_SCHEMA
|
||||
)
|
||||
async def create_img(request, response):
|
||||
|
||||
qemu_img = request.json.pop("qemu_img")
|
||||
path = request.json.pop("path")
|
||||
if os.path.isabs(path):
|
||||
config = Config.instance()
|
||||
if config.get_section_config("Server").getboolean("local", False) is False:
|
||||
response.set_status(403)
|
||||
return
|
||||
|
||||
await Qemu.instance().create_disk(qemu_img, path, request.json)
|
||||
response.set_status(201)
|
||||
|
||||
@Route.put(
|
||||
r"/qemu/img",
|
||||
status_codes={
|
||||
201: "Image Updated",
|
||||
},
|
||||
description="Update a Qemu image",
|
||||
input=QEMU_IMAGE_UPDATE_SCHEMA
|
||||
)
|
||||
async def update_img(request, response):
|
||||
|
||||
qemu_img = request.json.pop("qemu_img")
|
||||
path = request.json.pop("path")
|
||||
if os.path.isabs(path):
|
||||
config = Config.instance()
|
||||
if config.get_section_config("Server").getboolean("local", False) is False:
|
||||
response.set_status(403)
|
||||
return
|
||||
|
||||
if "extend" in request.json:
|
||||
await Qemu.instance().resize_disk(qemu_img, path, request.json.pop("extend"))
|
||||
response.set_status(201)
|
||||
|
||||
@Route.get(
|
||||
r"/qemu/images",
|
||||
status_codes={
|
||||
200: "List of Qemu images",
|
||||
},
|
||||
description="Retrieve the list of Qemu images",
|
||||
output=NODE_LIST_IMAGES_SCHEMA)
|
||||
async def list_qemu_images(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
images = await qemu_manager.list_images()
|
||||
response.set_status(200)
|
||||
response.json(images)
|
||||
|
||||
@Route.post(
|
||||
r"/qemu/images/{filename:.+}",
|
||||
parameters={
|
||||
"filename": "Image filename"
|
||||
},
|
||||
status_codes={
|
||||
204: "Image uploaded",
|
||||
},
|
||||
raw=True,
|
||||
description="Upload Qemu image")
|
||||
async def upload_image(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
await qemu_manager.write_image(request.match_info["filename"], request.content)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/qemu/images/{filename:.+}",
|
||||
parameters={
|
||||
"filename": "Image filename"
|
||||
},
|
||||
status_codes={
|
||||
200: "Image returned",
|
||||
},
|
||||
raw=True,
|
||||
description="Download Qemu image")
|
||||
async def download_image(request, response):
|
||||
filename = request.match_info["filename"]
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
image_path = qemu_manager.get_abs_image_path(filename)
|
||||
|
||||
# Raise error if user try to escape
|
||||
if filename[0] == ".":
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
|
||||
await response.stream_file(image_path)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/qemu/nodes/{node_id}/console/ws",
|
||||
description="WebSocket for console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
})
|
||||
async def console_ws(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
return await vm.start_websocket_console(request)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/nodes/{node_id}/console/reset",
|
||||
description="Reset console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Console has been reset",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "Container not started"
|
||||
})
|
||||
async def reset_console(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.reset_console()
|
||||
response.set_status(204)
|
||||
@@ -1,135 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import psutil
|
||||
import platform
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.config import Config
|
||||
from gns3server.schemas.version import VERSION_SCHEMA
|
||||
from gns3server.schemas.server_statistics import SERVER_STATISTICS_SCHEMA
|
||||
from gns3server.compute.port_manager import PortManager
|
||||
from gns3server.utils.cpu_percent import CpuPercent
|
||||
from gns3server.version import __version__
|
||||
from gns3server.utils.path import get_default_project_directory
|
||||
from aiohttp.web import HTTPConflict
|
||||
|
||||
|
||||
class ServerHandler:
|
||||
|
||||
@Route.get(
|
||||
r"/version",
|
||||
description="Retrieve the server version number",
|
||||
output=VERSION_SCHEMA)
|
||||
def version(request, response):
|
||||
|
||||
config = Config.instance()
|
||||
local_server = config.get_section_config("Server").getboolean("local", False)
|
||||
response.json({"version": __version__, "local": local_server})
|
||||
|
||||
@Route.get(
|
||||
r"/statistics",
|
||||
description="Retrieve server statistics",
|
||||
output=SERVER_STATISTICS_SCHEMA,
|
||||
status_codes={
|
||||
200: "Statistics information returned",
|
||||
409: "Conflict"
|
||||
})
|
||||
def statistics(request, response):
|
||||
|
||||
try:
|
||||
memory_total = psutil.virtual_memory().total
|
||||
memory_free = psutil.virtual_memory().available
|
||||
memory_used = memory_total - memory_free # actual memory usage in a cross platform fashion
|
||||
swap_total = psutil.swap_memory().total
|
||||
swap_free = psutil.swap_memory().free
|
||||
swap_used = psutil.swap_memory().used
|
||||
cpu_percent = int(CpuPercent.get())
|
||||
load_average_percent = [int(x / psutil.cpu_count() * 100) for x in psutil.getloadavg()]
|
||||
memory_percent = int(psutil.virtual_memory().percent)
|
||||
swap_percent = int(psutil.swap_memory().percent)
|
||||
disk_usage_percent = int(psutil.disk_usage(get_default_project_directory()).percent)
|
||||
except psutil.Error as e:
|
||||
raise HTTPConflict(text="Psutil error detected: {}".format(e))
|
||||
response.json({"memory_total": memory_total,
|
||||
"memory_free": memory_free,
|
||||
"memory_used": memory_used,
|
||||
"swap_total": swap_total,
|
||||
"swap_free": swap_free,
|
||||
"swap_used": swap_used,
|
||||
"cpu_usage_percent": cpu_percent,
|
||||
"memory_usage_percent": memory_percent,
|
||||
"swap_usage_percent": swap_percent,
|
||||
"disk_usage_percent": disk_usage_percent,
|
||||
"load_average_percent": load_average_percent})
|
||||
|
||||
@Route.get(
|
||||
r"/debug",
|
||||
description="Return debug information about the compute",
|
||||
status_codes={
|
||||
201: "Written"
|
||||
})
|
||||
def debug(request, response):
|
||||
response.content_type = "text/plain"
|
||||
response.text = ServerHandler._getDebugData()
|
||||
|
||||
@staticmethod
|
||||
def _getDebugData():
|
||||
try:
|
||||
addrs = ["* {}: {}".format(key, val) for key, val in psutil.net_if_addrs().items()]
|
||||
except UnicodeDecodeError:
|
||||
addrs = ["INVALID ADDR WITH UNICODE CHARACTERS"]
|
||||
|
||||
data = """Version: {version}
|
||||
OS: {os}
|
||||
Python: {python}
|
||||
CPU: {cpu}
|
||||
Memory: {memory}
|
||||
|
||||
Networks:
|
||||
{addrs}
|
||||
""".format(
|
||||
version=__version__,
|
||||
os=platform.platform(),
|
||||
python=platform.python_version(),
|
||||
memory=psutil.virtual_memory(),
|
||||
cpu=psutil.cpu_times(),
|
||||
addrs="\n".join(addrs)
|
||||
)
|
||||
|
||||
try:
|
||||
connections = psutil.net_connections()
|
||||
# You need to be root for OSX
|
||||
except psutil.AccessDenied:
|
||||
connections = None
|
||||
|
||||
if connections:
|
||||
data += "\n\nConnections:\n"
|
||||
for port in PortManager.instance().tcp_ports:
|
||||
found = False
|
||||
for open_port in connections:
|
||||
if open_port.laddr[1] == port:
|
||||
found = True
|
||||
data += "TCP {}: {}\n".format(port, found)
|
||||
for port in PortManager.instance().udp_ports:
|
||||
found = False
|
||||
for open_port in connections:
|
||||
if open_port.laddr[1] == port:
|
||||
found = True
|
||||
data += "UDP {}: {}\n".format(port, found)
|
||||
return data
|
||||
|
||||
@@ -1,365 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2018 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from aiohttp.web import HTTPConflict
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.schemas.nio import NIO_SCHEMA
|
||||
from gns3server.schemas.node import NODE_CAPTURE_SCHEMA
|
||||
from gns3server.compute.traceng import TraceNG
|
||||
|
||||
from gns3server.schemas.traceng import (
|
||||
TRACENG_CREATE_SCHEMA,
|
||||
TRACENG_UPDATE_SCHEMA,
|
||||
TRACENG_START_SCHEMA,
|
||||
TRACENG_OBJECT_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
class TraceNGHandler:
|
||||
"""
|
||||
API entry points for TraceNG.
|
||||
"""
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/traceng/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new TraceNG instance",
|
||||
input=TRACENG_CREATE_SCHEMA,
|
||||
output=TRACENG_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
traceng = TraceNG.instance()
|
||||
vm = await traceng.create_node(request.json["name"],
|
||||
request.match_info["project_id"],
|
||||
request.json.get("node_id"),
|
||||
console=request.json.get("console"))
|
||||
vm.ip_address = request.json.get("ip_address", "")
|
||||
vm.default_destination = request.json.get("default_destination", "")
|
||||
response.set_status(201)
|
||||
response.json(vm)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/traceng/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 TraceNG instance",
|
||||
output=TRACENG_OBJECT_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.json(vm)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/traceng/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 TraceNG instance",
|
||||
input=TRACENG_UPDATE_SCHEMA,
|
||||
output=TRACENG_OBJECT_SCHEMA)
|
||||
def update(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
vm.name = request.json.get("name", vm.name)
|
||||
vm.ip_address = request.json.get("ip_address", vm.ip_address)
|
||||
vm.default_destination = request.json.get("default_destination", vm.default_destination)
|
||||
vm.updated()
|
||||
response.json(vm)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/traceng/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 TraceNG instance")
|
||||
async def delete(request, response):
|
||||
|
||||
await TraceNG.instance().delete_node(request.match_info["node_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/traceng/nodes/{node_id}/duplicate",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance duplicated",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Duplicate a TraceNG instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await TraceNG.instance().duplicate_node(
|
||||
request.match_info["node_id"],
|
||||
request.json["destination_node_id"]
|
||||
)
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/traceng/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 TraceNG instance",
|
||||
input=TRACENG_START_SCHEMA,
|
||||
output=TRACENG_OBJECT_SCHEMA)
|
||||
async def start(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.start(request.get("destination"))
|
||||
response.json(vm)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/traceng/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 TraceNG instance")
|
||||
async def stop(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/traceng/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 TraceNG instance (does nothing)")
|
||||
def suspend(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/traceng/nodes/{node_id}/reload",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a TraceNG instance")
|
||||
async def reload(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.reload()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/traceng/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port where the nio should be added"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO created",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Add a NIO to a TraceNG instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def create_nio(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio_type = request.json["type"]
|
||||
if nio_type not in ("nio_udp"):
|
||||
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
nio = traceng_manager.create_nio(request.json)
|
||||
await vm.port_add_nio_binding(int(request.match_info["port_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/traceng/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be updated"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO on a TraceNG instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.port_update_nio_binding(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/traceng/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be removed"
|
||||
},
|
||||
status_codes={
|
||||
204: "NIO deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Remove a NIO from a TraceNG instance")
|
||||
async def delete_nio(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.port_remove_nio_binding(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/traceng/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 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 TraceNG instance",
|
||||
input=NODE_CAPTURE_SCHEMA)
|
||||
async def start_capture(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_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(vm.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
await vm.start_capture(port_number, pcap_file_path)
|
||||
response.json({"pcap_file_path": pcap_file_path})
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/traceng/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 to stop a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
204: "Capture stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
},
|
||||
description="Stop a packet capture on a TraceNG instance")
|
||||
async def stop_capture(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/traceng/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
traceng_manager = TraceNG.instance()
|
||||
vm = traceng_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(port_number)
|
||||
await traceng_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
@@ -1,459 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
|
||||
from aiohttp.web import HTTPConflict
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.schemas.nio import NIO_SCHEMA
|
||||
from gns3server.schemas.node import NODE_CAPTURE_SCHEMA
|
||||
from gns3server.compute.virtualbox import VirtualBox
|
||||
from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
|
||||
from gns3server.schemas.virtualbox import (
|
||||
VBOX_CREATE_SCHEMA,
|
||||
VBOX_OBJECT_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
class VirtualBoxHandler:
|
||||
|
||||
"""
|
||||
API entry points for VirtualBox.
|
||||
"""
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new VirtualBox VM instance",
|
||||
input=VBOX_CREATE_SCHEMA,
|
||||
output=VBOX_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = await vbox_manager.create_node(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("node_id"),
|
||||
request.json.pop("vmname"),
|
||||
linked_clone=request.json.pop("linked_clone", False),
|
||||
console=request.json.get("console", None),
|
||||
console_type=request.json.get("console_type", "telnet"),
|
||||
adapters=request.json.get("adapters", 0))
|
||||
|
||||
if "ram" in request.json:
|
||||
ram = request.json.pop("ram")
|
||||
if ram != vm.ram:
|
||||
await vm.set_ram(ram)
|
||||
|
||||
for name, value in request.json.items():
|
||||
if name != "node_id":
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
response.set_status(201)
|
||||
response.json(vm)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/virtualbox/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 VirtualBox VM instance",
|
||||
output=VBOX_OBJECT_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.json(vm)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/virtualbox/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 VirtualBox VM instance",
|
||||
input=VBOX_OBJECT_SCHEMA,
|
||||
output=VBOX_OBJECT_SCHEMA)
|
||||
async def update(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
if "name" in request.json:
|
||||
name = request.json.pop("name")
|
||||
vmname = request.json.pop("vmname", None)
|
||||
if name != vm.name:
|
||||
oldname = vm.name
|
||||
vm.name = name
|
||||
if vm.linked_clone:
|
||||
try:
|
||||
await vm.set_vmname(vm.name)
|
||||
except VirtualBoxError as e: # In case of error we rollback (we can't change the name when running)
|
||||
vm.name = oldname
|
||||
vm.updated()
|
||||
raise e
|
||||
|
||||
if "adapters" in request.json:
|
||||
adapters = int(request.json.pop("adapters"))
|
||||
if adapters != vm.adapters:
|
||||
await vm.set_adapters(adapters)
|
||||
|
||||
if "ram" in request.json:
|
||||
ram = request.json.pop("ram")
|
||||
if ram != vm.ram:
|
||||
await vm.set_ram(ram)
|
||||
|
||||
# update the console first to avoid issue if updating console type
|
||||
vm.console = request.json.pop("console", vm.console)
|
||||
|
||||
for name, value in request.json.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
vm.updated()
|
||||
response.json(vm)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/virtualbox/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 VirtualBox VM instance")
|
||||
async def delete(request, response):
|
||||
|
||||
# check the project_id exists
|
||||
ProjectManager.instance().get_project(request.match_info["project_id"])
|
||||
await VirtualBox.instance().delete_node(request.match_info["node_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/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 VirtualBox VM instance")
|
||||
async def start(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
if (await vm.check_hw_virtualization()):
|
||||
pm = ProjectManager.instance()
|
||||
if pm.check_hardware_virtualization(vm) is False:
|
||||
raise HTTPConflict(text="Cannot start VM because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or KVM (on Linux)")
|
||||
await vm.start()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/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 VirtualBox VM instance")
|
||||
async def stop(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/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 VirtualBox VM instance")
|
||||
async def suspend(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.suspend()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/nodes/{node_id}/resume",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance resumed",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Resume a suspended VirtualBox VM instance")
|
||||
async def resume(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.resume()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/nodes/{node_id}/reload",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a VirtualBox VM instance")
|
||||
async def reload(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.reload()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter where the nio should be added",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO created",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Add a NIO to a VirtualBox VM instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def create_nio(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio_type = request.json["type"]
|
||||
if nio_type not in ("nio_udp", "nio_nat"):
|
||||
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
nio = vbox_manager.create_nio(request.json)
|
||||
await vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be updated"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO on a Virtualbox instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
virtualbox_manager = VirtualBox.instance()
|
||||
vm = virtualbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
if "suspend" in request.json:
|
||||
nio.suspend = request.json["suspend"]
|
||||
await vm.adapter_update_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter from where the nio should be removed",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
204: "NIO deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Remove a NIO from a VirtualBox VM instance")
|
||||
async def delete_nio(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.adapter_remove_nio_binding(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/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 to start a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a packet capture on a VirtualBox VM instance",
|
||||
input=NODE_CAPTURE_SCHEMA)
|
||||
async def start_capture(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
await vm.start_capture(adapter_number, pcap_file_path)
|
||||
response.json({"pcap_file_path": pcap_file_path})
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/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 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 VirtualBox VM instance")
|
||||
async def stop_capture(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
virtualbox_manager = VirtualBox.instance()
|
||||
vm = virtualbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
await virtualbox_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/virtualbox/vms",
|
||||
status_codes={
|
||||
200: "Success",
|
||||
},
|
||||
description="Get all available VirtualBox VMs")
|
||||
async def get_vms(request, response):
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vms = await vbox_manager.list_vms()
|
||||
response.json(vms)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/virtualbox/nodes/{node_id}/console/ws",
|
||||
description="WebSocket for console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
})
|
||||
async def console_ws(request, response):
|
||||
|
||||
virtualbox_manager = VirtualBox.instance()
|
||||
vm = virtualbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
return await vm.start_websocket_console(request)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/nodes/{node_id}/console/reset",
|
||||
description="Reset console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Console has been reset",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "Container not started"
|
||||
})
|
||||
async def reset_console(request, response):
|
||||
|
||||
virtualbox_manager = VirtualBox.instance()
|
||||
vm = virtualbox_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.reset_console()
|
||||
response.set_status(204)
|
||||
@@ -1,444 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
|
||||
from aiohttp.web import HTTPConflict
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.schemas.node import NODE_CAPTURE_SCHEMA
|
||||
from gns3server.schemas.nio import NIO_SCHEMA
|
||||
from gns3server.compute.vmware import VMware
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
|
||||
from gns3server.schemas.vmware import (
|
||||
VMWARE_CREATE_SCHEMA,
|
||||
VMWARE_OBJECT_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
class VMwareHandler:
|
||||
|
||||
"""
|
||||
API entry points for VMware.
|
||||
"""
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new VMware VM instance",
|
||||
input=VMWARE_CREATE_SCHEMA,
|
||||
output=VMWARE_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = await vmware_manager.create_node(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("node_id"),
|
||||
request.json.pop("vmx_path"),
|
||||
linked_clone=request.json.pop("linked_clone"),
|
||||
console=request.json.get("console", None),
|
||||
console_type=request.json.get("console_type", "telnet"))
|
||||
|
||||
for name, value in request.json.items():
|
||||
if name != "node_id":
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
response.set_status(201)
|
||||
response.json(vm)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/vmware/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 VMware VM instance",
|
||||
output=VMWARE_OBJECT_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.json(vm)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/vmware/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 VMware VM instance",
|
||||
input=VMWARE_OBJECT_SCHEMA,
|
||||
output=VMWARE_OBJECT_SCHEMA)
|
||||
def update(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
# update the console first to avoid issue if updating console type
|
||||
vm.console = request.json.pop("console", vm.console)
|
||||
for name, value in request.json.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
vm.updated()
|
||||
response.json(vm)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/vmware/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 VMware VM instance")
|
||||
async def delete(request, response):
|
||||
|
||||
# check the project_id exists
|
||||
ProjectManager.instance().get_project(request.match_info["project_id"])
|
||||
await VMware.instance().delete_node(request.match_info["node_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/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 VMware VM instance")
|
||||
async def start(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
if vm.check_hw_virtualization():
|
||||
pm = ProjectManager.instance()
|
||||
if pm.check_hardware_virtualization(vm) is False:
|
||||
raise HTTPConflict(text="Cannot start VM because hardware virtualization (VT-x/AMD-V) is already used by another software like VirtualBox or KVM (on Linux)")
|
||||
await vm.start()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/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 VMware VM instance")
|
||||
async def stop(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/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 VMware VM instance")
|
||||
async def suspend(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.suspend()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/resume",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance resumed",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Resume a suspended VMware VM instance")
|
||||
async def resume(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.resume()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/reload",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a VMware VM instance")
|
||||
async def reload(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.reload()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter where the nio should be added",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO created",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Add a NIO to a VMware VM instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def create_nio(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio_type = request.json["type"]
|
||||
if nio_type not in ("nio_udp", "nio_vmnet", "nio_nat", "nio_tap"):
|
||||
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
nio = vmware_manager.create_nio(request.json)
|
||||
await vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be updated"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO on a VMware VM instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.adapter_update_nio_binding(adapter_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter from where the nio should be removed",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
204: "NIO deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Remove a NIO from a VMware VM instance")
|
||||
async def delete_nio(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.adapter_remove_nio_binding(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/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 to start a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
},
|
||||
description="Start a packet capture on a VMware VM instance",
|
||||
input=NODE_CAPTURE_SCHEMA)
|
||||
async def start_capture(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
await vm.start_capture(adapter_number, pcap_file_path)
|
||||
response.json({"pcap_file_path": pcap_file_path})
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/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 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 VMware VM instance")
|
||||
async def stop_capture(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
await vm.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
nio = vm.get_nio(adapter_number)
|
||||
await vmware_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/interfaces/vmnet",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
201: "VMnet interface allocated",
|
||||
},
|
||||
description="Allocate a VMware VMnet interface on the server")
|
||||
def allocate_vmnet(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
vmware_manager.refresh_vmnet_list(ubridge=False)
|
||||
vmnet = vmware_manager.allocate_vmnet()
|
||||
vm.vmnets.append(vmnet)
|
||||
response.set_status(201)
|
||||
response.json({"vmnet": vmnet})
|
||||
|
||||
@Route.get(
|
||||
r"/vmware/vms",
|
||||
status_codes={
|
||||
200: "Success",
|
||||
},
|
||||
description="Get all VMware VMs available")
|
||||
async def get_vms(request, response):
|
||||
vmware_manager = VMware.instance()
|
||||
vms = await vmware_manager.list_vms()
|
||||
response.json(vms)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/console/ws",
|
||||
description="WebSocket for console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
})
|
||||
async def console_ws(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
return await vm.start_websocket_console(request)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/nodes/{node_id}/console/reset",
|
||||
description="Reset console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Console has been reset",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "Container not started"
|
||||
})
|
||||
async def reset_console(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.reset_console()
|
||||
response.set_status(204)
|
||||
@@ -1,397 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
from aiohttp.web import HTTPConflict
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.schemas.nio import NIO_SCHEMA
|
||||
from gns3server.schemas.node import NODE_CAPTURE_SCHEMA
|
||||
from gns3server.compute.vpcs import VPCS
|
||||
|
||||
from gns3server.schemas.vpcs import (
|
||||
VPCS_CREATE_SCHEMA,
|
||||
VPCS_UPDATE_SCHEMA,
|
||||
VPCS_OBJECT_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
class VPCSHandler:
|
||||
"""
|
||||
API entry points for VPCS.
|
||||
"""
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new VPCS instance",
|
||||
input=VPCS_CREATE_SCHEMA,
|
||||
output=VPCS_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
vpcs = VPCS.instance()
|
||||
vm = await vpcs.create_node(request.json["name"],
|
||||
request.match_info["project_id"],
|
||||
request.json.get("node_id"),
|
||||
console=request.json.get("console"),
|
||||
console_type=request.json.get("console_type", "telnet"),
|
||||
startup_script=request.json.get("startup_script"))
|
||||
response.set_status(201)
|
||||
response.json(vm)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/vpcs/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 VPCS instance",
|
||||
output=VPCS_OBJECT_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.json(vm)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/vpcs/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 VPCS instance",
|
||||
input=VPCS_UPDATE_SCHEMA,
|
||||
output=VPCS_OBJECT_SCHEMA)
|
||||
def update(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
vm.name = request.json.get("name", vm.name)
|
||||
vm.console = request.json.get("console", vm.console)
|
||||
vm.console_type = request.json.get("console_type", vm.console_type)
|
||||
vm.updated()
|
||||
response.json(vm)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/vpcs/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 VPCS instance")
|
||||
async def delete(request, response):
|
||||
|
||||
await VPCS.instance().delete_node(request.match_info["node_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/nodes/{node_id}/duplicate",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance duplicated",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Duplicate a VPCS instance")
|
||||
async def duplicate(request, response):
|
||||
|
||||
new_node = await VPCS.instance().duplicate_node(
|
||||
request.match_info["node_id"],
|
||||
request.json["destination_node_id"]
|
||||
)
|
||||
response.set_status(201)
|
||||
response.json(new_node)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/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 VPCS instance",
|
||||
output=VPCS_OBJECT_SCHEMA)
|
||||
async def start(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.start()
|
||||
response.json(vm)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/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 VPCS instance")
|
||||
async def stop(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/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 VPCS instance (does nothing)")
|
||||
def suspend(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/nodes/{node_id}/reload",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a VPCS instance")
|
||||
async def reload(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.reload()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port where the nio should be added"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO created",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Add a NIO to a VPCS instance",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
async def create_nio(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
nio_type = request.json["type"]
|
||||
if nio_type not in ("nio_udp", "nio_tap"):
|
||||
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vpcs_manager.create_nio(request.json)
|
||||
await vm.port_add_nio_binding(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/vpcs/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be updated"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA,
|
||||
description="Update a NIO on a VPCS instance")
|
||||
async def update_nio(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(port_number)
|
||||
if "filters" in request.json:
|
||||
nio.filters = request.json["filters"]
|
||||
await vm.port_update_nio_binding(port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(request.json)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/vpcs/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Network adapter where the nio is located",
|
||||
"port_number": "Port from where the nio should be removed"
|
||||
},
|
||||
status_codes={
|
||||
204: "NIO deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Remove a NIO from a VPCS instance")
|
||||
async def delete_nio(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.port_remove_nio_binding(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/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 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 VPCS instance",
|
||||
input=NODE_CAPTURE_SCHEMA)
|
||||
async def start_capture(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_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(vm.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
await vm.start_capture(port_number, pcap_file_path)
|
||||
response.json({"pcap_file_path": pcap_file_path})
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/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 to stop a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
204: "Capture stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
},
|
||||
description="Stop a packet capture on a VPCS instance")
|
||||
async def stop_capture(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
await vm.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/vpcs/nodes/{node_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/pcap",
|
||||
description="Stream the pcap capture file",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
"adapter_number": "Adapter to steam a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def stream_pcap_file(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = vm.get_nio(port_number)
|
||||
await vpcs_manager.stream_pcap_file(nio, vm.project.id, request, response)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/vpcs/nodes/{node_id}/console/ws",
|
||||
description="WebSocket for console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
})
|
||||
async def console_ws(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
return await vm.start_websocket_console(request)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/nodes/{node_id}/console/reset",
|
||||
description="Reset console",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Console has been reset",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "Container not started"
|
||||
})
|
||||
async def reset_console(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_node(request.match_info["node_id"], project_id=request.match_info["project_id"])
|
||||
await vm.reset_console()
|
||||
response.set_status(204)
|
||||
@@ -1,29 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .compute_handler import ComputeHandler
|
||||
from .project_handler import ProjectHandler
|
||||
from .node_handler import NodeHandler
|
||||
from .link_handler import LinkHandler
|
||||
from .server_handler import ServerHandler
|
||||
from .drawing_handler import DrawingHandler
|
||||
from .symbol_handler import SymbolHandler
|
||||
from .snapshot_handler import SnapshotHandler
|
||||
from .appliance_handler import ApplianceHandler
|
||||
from .template_handler import TemplateHandler
|
||||
from .gns3_vm_handler import GNS3VMHandler
|
||||
from .notification_handler import NotificationHandler
|
||||
@@ -1,43 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ApplianceHandler:
|
||||
"""
|
||||
API entry points for appliance management.
|
||||
"""
|
||||
|
||||
@Route.get(
|
||||
r"/appliances",
|
||||
description="List of appliances",
|
||||
status_codes={
|
||||
200: "Appliance list returned"
|
||||
})
|
||||
async def list_appliances(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
if request.query.get("update", "no").lower() == "yes":
|
||||
await controller.appliance_manager.download_appliances()
|
||||
symbol_theme = request.query.get("symbol_theme", "Classic")
|
||||
controller.appliance_manager.load_appliances(symbol_theme=symbol_theme)
|
||||
response.json([c for c in controller.appliance_manager.appliances.values()])
|
||||
@@ -1,234 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
|
||||
from gns3server.schemas.compute import (
|
||||
COMPUTE_CREATE_SCHEMA,
|
||||
COMPUTE_OBJECT_SCHEMA,
|
||||
COMPUTE_UPDATE_SCHEMA,
|
||||
COMPUTE_ENDPOINT_OUTPUT_OBJECT_SCHEMA,
|
||||
COMPUTE_PORTS_OBJECT_SCHEMA
|
||||
)
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ComputeHandler:
|
||||
"""API entry points for compute management."""
|
||||
|
||||
@Route.post(
|
||||
r"/computes",
|
||||
description="Register a compute",
|
||||
status_codes={
|
||||
201: "Compute added"
|
||||
},
|
||||
input=COMPUTE_CREATE_SCHEMA,
|
||||
output=COMPUTE_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
compute = await Controller.instance().add_compute(**request.json)
|
||||
response.set_status(201)
|
||||
response.json(compute)
|
||||
|
||||
@Route.get(
|
||||
r"/computes",
|
||||
description="List of computes",
|
||||
status_codes={
|
||||
200: "Computes list returned"
|
||||
})
|
||||
def list(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
response.json([c for c in controller.computes.values()])
|
||||
|
||||
@Route.put(
|
||||
r"/computes/{compute_id}",
|
||||
description="Update a compute",
|
||||
status_codes={
|
||||
200: "Compute updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
input=COMPUTE_UPDATE_SCHEMA,
|
||||
output=COMPUTE_OBJECT_SCHEMA)
|
||||
async def update(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
compute = controller.get_compute(request.match_info["compute_id"])
|
||||
|
||||
# Ignore these because we only use them when creating a node
|
||||
request.json.pop("compute_id", None)
|
||||
await compute.update(**request.json)
|
||||
response.set_status(200)
|
||||
response.json(compute)
|
||||
|
||||
@Route.get(
|
||||
r"/computes/{compute_id}/{emulator}/images",
|
||||
parameters={
|
||||
"compute_id": "Compute UUID",
|
||||
"emulator": "Emulator type"
|
||||
},
|
||||
status_codes={
|
||||
200: "OK",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Return the list of images available on compute for this emulator type")
|
||||
async def images(request, response):
|
||||
controller = Controller.instance()
|
||||
compute = controller.get_compute(request.match_info["compute_id"])
|
||||
res = await compute.images(request.match_info["emulator"])
|
||||
response.json(res)
|
||||
|
||||
@Route.get(
|
||||
r"/computes/endpoint/{compute_id}/{emulator}/{action:.+}",
|
||||
parameters={
|
||||
"compute_id": "Compute UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "OK",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
raw=True,
|
||||
output=COMPUTE_ENDPOINT_OUTPUT_OBJECT_SCHEMA,
|
||||
description="Returns the endpoint for particular `compute` to specific action. "
|
||||
"WARNING: This is experimental feature and may change anytime. Please don't rely on this endpoint.")
|
||||
def endpoint(request, response):
|
||||
controller = Controller.instance()
|
||||
compute = controller.get_compute(request.match_info["compute_id"])
|
||||
|
||||
path = '/{emulator}/{action}'.format(
|
||||
emulator=request.match_info['emulator'],
|
||||
action=request.match_info['action'])
|
||||
|
||||
endpoint = compute.get_url(path)
|
||||
|
||||
response.set_status(200)
|
||||
response.json(dict(
|
||||
endpoint=endpoint
|
||||
))
|
||||
|
||||
@Route.get(
|
||||
r"/computes/{compute_id}/{emulator}/{action:.+}",
|
||||
parameters={
|
||||
"compute_id": "Compute UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "OK",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Forward call specific to compute node. Read the full compute API for available actions")
|
||||
async def get_forward(request, response):
|
||||
controller = Controller.instance()
|
||||
compute = controller.get_compute(request.match_info["compute_id"])
|
||||
res = await compute.forward("GET", request.match_info["emulator"], request.match_info["action"])
|
||||
response.json(res)
|
||||
|
||||
@Route.post(
|
||||
r"/computes/{compute_id}/{emulator}/{action:.+}",
|
||||
parameters={
|
||||
"compute_id": "Compute UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "OK",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
raw=True,
|
||||
description="Forward call specific to compute node. Read the full compute API for available actions")
|
||||
async def post_forward(request, response):
|
||||
controller = Controller.instance()
|
||||
compute = controller.get_compute(request.match_info["compute_id"])
|
||||
res = await compute.forward("POST", request.match_info["emulator"], request.match_info["action"], data=request.content)
|
||||
response.json(res)
|
||||
|
||||
@Route.put(
|
||||
r"/computes/{compute_id}/{emulator}/{action:.+}",
|
||||
parameters={
|
||||
"compute_id": "Compute UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "OK",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
raw=True,
|
||||
description="Forward call specific to compute node. Read the full compute API for available actions")
|
||||
async def put_forward(request, response):
|
||||
controller = Controller.instance()
|
||||
compute = controller.get_compute(request.match_info["compute_id"])
|
||||
res = await compute.forward("PUT", request.match_info["emulator"], request.match_info["action"], data=request.content)
|
||||
response.json(res)
|
||||
|
||||
@Route.get(
|
||||
r"/computes/{compute_id}",
|
||||
description="Get a compute information",
|
||||
status_codes={
|
||||
200: "Compute information returned"
|
||||
},
|
||||
output=COMPUTE_OBJECT_SCHEMA)
|
||||
def get(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
compute = controller.get_compute(request.match_info["compute_id"])
|
||||
response.json(compute)
|
||||
|
||||
@Route.delete(
|
||||
r"/computes/{compute_id}",
|
||||
parameters={
|
||||
"compute_id": "Compute UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Delete a compute instance")
|
||||
async def delete(request, response):
|
||||
controller = Controller.instance()
|
||||
await controller.delete_compute(request.match_info["compute_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/computes/{compute_id}/auto_idlepc",
|
||||
parameters={
|
||||
"compute_id": "Compute UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "Idle PC computed",
|
||||
},
|
||||
description="Compute IDLE PC value")
|
||||
async def autoidlepc(request, response):
|
||||
controller = Controller.instance()
|
||||
res = await controller.autoidlepc(request.match_info["compute_id"], request.json["platform"], request.json["image"], request.json["ram"])
|
||||
response.json(res)
|
||||
|
||||
@Route.get(
|
||||
r"/computes/{compute_id}/ports",
|
||||
parameters={
|
||||
"compute_id": "Compute UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "Ports information returned",
|
||||
},
|
||||
description="Get ports used by a compute",
|
||||
output=COMPUTE_PORTS_OBJECT_SCHEMA)
|
||||
async def ports(request, response):
|
||||
controller = Controller.instance()
|
||||
res = await controller.compute_ports(request.match_info["compute_id"])
|
||||
response.json(res)
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
|
||||
from gns3server.schemas.drawing import (
|
||||
DRAWING_OBJECT_SCHEMA,
|
||||
)
|
||||
|
||||
|
||||
class DrawingHandler:
|
||||
"""
|
||||
API entry point for Drawing
|
||||
"""
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/drawings",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "List of drawings returned",
|
||||
},
|
||||
description="List drawings of a project")
|
||||
async def list_drawings(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
response.json([v for v in project.drawings.values()])
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/drawings",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Drawing created",
|
||||
400: "Invalid request"
|
||||
},
|
||||
description="Create a new drawing instance",
|
||||
input=DRAWING_OBJECT_SCHEMA,
|
||||
output=DRAWING_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
drawing = await project.add_drawing(**request.json)
|
||||
response.set_status(201)
|
||||
response.json(drawing)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/drawings/{drawing_id}",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"drawing_id": "Drawing UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "Drawing found",
|
||||
400: "Invalid request",
|
||||
404: "Drawing doesn't exist"
|
||||
},
|
||||
description="Get a drawing instance",
|
||||
output=DRAWING_OBJECT_SCHEMA)
|
||||
async def get_drawing(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
drawing = project.get_drawing(request.match_info["drawing_id"])
|
||||
response.set_status(200)
|
||||
response.json(drawing)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/drawings/{drawing_id}",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"drawing_id": "Drawing UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Drawing updated",
|
||||
400: "Invalid request"
|
||||
},
|
||||
description="Update a drawing instance",
|
||||
input=DRAWING_OBJECT_SCHEMA,
|
||||
output=DRAWING_OBJECT_SCHEMA)
|
||||
async def update(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
drawing = project.get_drawing(request.match_info["drawing_id"])
|
||||
await drawing.update(**request.json)
|
||||
response.set_status(201)
|
||||
response.json(drawing)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/drawings/{drawing_id}",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"drawing_id": "Drawing UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Drawing deleted",
|
||||
400: "Invalid request"
|
||||
},
|
||||
description="Delete a drawing instance")
|
||||
async def delete(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
await project.delete_drawing(request.match_info["drawing_id"])
|
||||
response.set_status(204)
|
||||
@@ -1,80 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.schemas.gns3vm import GNS3VM_SETTINGS_SCHEMA
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GNS3VMHandler:
|
||||
"""API entry points for GNS3 VM management."""
|
||||
|
||||
@Route.get(
|
||||
r"/gns3vm/engines",
|
||||
description="Return the list of engines supported for the GNS3VM",
|
||||
status_codes={
|
||||
200: "OK"
|
||||
})
|
||||
def list_engines(request, response):
|
||||
|
||||
gns3_vm = Controller().instance().gns3vm
|
||||
response.json(gns3_vm.engine_list())
|
||||
|
||||
@Route.get(
|
||||
r"/gns3vm/engines/{engine}/vms",
|
||||
parameters={
|
||||
"engine": "Virtualization engine name"
|
||||
},
|
||||
status_codes={
|
||||
200: "Success",
|
||||
400: "Invalid request",
|
||||
},
|
||||
description="Get all the available VMs for a specific virtualization engine")
|
||||
async def get_vms(request, response):
|
||||
|
||||
vms = await Controller.instance().gns3vm.list(request.match_info["engine"])
|
||||
response.json(vms)
|
||||
|
||||
@Route.get(
|
||||
r"/gns3vm",
|
||||
description="Get GNS3 VM settings",
|
||||
status_codes={
|
||||
200: "GNS3 VM settings returned"
|
||||
},
|
||||
output_schema=GNS3VM_SETTINGS_SCHEMA)
|
||||
def show(request, response):
|
||||
response.json(Controller.instance().gns3vm)
|
||||
|
||||
@Route.put(
|
||||
r"/gns3vm",
|
||||
description="Update GNS3 VM settings",
|
||||
input_schema=GNS3VM_SETTINGS_SCHEMA,
|
||||
output_schema=GNS3VM_SETTINGS_SCHEMA,
|
||||
status_codes={
|
||||
201: "GNS3 VM updated"
|
||||
})
|
||||
async def update(request, response):
|
||||
|
||||
controller = Controller().instance()
|
||||
gns3_vm = controller.gns3vm
|
||||
await gns3_vm.update_settings(request.json)
|
||||
controller.save()
|
||||
response.json(gns3_vm)
|
||||
response.set_status(201)
|
||||
@@ -1,260 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import aiohttp
|
||||
import multidict
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
|
||||
from gns3server.schemas.link import (
|
||||
LINK_OBJECT_SCHEMA,
|
||||
LINK_CAPTURE_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
class LinkHandler:
|
||||
"""
|
||||
API entry point for Link
|
||||
"""
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/links",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "List of links returned",
|
||||
},
|
||||
description="List links of a project")
|
||||
async def list_links(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
response.json([v for v in project.links.values()])
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/links",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Link created",
|
||||
400: "Invalid request"
|
||||
},
|
||||
description="Create a new link instance",
|
||||
input=LINK_OBJECT_SCHEMA,
|
||||
output=LINK_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
link = await project.add_link()
|
||||
if "filters" in request.json:
|
||||
await link.update_filters(request.json["filters"])
|
||||
if "suspend" in request.json:
|
||||
await link.update_suspend(request.json["suspend"])
|
||||
try:
|
||||
for node in request.json["nodes"]:
|
||||
await link.add_node(project.get_node(node["node_id"]),
|
||||
node.get("adapter_number", 0),
|
||||
node.get("port_number", 0),
|
||||
label=node.get("label"))
|
||||
except aiohttp.web.HTTPException as e:
|
||||
await project.delete_link(link.id)
|
||||
raise e
|
||||
response.set_status(201)
|
||||
response.json(link)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/links/{link_id}/available_filters",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"link_id": "Link UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "List of filters",
|
||||
400: "Invalid request"
|
||||
},
|
||||
description="Return the list of filters available for this link")
|
||||
async def list_filters(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
link = project.get_link(request.match_info["link_id"])
|
||||
response.set_status(200)
|
||||
response.json(link.available_filters())
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/links/{link_id}",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"link_id": "Link UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "Link found",
|
||||
400: "Invalid request",
|
||||
404: "Link doesn't exist"
|
||||
},
|
||||
description="Get a link instance",
|
||||
output=LINK_OBJECT_SCHEMA)
|
||||
async def get_link(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
link = project.get_link(request.match_info["link_id"])
|
||||
response.set_status(200)
|
||||
response.json(link)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/links/{link_id}",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"link_id": "Link UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Link updated",
|
||||
400: "Invalid request"
|
||||
},
|
||||
description="Update a link instance",
|
||||
input=LINK_OBJECT_SCHEMA,
|
||||
output=LINK_OBJECT_SCHEMA)
|
||||
async def update(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
link = project.get_link(request.match_info["link_id"])
|
||||
if "filters" in request.json:
|
||||
await link.update_filters(request.json["filters"])
|
||||
if "suspend" in request.json:
|
||||
await link.update_suspend(request.json["suspend"])
|
||||
if "nodes" in request.json:
|
||||
await link.update_nodes(request.json["nodes"])
|
||||
response.set_status(201)
|
||||
response.json(link)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/links/{link_id}/start_capture",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"link_id": "Link UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Capture started",
|
||||
400: "Invalid request"
|
||||
},
|
||||
input=LINK_CAPTURE_SCHEMA,
|
||||
output=LINK_OBJECT_SCHEMA,
|
||||
description="Start capture on a link instance. By default we consider it as an Ethernet link")
|
||||
async def start_capture(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
link = project.get_link(request.match_info["link_id"])
|
||||
await link.start_capture(data_link_type=request.json.get("data_link_type", "DLT_EN10MB"),
|
||||
capture_file_name=request.json.get("capture_file_name"))
|
||||
response.set_status(201)
|
||||
response.json(link)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/links/{link_id}/stop_capture",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"link_id": "Link UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Capture stopped",
|
||||
400: "Invalid request"
|
||||
},
|
||||
description="Stop capture on a link instance")
|
||||
async def stop_capture(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
link = project.get_link(request.match_info["link_id"])
|
||||
await link.stop_capture()
|
||||
response.set_status(201)
|
||||
response.json(link)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/links/{link_id}",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"link_id": "Link UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Link deleted",
|
||||
400: "Invalid request"
|
||||
},
|
||||
description="Delete a link instance")
|
||||
async def delete(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
await project.delete_link(request.match_info["link_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/links/{link_id}/reset",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"link_id": "Link UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Link reset",
|
||||
400: "Invalid request"
|
||||
},
|
||||
description="Reset link instance",
|
||||
output=LINK_OBJECT_SCHEMA)
|
||||
async def reset(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
link = project.get_link(request.match_info["link_id"])
|
||||
await link.reset()
|
||||
response.set_status(201)
|
||||
response.json(link)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/links/{link_id}/pcap",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"link_id": "Link UUID"
|
||||
},
|
||||
description="Stream the PCAP capture file from compute",
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def pcap(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
link = project.get_link(request.match_info["link_id"])
|
||||
if not link.capturing:
|
||||
raise aiohttp.web.HTTPConflict(text="This link has no active packet capture")
|
||||
|
||||
compute = link.compute
|
||||
pcap_streaming_url = link.pcap_streaming_url()
|
||||
headers = multidict.MultiDict(request.headers)
|
||||
headers['Host'] = compute.host
|
||||
headers['Router-Host'] = request.host
|
||||
body = await request.read()
|
||||
|
||||
connector = aiohttp.TCPConnector(limit=None, force_close=True)
|
||||
async with aiohttp.ClientSession(connector=connector, headers=headers) as session:
|
||||
async with session.request(request.method, pcap_streaming_url, timeout=None, data=body) as response:
|
||||
proxied_response = aiohttp.web.Response(headers=response.headers, status=response.status)
|
||||
if response.headers.get('Transfer-Encoding', '').lower() == 'chunked':
|
||||
proxied_response.enable_chunked_encoding()
|
||||
|
||||
await proxied_response.prepare(request)
|
||||
async for data in response.content.iter_any():
|
||||
if not data:
|
||||
break
|
||||
await proxied_response.write(data)
|
||||
@@ -1,547 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import aiohttp
|
||||
import asyncio
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.utils import force_unix_path
|
||||
|
||||
from gns3server.schemas.node import (
|
||||
NODE_OBJECT_SCHEMA,
|
||||
NODE_UPDATE_SCHEMA,
|
||||
NODE_CREATE_SCHEMA,
|
||||
NODE_DUPLICATE_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
class NodeHandler:
|
||||
"""
|
||||
API entry point for node
|
||||
"""
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request"
|
||||
},
|
||||
description="Create a new node instance",
|
||||
input=NODE_CREATE_SCHEMA,
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
async def create(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
compute = controller.get_compute(request.json.pop("compute_id"))
|
||||
project = await controller.get_loaded_project(request.match_info["project_id"])
|
||||
node = await project.add_node(compute, request.json.pop("name"), request.json.pop("node_id", None), **request.json)
|
||||
response.set_status(201)
|
||||
response.json(node)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/nodes",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "List of nodes returned",
|
||||
},
|
||||
description="List nodes of a project")
|
||||
async def list_nodes(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
response.json([v for v in project.nodes.values()])
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes/start",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "All nodes successfully started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start all nodes belonging to the project",
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
async def start_all(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
await project.start_all()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes/stop",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "All nodes successfully stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop all nodes belonging to the project",
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
async def stop_all(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
await project.stop_all()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes/suspend",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "All nodes successfully suspended",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Suspend all nodes belonging to the project",
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
async def suspend_all(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
await project.suspend_all()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes/reload",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "All nodes successfully reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload all nodes belonging to the project",
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
async def reload_all(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
await project.stop_all()
|
||||
await project.start_all()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/nodes/{node_id}",
|
||||
status_codes={
|
||||
200: "Node found",
|
||||
400: "Invalid request",
|
||||
404: "Node doesn't exist"
|
||||
},
|
||||
description="Get a node",
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
def get_node(request, response):
|
||||
project = Controller.instance().get_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
response.set_status(200)
|
||||
response.json(node)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/nodes/{node_id}",
|
||||
status_codes={
|
||||
200: "Instance updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Update a node instance",
|
||||
input=NODE_UPDATE_SCHEMA,
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
async def update(request, response):
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
|
||||
# Ignore these because we only use them when creating a node
|
||||
request.json.pop("node_id", None)
|
||||
request.json.pop("node_type", None)
|
||||
request.json.pop("compute_id", None)
|
||||
|
||||
await node.update(**request.json)
|
||||
response.set_status(200)
|
||||
response.json(node)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/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",
|
||||
409: "Cannot delete locked node"
|
||||
},
|
||||
description="Delete a node instance")
|
||||
async def delete(request, response):
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
await project.delete_node(request.match_info["node_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes/{node_id}/duplicate",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance duplicated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Duplicate a node instance",
|
||||
input=NODE_DUPLICATE_SCHEMA,
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
async def duplicate(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
new_node = await project.duplicate_node(
|
||||
node,
|
||||
request.json["x"],
|
||||
request.json["y"],
|
||||
request.json.get("z", 0))
|
||||
response.json(new_node)
|
||||
response.set_status(201)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes/{node_id}/start",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "Instance started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a node instance",
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
async def start(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
await node.start(data=request.json)
|
||||
response.json(node)
|
||||
response.set_status(200)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes/{node_id}/stop",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "Instance stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a node instance",
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
async def stop(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
await node.stop()
|
||||
response.json(node)
|
||||
response.set_status(200)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes/{node_id}/suspend",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "Instance suspended",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Suspend a node instance",
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
async def suspend(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
await node.suspend()
|
||||
response.json(node)
|
||||
response.set_status(200)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes/{node_id}/reload",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a node instance",
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
async def reload(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
await node.reload()
|
||||
response.json(node)
|
||||
response.set_status(200)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/nodes/{node_id}/links",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
200: "Links returned",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Return all the links connected to this node")
|
||||
async def links(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
links = []
|
||||
for link in node.links:
|
||||
links.append(link.__json__())
|
||||
response.json(links)
|
||||
response.set_status(200)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/nodes/{node_id}/dynamips/auto_idlepc",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Compute the IDLE PC for a Dynamips node")
|
||||
async def auto_idlepc(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
idle = await node.dynamips_auto_idlepc()
|
||||
response.json(idle)
|
||||
response.set_status(200)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/nodes/{node_id}/dynamips/idlepc_proposals",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Compute a list of potential idle PC for a node")
|
||||
async def idlepc_proposals(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
idle = await node.dynamips_idlepc_proposals()
|
||||
response.json(idle)
|
||||
response.set_status(200)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes/{node_id}/resize_disk",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Disk image resized",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a node instance")
|
||||
async def resize_disk(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
await node.post("/resize_disk", request.json)
|
||||
response.set_status(201)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/nodes/{node_id}/files/{path:.+}",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Get a file in the node directory")
|
||||
async def get_file(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
path = request.match_info["path"]
|
||||
path = force_unix_path(path)
|
||||
|
||||
|
||||
# Raise error if user try to escape
|
||||
if path[0] == ".":
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
|
||||
node_type = node.node_type
|
||||
path = "/project-files/{}/{}/{}".format(node_type, node.id, path)
|
||||
|
||||
res = await node.compute.http_query("GET", "/projects/{project_id}/files{path}".format(project_id=project.id, path=path), timeout=None, raw=True)
|
||||
response.set_status(200)
|
||||
response.content_type = "application/octet-stream"
|
||||
response.enable_chunked_encoding()
|
||||
await response.prepare(request)
|
||||
await response.write(res.body)
|
||||
# await response.write_eof() #FIXME: shound't be needed anymore
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes/{node_id}/files/{path:.+}",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
raw=True,
|
||||
description="Write a file in the node directory")
|
||||
async def post_file(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
path = request.match_info["path"]
|
||||
path = force_unix_path(path)
|
||||
|
||||
# Raise error if user try to escape
|
||||
if path[0] == ".":
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
|
||||
node_type = node.node_type
|
||||
path = "/project-files/{}/{}/{}".format(node_type, node.id, path)
|
||||
data = await request.content.read() #FIXME: are we handling timeout or large files correctly?
|
||||
await node.compute.http_query("POST", "/projects/{project_id}/files{path}".format(project_id=project.id, path=path), data=data, timeout=None, raw=True)
|
||||
response.set_status(201)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/nodes/{node_id}/console/ws",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
description="Connect to WebSocket console",
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def ws_console(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
compute = node.compute
|
||||
ws = aiohttp.web.WebSocketResponse()
|
||||
await ws.prepare(request)
|
||||
request.app['websockets'].add(ws)
|
||||
|
||||
ws_console_compute_url = "ws://{compute_host}:{compute_port}/v2/compute/projects/{project_id}/{node_type}/nodes/{node_id}/console/ws".format(compute_host=compute.host,
|
||||
compute_port=compute.port,
|
||||
project_id=project.id,
|
||||
node_type=node.node_type,
|
||||
node_id=node.id)
|
||||
|
||||
async def ws_forward(ws_client):
|
||||
async for msg in ws:
|
||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||
await ws_client.send_str(msg.data)
|
||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||
await ws_client.send_bytes(msg.data)
|
||||
elif msg.type == aiohttp.WSMsgType.ERROR:
|
||||
break
|
||||
|
||||
try:
|
||||
async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(limit=None, force_close=True)) as session:
|
||||
async with session.ws_connect(ws_console_compute_url) as ws_client:
|
||||
asyncio.ensure_future(ws_forward(ws_client))
|
||||
async for msg in ws_client:
|
||||
if msg.type == aiohttp.WSMsgType.TEXT:
|
||||
await ws.send_str(msg.data)
|
||||
elif msg.type == aiohttp.WSMsgType.BINARY:
|
||||
await ws.send_bytes(msg.data)
|
||||
elif msg.type == aiohttp.WSMsgType.ERROR:
|
||||
break
|
||||
finally:
|
||||
if not ws.closed:
|
||||
await ws.close()
|
||||
request.app['websockets'].discard(ws)
|
||||
|
||||
return ws
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes/console/reset",
|
||||
parameters={
|
||||
"project_id": "Project UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "All nodes successfully reset consoles",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reset console for all nodes belonging to the project",
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
async def reset_console_all(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
await project.reset_console_all()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/nodes/{node_id}/console/reset",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"node_id": "Node UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Console reset",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a node instance")
|
||||
async def console_reset(request, response):
|
||||
|
||||
project = await Controller.instance().get_loaded_project(request.match_info["project_id"])
|
||||
node = project.get_node(request.match_info["node_id"])
|
||||
await node.post("/console/reset", request.json)
|
||||
response.set_status(204)
|
||||
@@ -1,87 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import asyncio
|
||||
import aiohttp
|
||||
from aiohttp.web import WebSocketResponse
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def process_websocket(ws):
|
||||
"""
|
||||
Process ping / pong and close message
|
||||
"""
|
||||
try:
|
||||
await ws.receive()
|
||||
except aiohttp.WSServerHandshakeError:
|
||||
pass
|
||||
|
||||
|
||||
class NotificationHandler:
|
||||
|
||||
@Route.get(
|
||||
r"/notifications",
|
||||
description="Receive notifications about the controller",
|
||||
status_codes={
|
||||
200: "End of stream"
|
||||
})
|
||||
async def notification(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
response.content_type = "application/json"
|
||||
response.set_status(200)
|
||||
response.enable_chunked_encoding()
|
||||
|
||||
await response.prepare(request)
|
||||
with controller.notification.controller_queue() as queue:
|
||||
while True:
|
||||
msg = await queue.get_json(5)
|
||||
await response.write(("{}\n".format(msg)).encode("utf-8"))
|
||||
|
||||
@Route.get(
|
||||
r"/notifications/ws",
|
||||
description="Receive notifications about controller from a Websocket",
|
||||
status_codes={
|
||||
200: "End of stream"
|
||||
})
|
||||
async def notification_ws(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
ws = aiohttp.web.WebSocketResponse()
|
||||
await ws.prepare(request)
|
||||
|
||||
request.app['websockets'].add(ws)
|
||||
asyncio.ensure_future(process_websocket(ws))
|
||||
log.info("New client has connected to controller WebSocket")
|
||||
try:
|
||||
with controller.notification.controller_queue() as queue:
|
||||
while True:
|
||||
notification = await queue.get_json(5)
|
||||
if ws.closed:
|
||||
break
|
||||
await ws.send_str(notification)
|
||||
finally:
|
||||
log.info("Client has disconnected from controller WebSocket")
|
||||
if not ws.closed:
|
||||
await ws.close()
|
||||
request.app['websockets'].discard(ws)
|
||||
|
||||
return ws
|
||||
@@ -1,508 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import aiohttp
|
||||
import asyncio
|
||||
import tempfile
|
||||
import zipfile
|
||||
import aiofiles
|
||||
import time
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.controller.import_project import import_project
|
||||
from gns3server.controller.export_project import export_project
|
||||
from gns3server.utils.asyncio import aiozipstream
|
||||
from gns3server.config import Config
|
||||
|
||||
|
||||
from gns3server.schemas.project import (
|
||||
PROJECT_OBJECT_SCHEMA,
|
||||
PROJECT_UPDATE_SCHEMA,
|
||||
PROJECT_LOAD_SCHEMA,
|
||||
PROJECT_CREATE_SCHEMA,
|
||||
PROJECT_DUPLICATE_SCHEMA
|
||||
)
|
||||
|
||||
import logging
|
||||
log = logging.getLogger()
|
||||
|
||||
|
||||
async def process_websocket(ws):
|
||||
"""
|
||||
Process ping / pong and close message
|
||||
"""
|
||||
try:
|
||||
await ws.receive()
|
||||
except aiohttp.WSServerHandshakeError:
|
||||
pass
|
||||
|
||||
CHUNK_SIZE = 1024 * 8 # 8KB
|
||||
|
||||
|
||||
class ProjectHandler:
|
||||
|
||||
@Route.post(
|
||||
r"/projects",
|
||||
description="Create a new project on the server",
|
||||
status_codes={
|
||||
201: "Project created",
|
||||
409: "Project already created"
|
||||
},
|
||||
output=PROJECT_OBJECT_SCHEMA,
|
||||
input=PROJECT_CREATE_SCHEMA)
|
||||
async def create_project(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
project = await controller.add_project(**request.json)
|
||||
response.set_status(201)
|
||||
response.json(project)
|
||||
|
||||
@Route.get(
|
||||
r"/projects",
|
||||
description="List projects",
|
||||
status_codes={
|
||||
200: "List of projects",
|
||||
})
|
||||
def list_projects(request, response):
|
||||
controller = Controller.instance()
|
||||
response.json([p for p in controller.projects.values()])
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}",
|
||||
description="Get a project",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
200: "Project information returned",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
def get(request, response):
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(request.match_info["project_id"])
|
||||
response.json(project)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}",
|
||||
status_codes={
|
||||
200: "Node updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Update a project instance",
|
||||
input=PROJECT_UPDATE_SCHEMA,
|
||||
output=PROJECT_OBJECT_SCHEMA)
|
||||
async def update(request, response):
|
||||
project = Controller.instance().get_project(request.match_info["project_id"])
|
||||
|
||||
# Ignore these because we only use them when creating a project
|
||||
request.json.pop("project_id", None)
|
||||
|
||||
await project.update(**request.json)
|
||||
response.set_status(200)
|
||||
response.json(project)
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}",
|
||||
description="Delete a project from disk",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "Changes have been written on disk",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
async def delete(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(request.match_info["project_id"])
|
||||
await project.delete()
|
||||
controller.remove_project(project)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/stats",
|
||||
description="Get a project statistics",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
200: "Project statistics returned",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
def get(request, response):
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(request.match_info["project_id"])
|
||||
response.json(project.stats())
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/close",
|
||||
description="Close a project",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
204: "The project has been closed",
|
||||
404: "The project doesn't exist"
|
||||
},
|
||||
output=PROJECT_OBJECT_SCHEMA)
|
||||
async def close(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(request.match_info["project_id"])
|
||||
await project.close()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/open",
|
||||
description="Open a project",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
201: "The project has been opened",
|
||||
404: "The project doesn't exist"
|
||||
},
|
||||
output=PROJECT_OBJECT_SCHEMA)
|
||||
async def open(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(request.match_info["project_id"])
|
||||
await project.open()
|
||||
response.set_status(201)
|
||||
response.json(project)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/load",
|
||||
description="Open a project (only local server)",
|
||||
parameters={
|
||||
"path": ".gns3 path",
|
||||
},
|
||||
status_codes={
|
||||
201: "The project has been opened",
|
||||
403: "The server is not the local server"
|
||||
},
|
||||
input=PROJECT_LOAD_SCHEMA,
|
||||
output=PROJECT_OBJECT_SCHEMA)
|
||||
async def load(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
config = Config.instance()
|
||||
dot_gns3_file = request.json.get("path")
|
||||
if config.get_section_config("Server").getboolean("local", False) is False:
|
||||
log.error("Cannot load '{}' because the server has not been started with the '--local' parameter".format(dot_gns3_file))
|
||||
response.set_status(403)
|
||||
return
|
||||
project = await controller.load_project(dot_gns3_file,)
|
||||
response.set_status(201)
|
||||
response.json(project)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/notifications",
|
||||
description="Receive notifications about projects",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
200: "End of stream",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
async def notification(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(request.match_info["project_id"])
|
||||
response.content_type = "application/json"
|
||||
response.set_status(200)
|
||||
response.enable_chunked_encoding()
|
||||
await response.prepare(request)
|
||||
log.info("New client has connected to the notification stream for project ID '{}' (HTTP long-polling method)".format(project.id))
|
||||
|
||||
try:
|
||||
with controller.notification.project_queue(project.id) as queue:
|
||||
while True:
|
||||
msg = await queue.get_json(5)
|
||||
await response.write(("{}\n".format(msg)).encode("utf-8"))
|
||||
finally:
|
||||
log.info("Client has disconnected from notification for project ID '{}' (HTTP long-polling method)".format(project.id))
|
||||
if project.auto_close:
|
||||
# To avoid trouble with client connecting disconnecting we sleep few seconds before checking
|
||||
# if someone else is not connected
|
||||
await asyncio.sleep(5)
|
||||
if not controller.notification.project_has_listeners(project.id):
|
||||
log.info("Project '{}' is automatically closing due to no client listening".format(project.id))
|
||||
await project.close()
|
||||
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/notifications/ws",
|
||||
description="Receive notifications about projects from a Websocket",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
200: "End of stream",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
async def notification_ws(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(request.match_info["project_id"])
|
||||
ws = aiohttp.web.WebSocketResponse()
|
||||
await ws.prepare(request)
|
||||
|
||||
request.app['websockets'].add(ws)
|
||||
asyncio.ensure_future(process_websocket(ws))
|
||||
log.info("New client has connected to the notification stream for project ID '{}' (WebSocket method)".format(project.id))
|
||||
try:
|
||||
with controller.notification.project_queue(project.id) as queue:
|
||||
while True:
|
||||
notification = await queue.get_json(5)
|
||||
if ws.closed:
|
||||
break
|
||||
await ws.send_str(notification)
|
||||
finally:
|
||||
log.info("Client has disconnected from notification stream for project ID '{}' (WebSocket method)".format(project.id))
|
||||
if not ws.closed:
|
||||
await ws.close()
|
||||
request.app['websockets'].discard(ws)
|
||||
if project.auto_close:
|
||||
# To avoid trouble with client connecting disconnecting we sleep few seconds before checking
|
||||
# if someone else is not connected
|
||||
await asyncio.sleep(5)
|
||||
if not controller.notification.project_has_listeners(project.id):
|
||||
log.info("Project '{}' is automatically closing due to no client listening".format(project.id))
|
||||
await project.close()
|
||||
|
||||
return ws
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/export",
|
||||
description="Export a project as a portable archive",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
raw=True,
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
async def export_project(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
project = await controller.get_loaded_project(request.match_info["project_id"])
|
||||
if request.query.get("include_snapshots", "no").lower() == "yes":
|
||||
include_snapshots = True
|
||||
else:
|
||||
include_snapshots = False
|
||||
if request.query.get("include_images", "no").lower() == "yes":
|
||||
include_images = True
|
||||
else:
|
||||
include_images = False
|
||||
if request.query.get("reset_mac_addresses", "no").lower() == "yes":
|
||||
reset_mac_addresses = True
|
||||
else:
|
||||
reset_mac_addresses = False
|
||||
|
||||
compression_query = request.query.get("compression", "zip").lower()
|
||||
if compression_query == "zip":
|
||||
compression = zipfile.ZIP_DEFLATED
|
||||
elif compression_query == "none":
|
||||
compression = zipfile.ZIP_STORED
|
||||
elif compression_query == "bzip2":
|
||||
compression = zipfile.ZIP_BZIP2
|
||||
elif compression_query == "lzma":
|
||||
compression = zipfile.ZIP_LZMA
|
||||
|
||||
try:
|
||||
begin = time.time()
|
||||
# use the parent directory as a temporary working dir
|
||||
working_dir = os.path.abspath(os.path.join(project.path, os.pardir))
|
||||
with tempfile.TemporaryDirectory(dir=working_dir) as tmpdir:
|
||||
with aiozipstream.ZipFile(compression=compression) as zstream:
|
||||
await export_project(zstream, project, tmpdir, include_snapshots=include_snapshots, include_images=include_images, reset_mac_addresses=reset_mac_addresses)
|
||||
|
||||
# We need to do that now because export could failed and raise an HTTP error
|
||||
# that why response start need to be the later possible
|
||||
response.content_type = 'application/gns3project'
|
||||
response.headers['CONTENT-DISPOSITION'] = 'attachment; filename="{}.gns3project"'.format(project.name)
|
||||
response.enable_chunked_encoding()
|
||||
await response.prepare(request)
|
||||
|
||||
async for chunk in zstream:
|
||||
await response.write(chunk)
|
||||
|
||||
log.info("Project '{}' exported in {:.4f} seconds".format(project.name, time.time() - begin))
|
||||
|
||||
# Will be raise if you have no space left or permission issue on your temporary directory
|
||||
# RuntimeError: something was wrong during the zip process
|
||||
except (ValueError, OSError, RuntimeError) as e:
|
||||
raise aiohttp.web.HTTPNotFound(text="Cannot export project: {}".format(str(e)))
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/import",
|
||||
description="Import a project from a portable archive",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
raw=True,
|
||||
output=PROJECT_OBJECT_SCHEMA,
|
||||
status_codes={
|
||||
200: "Project imported",
|
||||
403: "Forbidden to import project"
|
||||
})
|
||||
async def import_project(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
|
||||
if request.get("path"):
|
||||
config = Config.instance()
|
||||
if config.get_section_config("Server").getboolean("local", False) is False:
|
||||
response.set_status(403)
|
||||
return
|
||||
path = request.json.get("path")
|
||||
name = request.json.get("name")
|
||||
|
||||
# We write the content to a temporary location and after we extract it all.
|
||||
# It could be more optimal to stream this but it is not implemented in Python.
|
||||
try:
|
||||
begin = time.time()
|
||||
# use the parent directory or projects dir as a temporary working dir
|
||||
if path:
|
||||
working_dir = os.path.abspath(os.path.join(path, os.pardir))
|
||||
else:
|
||||
working_dir = controller.projects_directory()
|
||||
with tempfile.TemporaryDirectory(dir=working_dir) as tmpdir:
|
||||
temp_project_path = os.path.join(tmpdir, "project.zip")
|
||||
async with aiofiles.open(temp_project_path, 'wb') as f:
|
||||
while True:
|
||||
chunk = await request.content.read(CHUNK_SIZE)
|
||||
if not chunk:
|
||||
break
|
||||
await f.write(chunk)
|
||||
|
||||
with open(temp_project_path, "rb") as f:
|
||||
project = await import_project(controller, request.match_info["project_id"], f, location=path, name=name)
|
||||
|
||||
log.info("Project '{}' imported in {:.4f} seconds".format(project.name, time.time() - begin))
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not import the project: {}".format(e))
|
||||
response.json(project)
|
||||
response.set_status(201)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/duplicate",
|
||||
description="Duplicate a project",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
input=PROJECT_DUPLICATE_SCHEMA,
|
||||
output=PROJECT_OBJECT_SCHEMA,
|
||||
status_codes={
|
||||
201: "Project duplicate",
|
||||
403: "The server is not the local server",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
async def duplicate(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
project = await controller.get_loaded_project(request.match_info["project_id"])
|
||||
|
||||
if request.json.get("path"):
|
||||
config = Config.instance()
|
||||
if config.get_section_config("Server").getboolean("local", False) is False:
|
||||
response.set_status(403)
|
||||
return
|
||||
location = request.json.get("path")
|
||||
else:
|
||||
location = None
|
||||
|
||||
reset_mac_addresses = request.json.get("reset_mac_addresses", False)
|
||||
|
||||
new_project = await project.duplicate(name=request.json.get("name"), location=location, reset_mac_addresses=reset_mac_addresses)
|
||||
|
||||
response.json(new_project)
|
||||
response.set_status(201)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/files/{path:.+}",
|
||||
description="Get a file from a project. Beware you have warranty to be able to access only to file global to the project (for example README.txt)",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
async def get_file(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
project = await controller.get_loaded_project(request.match_info["project_id"])
|
||||
path = request.match_info["path"]
|
||||
path = os.path.normpath(path).strip('/')
|
||||
|
||||
# Raise error if user try to escape
|
||||
if path[0] == ".":
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
path = os.path.join(project.path, path)
|
||||
|
||||
await response.stream_file(path)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/files/{path:.+}",
|
||||
description="Write a file to a project",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
raw=True,
|
||||
status_codes={
|
||||
200: "File returned",
|
||||
403: "Permission denied",
|
||||
404: "The path doesn't exist"
|
||||
})
|
||||
async def write_file(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
project = await controller.get_loaded_project(request.match_info["project_id"])
|
||||
path = request.match_info["path"]
|
||||
path = os.path.normpath(path).strip("/")
|
||||
|
||||
# Raise error if user try to escape
|
||||
if path[0] == ".":
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
path = os.path.join(project.path, path)
|
||||
|
||||
response.set_status(200)
|
||||
|
||||
try:
|
||||
async with aiofiles.open(path, 'wb+') as f:
|
||||
while True:
|
||||
try:
|
||||
chunk = await request.content.read(CHUNK_SIZE)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when writing to file '{}'".format(path))
|
||||
if not chunk:
|
||||
break
|
||||
await f.write(chunk)
|
||||
except FileNotFoundError:
|
||||
raise aiohttp.web.HTTPNotFound()
|
||||
except PermissionError:
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text=str(e))
|
||||
@@ -1,244 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.config import Config
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.schemas.version import VERSION_SCHEMA
|
||||
from gns3server.schemas.iou_license import IOU_LICENSE_SETTINGS_SCHEMA
|
||||
from gns3server.version import __version__
|
||||
|
||||
from aiohttp.web import HTTPConflict, HTTPForbidden
|
||||
|
||||
import os
|
||||
import psutil
|
||||
import shutil
|
||||
import asyncio
|
||||
import platform
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ServerHandler:
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/shutdown",
|
||||
description="Shutdown the local server",
|
||||
status_codes={
|
||||
201: "Server is shutting down",
|
||||
403: "Server shutdown refused"
|
||||
})
|
||||
async def shutdown(request, response):
|
||||
|
||||
config = Config.instance()
|
||||
if config.get_section_config("Server").getboolean("local", False) is False:
|
||||
raise HTTPForbidden(text="You can only stop a local server")
|
||||
|
||||
log.info("Start shutting down the server")
|
||||
|
||||
# close all the projects first
|
||||
controller = Controller.instance()
|
||||
projects = controller.projects.values()
|
||||
|
||||
tasks = []
|
||||
for project in projects:
|
||||
tasks.append(asyncio.ensure_future(project.close()))
|
||||
|
||||
if tasks:
|
||||
done, _ = await asyncio.wait(tasks)
|
||||
for future in done:
|
||||
try:
|
||||
future.result()
|
||||
except Exception as e:
|
||||
log.error("Could not close project {}".format(e), exc_info=1)
|
||||
continue
|
||||
|
||||
# then shutdown the server itself
|
||||
from gns3server.web.web_server import WebServer
|
||||
server = WebServer.instance()
|
||||
try:
|
||||
asyncio.ensure_future(server.shutdown_server())
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
response.set_status(201)
|
||||
|
||||
@Route.get(
|
||||
r"/version",
|
||||
description="Retrieve the server version number",
|
||||
output=VERSION_SCHEMA)
|
||||
def version(request, response):
|
||||
|
||||
config = Config.instance()
|
||||
local_server = config.get_section_config("Server").getboolean("local", False)
|
||||
response.json({"version": __version__, "local": local_server})
|
||||
|
||||
@Route.post(
|
||||
r"/version",
|
||||
description="Check if version is the same as the server",
|
||||
output=VERSION_SCHEMA,
|
||||
input=VERSION_SCHEMA,
|
||||
status_codes={
|
||||
200: "Same version",
|
||||
409: "Invalid version"
|
||||
})
|
||||
def check_version(request, response):
|
||||
if request.json["version"] != __version__:
|
||||
raise HTTPConflict(text="Client version {} is not the same as server version {}".format(request.json["version"], __version__))
|
||||
response.json({"version": __version__})
|
||||
|
||||
@Route.get(
|
||||
r"/iou_license",
|
||||
description="Get the IOU license settings",
|
||||
status_codes={
|
||||
200: "IOU license settings returned"
|
||||
},
|
||||
output_schema=IOU_LICENSE_SETTINGS_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
response.json(Controller.instance().iou_license)
|
||||
|
||||
@Route.put(
|
||||
r"/iou_license",
|
||||
description="Update the IOU license settings",
|
||||
input_schema=IOU_LICENSE_SETTINGS_SCHEMA,
|
||||
output_schema=IOU_LICENSE_SETTINGS_SCHEMA,
|
||||
status_codes={
|
||||
201: "IOU license settings updated"
|
||||
})
|
||||
async def update(request, response):
|
||||
|
||||
controller = Controller().instance()
|
||||
iou_license = controller.iou_license
|
||||
iou_license.update(request.json)
|
||||
controller.save()
|
||||
response.json(iou_license)
|
||||
response.set_status(201)
|
||||
|
||||
@Route.get(
|
||||
r"/statistics",
|
||||
description="Retrieve server statistics",
|
||||
status_codes={
|
||||
200: "Statistics information returned",
|
||||
409: "Conflict"
|
||||
})
|
||||
async def statistics(request, response):
|
||||
|
||||
compute_statistics = []
|
||||
for compute in list(Controller.instance().computes.values()):
|
||||
try:
|
||||
r = await compute.get("/statistics")
|
||||
compute_statistics.append({"compute_id": compute.id, "compute_name": compute.name, "statistics": r.json})
|
||||
except HTTPConflict as e:
|
||||
log.error("Could not retrieve statistics on compute {}: {}".format(compute.name, e.text))
|
||||
response.json(compute_statistics)
|
||||
|
||||
@Route.post(
|
||||
r"/debug",
|
||||
description="Dump debug information to disk (debug directory in config directory). Work only for local server",
|
||||
status_codes={
|
||||
201: "Written"
|
||||
})
|
||||
async def debug(request, response):
|
||||
|
||||
config = Config.instance()
|
||||
if config.get_section_config("Server").getboolean("local", False) is False:
|
||||
raise HTTPForbidden(text="You can only debug a local server")
|
||||
|
||||
debug_dir = os.path.join(config.config_dir, "debug")
|
||||
try:
|
||||
if os.path.exists(debug_dir):
|
||||
shutil.rmtree(debug_dir)
|
||||
os.makedirs(debug_dir)
|
||||
with open(os.path.join(debug_dir, "controller.txt"), "w+") as f:
|
||||
f.write(ServerHandler._getDebugData())
|
||||
except Exception as e:
|
||||
# If something is wrong we log the info to the log and we hope the log will be include correctly to the debug export
|
||||
log.error("Could not export debug information {}".format(e), exc_info=1)
|
||||
|
||||
try:
|
||||
if Controller.instance().gns3vm.engine == "vmware":
|
||||
vmx_path = Controller.instance().gns3vm.current_engine().vmx_path
|
||||
if vmx_path:
|
||||
shutil.copy(vmx_path, os.path.join(debug_dir, os.path.basename(vmx_path)))
|
||||
except OSError as e:
|
||||
# If something is wrong we log the info to the log and we hope the log will be include correctly to the debug export
|
||||
log.error("Could not copy VMware VMX file {}".format(e), exc_info=1)
|
||||
|
||||
for compute in list(Controller.instance().computes.values()):
|
||||
try:
|
||||
r = await compute.get("/debug", raw=True)
|
||||
data = r.body.decode("utf-8")
|
||||
except Exception as e:
|
||||
data = str(e)
|
||||
with open(os.path.join(debug_dir, "compute_{}.txt".format(compute.id)), "w+") as f:
|
||||
f.write("Compute ID: {}\n".format(compute.id))
|
||||
f.write(data)
|
||||
|
||||
response.set_status(201)
|
||||
|
||||
@staticmethod
|
||||
def _getDebugData():
|
||||
try:
|
||||
connections = psutil.net_connections()
|
||||
# You need to be root for OSX
|
||||
except psutil.AccessDenied:
|
||||
connections = None
|
||||
|
||||
try:
|
||||
addrs = ["* {}: {}".format(key, val) for key, val in psutil.net_if_addrs().items()]
|
||||
except UnicodeDecodeError:
|
||||
addrs = ["INVALID ADDR WITH UNICODE CHARACTERS"]
|
||||
|
||||
data = """Version: {version}
|
||||
OS: {os}
|
||||
Python: {python}
|
||||
CPU: {cpu}
|
||||
Memory: {memory}
|
||||
|
||||
Networks:
|
||||
{addrs}
|
||||
|
||||
Open connections:
|
||||
{connections}
|
||||
|
||||
Processus:
|
||||
""".format(
|
||||
version=__version__,
|
||||
os=platform.platform(),
|
||||
python=platform.python_version(),
|
||||
memory=psutil.virtual_memory(),
|
||||
cpu=psutil.cpu_times(),
|
||||
connections=connections,
|
||||
addrs="\n".join(addrs)
|
||||
)
|
||||
for proc in psutil.process_iter():
|
||||
try:
|
||||
psinfo = proc.as_dict(attrs=["name", "exe"])
|
||||
data += "* {} {}\n".format(psinfo["name"], psinfo["exe"])
|
||||
except psutil.NoSuchProcess:
|
||||
pass
|
||||
|
||||
data += "\n\nProjects"
|
||||
for project in Controller.instance().projects.values():
|
||||
data += "\n\nProject name: {}\nProject ID: {}\n".format(project.name, project.id)
|
||||
if project.status != "closed":
|
||||
for link in project.links.values():
|
||||
data += "Link {}: {}".format(link.id, link.debug_link_data)
|
||||
|
||||
return data
|
||||
@@ -1,106 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger()
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
|
||||
from gns3server.schemas.snapshot import (
|
||||
SNAPSHOT_OBJECT_SCHEMA,
|
||||
SNAPSHOT_CREATE_SCHEMA
|
||||
)
|
||||
from gns3server.schemas.project import PROJECT_OBJECT_SCHEMA
|
||||
|
||||
|
||||
class SnapshotHandler:
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/snapshots",
|
||||
description="Create snapshot of a project",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
input=SNAPSHOT_CREATE_SCHEMA,
|
||||
output=SNAPSHOT_OBJECT_SCHEMA,
|
||||
status_codes={
|
||||
201: "Snapshot created",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
async def create(request, response):
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(request.match_info["project_id"])
|
||||
snapshot = await project.snapshot(request.json["name"])
|
||||
response.json(snapshot)
|
||||
response.set_status(201)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/snapshots",
|
||||
description="List snapshots of a project",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
},
|
||||
status_codes={
|
||||
200: "Snapshot list returned",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
def list(request, response):
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(request.match_info["project_id"])
|
||||
snapshots = [s for s in project.snapshots.values()]
|
||||
response.json(sorted(snapshots, key=lambda s: (s.created_at, s.name)))
|
||||
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/snapshots/{snapshot_id}",
|
||||
description="Delete a snapshot from disk",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"snapshot_id": "Snapshot UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Changes have been written on disk",
|
||||
404: "The project or snapshot doesn't exist"
|
||||
})
|
||||
async def delete(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(request.match_info["project_id"])
|
||||
await project.delete_snapshot(request.match_info["snapshot_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/snapshots/{snapshot_id}/restore",
|
||||
description="Restore a snapshot from disk",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"snapshot_id": "Snapshot UUID"
|
||||
},
|
||||
output=PROJECT_OBJECT_SCHEMA,
|
||||
status_codes={
|
||||
201: "The snapshot has been restored",
|
||||
404: "The project or snapshot doesn't exist"
|
||||
})
|
||||
async def restore(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(request.match_info["project_id"])
|
||||
snapshot = project.get_snapshot(request.match_info["snapshot_id"])
|
||||
project = await snapshot.restore()
|
||||
response.set_status(201)
|
||||
response.json(project)
|
||||
@@ -1,113 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import aiohttp
|
||||
import asyncio
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SymbolHandler:
|
||||
"""
|
||||
API entry points for symbols management.
|
||||
"""
|
||||
|
||||
@Route.get(
|
||||
r"/symbols",
|
||||
description="List of symbols",
|
||||
status_codes={
|
||||
200: "Symbols list returned"
|
||||
})
|
||||
def list(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
response.json(controller.symbols.list())
|
||||
|
||||
@Route.get(
|
||||
r"/symbols/{symbol_id:.+}/raw",
|
||||
description="Get the symbol file",
|
||||
status_codes={
|
||||
200: "Symbol returned"
|
||||
})
|
||||
async def raw(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
try:
|
||||
await response.stream_file(controller.symbols.get_path(request.match_info["symbol_id"]))
|
||||
except (KeyError, OSError) as e:
|
||||
log.warning("Could not get symbol file: {}".format(e))
|
||||
response.set_status(404)
|
||||
|
||||
@Route.post(
|
||||
r"/symbols/{symbol_id:.+}/raw",
|
||||
description="Write the symbol file",
|
||||
status_codes={
|
||||
200: "Symbol written"
|
||||
},
|
||||
raw=True)
|
||||
async def upload(request, response):
|
||||
controller = Controller.instance()
|
||||
path = os.path.join(controller.symbols.symbols_path(), os.path.basename(request.match_info["symbol_id"]))
|
||||
try:
|
||||
with open(path, "wb") as f:
|
||||
while True:
|
||||
try:
|
||||
chunk = await request.content.read(1024)
|
||||
except asyncio.TimeoutError:
|
||||
raise aiohttp.web.HTTPRequestTimeout(text="Timeout when writing to symbol '{}'".format(path))
|
||||
if not chunk:
|
||||
break
|
||||
f.write(chunk)
|
||||
except (UnicodeEncodeError, OSError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not write symbol file '{}': {}".format(path, e))
|
||||
|
||||
# Reset the symbol list
|
||||
controller.symbols.list()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/default_symbols",
|
||||
description="List of default symbols",
|
||||
status_codes={
|
||||
200: "Default symbols list returned"
|
||||
})
|
||||
def list_default_symbols(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
response.json(controller.symbols.default_symbols())
|
||||
|
||||
# @Route.post(
|
||||
# r"/symbol_theme",
|
||||
# description="Create a new symbol theme",
|
||||
# status_codes={
|
||||
# 201: "Appliance created",
|
||||
# 400: "Invalid request"
|
||||
# },
|
||||
# input=APPLIANCE_CREATE_SCHEMA,
|
||||
# output=APPLIANCE_OBJECT_SCHEMA)
|
||||
# def create(request, response):
|
||||
#
|
||||
# controller = Controller.instance()
|
||||
# appliance = controller.add_appliance(request.json)
|
||||
# response.set_status(201)
|
||||
# response.json(appliance)
|
||||
@@ -1,174 +0,0 @@
|
||||
# -*- 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.schemas.node import NODE_OBJECT_SCHEMA
|
||||
from gns3server.schemas.template import TEMPLATE_USAGE_SCHEMA
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
|
||||
from gns3server.schemas.template import (
|
||||
TEMPLATE_OBJECT_SCHEMA,
|
||||
TEMPLATE_UPDATE_SCHEMA,
|
||||
TEMPLATE_CREATE_SCHEMA
|
||||
)
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TemplateHandler:
|
||||
"""
|
||||
API entry points for template management.
|
||||
"""
|
||||
|
||||
@Route.post(
|
||||
r"/templates",
|
||||
description="Create a new template",
|
||||
status_codes={
|
||||
201: "Template created",
|
||||
400: "Invalid request"
|
||||
},
|
||||
input=TEMPLATE_CREATE_SCHEMA,
|
||||
output=TEMPLATE_OBJECT_SCHEMA)
|
||||
def create(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
template = controller.template_manager.add_template(request.json)
|
||||
# Reset the symbol list
|
||||
controller.symbols.list()
|
||||
response.set_status(201)
|
||||
response.json(template)
|
||||
|
||||
@Route.get(
|
||||
r"/templates/{template_id}",
|
||||
status_codes={
|
||||
200: "Template found",
|
||||
400: "Invalid request",
|
||||
404: "Template doesn't exist"
|
||||
},
|
||||
description="Get an template",
|
||||
output=TEMPLATE_OBJECT_SCHEMA)
|
||||
def get(request, response):
|
||||
|
||||
request_etag = request.headers.get("If-None-Match", "")
|
||||
controller = Controller.instance()
|
||||
template = controller.template_manager.get_template(request.match_info["template_id"])
|
||||
data = json.dumps(template.__json__())
|
||||
template_etag = '"' + hashlib.md5(data.encode()).hexdigest() + '"'
|
||||
if template_etag == request_etag:
|
||||
response.set_status(304)
|
||||
else:
|
||||
response.headers["ETag"] = template_etag
|
||||
response.set_status(200)
|
||||
response.json(template)
|
||||
|
||||
@Route.put(
|
||||
r"/templates/{template_id}",
|
||||
status_codes={
|
||||
200: "Template updated",
|
||||
400: "Invalid request",
|
||||
404: "Template doesn't exist"
|
||||
},
|
||||
description="Update an template",
|
||||
input=TEMPLATE_UPDATE_SCHEMA,
|
||||
output=TEMPLATE_OBJECT_SCHEMA)
|
||||
def update(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
template = controller.template_manager.get_template(request.match_info["template_id"])
|
||||
# Ignore these because we only use them when creating a template
|
||||
request.json.pop("template_id", None)
|
||||
request.json.pop("template_type", None)
|
||||
request.json.pop("compute_id", None)
|
||||
request.json.pop("builtin", None)
|
||||
template.update(**request.json)
|
||||
response.set_status(200)
|
||||
response.json(template)
|
||||
|
||||
@Route.delete(
|
||||
r"/templates/{template_id}",
|
||||
parameters={
|
||||
"template_id": "template UUID"
|
||||
},
|
||||
status_codes={
|
||||
204: "Template deleted",
|
||||
400: "Invalid request",
|
||||
404: "Template doesn't exist"
|
||||
},
|
||||
description="Delete an template")
|
||||
def delete(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
controller.template_manager.delete_template(request.match_info["template_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/templates",
|
||||
description="List of template",
|
||||
status_codes={
|
||||
200: "Template list returned"
|
||||
})
|
||||
def list(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
response.json([c for c in controller.template_manager.templates.values()])
|
||||
|
||||
@Route.post(
|
||||
r"/templates/{template_id}/duplicate",
|
||||
parameters={
|
||||
"template_id": "Template UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Template duplicated",
|
||||
400: "Invalid request",
|
||||
404: "Template doesn't exist"
|
||||
},
|
||||
description="Duplicate an template",
|
||||
output=TEMPLATE_OBJECT_SCHEMA)
|
||||
async def duplicate(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
template = controller.template_manager.duplicate_template(request.match_info["template_id"])
|
||||
response.set_status(201)
|
||||
response.json(template)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/templates/{template_id}",
|
||||
description="Create a node from a template",
|
||||
parameters={
|
||||
"project_id": "Project UUID",
|
||||
"template_id": "Template UUID"
|
||||
},
|
||||
status_codes={
|
||||
201: "Node created",
|
||||
404: "The project or template doesn't exist"
|
||||
},
|
||||
input=TEMPLATE_USAGE_SCHEMA,
|
||||
output=NODE_OBJECT_SCHEMA)
|
||||
async def create_node_from_template(request, response):
|
||||
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(request.match_info["project_id"])
|
||||
node = await project.add_node_from_template(request.match_info["template_id"],
|
||||
x=request.json["x"],
|
||||
y=request.json["y"],
|
||||
compute_id=request.json.get("compute_id"))
|
||||
response.set_status(201)
|
||||
response.json(node)
|
||||
@@ -1,116 +0,0 @@
|
||||
#
|
||||
# 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import os
|
||||
import aiohttp
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.compute.port_manager import PortManager
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
from gns3server.version import __version__
|
||||
from gns3server.utils.get_resource import get_resource
|
||||
|
||||
|
||||
class IndexHandler:
|
||||
|
||||
@Route.get(
|
||||
r"/",
|
||||
description="Home page of the GNS3 server"
|
||||
)
|
||||
async def index(request, response):
|
||||
|
||||
raise aiohttp.web.HTTPFound(location="/static/web-ui/bundled")
|
||||
|
||||
@Route.get(
|
||||
r"/debug",
|
||||
description="Old index page"
|
||||
)
|
||||
def upload(request, response):
|
||||
response.template("index.html")
|
||||
|
||||
@Route.get(
|
||||
r"/upload",
|
||||
description="Placeholder page for the old /upload"
|
||||
)
|
||||
def upload(request, response):
|
||||
response.template("upload.html")
|
||||
|
||||
@Route.get(
|
||||
r"/compute",
|
||||
description="Resources used by the GNS3 computes"
|
||||
)
|
||||
def compute(request, response):
|
||||
response.template("compute.html",
|
||||
port_manager=PortManager.instance(),
|
||||
project_manager=ProjectManager.instance())
|
||||
|
||||
@Route.get(
|
||||
r"/controller",
|
||||
description="Resources used by the GNS3 controller server"
|
||||
)
|
||||
def controller(request, response):
|
||||
response.template("controller.html",
|
||||
controller=Controller.instance())
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}",
|
||||
description="List of the GNS3 projects"
|
||||
)
|
||||
def project(request, response):
|
||||
controller = Controller.instance()
|
||||
response.template("project.html",
|
||||
project=controller.get_project(request.match_info["project_id"]))
|
||||
|
||||
@Route.get(
|
||||
r"/static/web-ui/{filename:.+}",
|
||||
parameters={
|
||||
"filename": "Static filename"
|
||||
},
|
||||
status_codes={
|
||||
200: "Static file returned",
|
||||
404: "Static cannot be found",
|
||||
},
|
||||
raw=True,
|
||||
description="Get static resource")
|
||||
async def webui(request, response):
|
||||
filename = request.match_info["filename"]
|
||||
filename = os.path.normpath(filename).strip("/")
|
||||
filename = os.path.join('static', 'web-ui', filename)
|
||||
|
||||
# Raise error if user try to escape
|
||||
if filename[0] == ".":
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
|
||||
static = get_resource(filename)
|
||||
|
||||
if static is None or not os.path.exists(static):
|
||||
static = get_resource(os.path.join('static', 'web-ui', 'index.html'))
|
||||
|
||||
# guesstype prefers to have text/html type than application/javascript
|
||||
# which results with warnings in Firefox 66 on Windows
|
||||
# Ref. gns3-server#1559
|
||||
_, ext = os.path.splitext(static)
|
||||
mimetype = ext == '.js' and 'application/javascript' or None
|
||||
|
||||
await response.stream_file(static, status=200, set_content_type=mimetype)
|
||||
|
||||
@Route.get(
|
||||
r"/v1/version",
|
||||
description="Old 1.0 API"
|
||||
)
|
||||
def get_v1(request, response):
|
||||
response.json({"version": __version__})
|
||||
Reference in new issue
Block a user