mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-05 23:01:15 +02:00
186 files changed
+1173
-1206
No files matched your search
@@ -0,0 +1,39 @@
|
||||
#!/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 .network_handler import NetworkHandler
|
||||
from .project_handler import ProjectHandler
|
||||
from .dynamips_device_handler import DynamipsDeviceHandler
|
||||
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 .config_handler import ConfigHandler
|
||||
from .file_handler import FileHandler
|
||||
from .version_handler import VersionHandler
|
||||
from .notification_handler import NotificationHandler
|
||||
|
||||
|
||||
if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1":
|
||||
# IOU runs only on Linux but testsuite work on UNIX platform
|
||||
if not sys.platform.startswith("win"):
|
||||
from .iou_handler import IOUHandler
|
||||
from .docker_handler import DockerHandler
|
||||
@@ -0,0 +1,40 @@
|
||||
# -*- 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 aiohttp.web import HTTPForbidden
|
||||
|
||||
from ....web.route import Route
|
||||
from ....config import Config
|
||||
|
||||
|
||||
class ConfigHandler:
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/config/reload",
|
||||
description="Check if version is the same as the server",
|
||||
status_codes={
|
||||
201: "Config reload",
|
||||
403: "Config reload refused"
|
||||
})
|
||||
def reload(request, response):
|
||||
|
||||
config = Config.instance()
|
||||
if config.get_section_config("Server").getboolean("local", False) is False:
|
||||
raise HTTPForbidden(text="You can only reload the configuration for a local server")
|
||||
config.reload()
|
||||
response.set_status(201)
|
||||
@@ -0,0 +1,342 @@
|
||||
# -*- 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 ....web.route import Route
|
||||
from ....compute.docker import Docker
|
||||
|
||||
from ....schemas.docker import (
|
||||
DOCKER_CREATE_SCHEMA,
|
||||
DOCKER_OBJECT_SCHEMA,
|
||||
DOCKER_UPDATE_SCHEMA,
|
||||
DOCKER_LIST_IMAGES_SCHEMA
|
||||
)
|
||||
from ....schemas.vm import VM_CAPTURE_SCHEMA
|
||||
from ....schemas.nio import NIO_SCHEMA
|
||||
|
||||
|
||||
class DockerHandler:
|
||||
"""API entry points for Docker."""
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/docker/images",
|
||||
status_codes={
|
||||
200: "Success",
|
||||
},
|
||||
output=DOCKER_LIST_IMAGES_SCHEMA,
|
||||
description="Get all available Docker images")
|
||||
def show(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
images = yield from docker_manager.list_images()
|
||||
response.json(images)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/vms",
|
||||
parameters={
|
||||
"project_id": "UUID for the project"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new Docker container",
|
||||
input=DOCKER_CREATE_SCHEMA,
|
||||
output=DOCKER_OBJECT_SCHEMA)
|
||||
def create(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = yield from docker_manager.create_vm(
|
||||
request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("vm_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"),
|
||||
aux=request.json.get("aux")
|
||||
)
|
||||
for name, value in request.json.items():
|
||||
if name != "_vm_id":
|
||||
if hasattr(container, name) and getattr(container, name) != value:
|
||||
setattr(container, name, value)
|
||||
|
||||
response.set_status(201)
|
||||
response.json(container)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/vms/{id}/start",
|
||||
parameters={
|
||||
"project_id": "UUID of the project",
|
||||
"id": "ID of the container"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a Docker container",
|
||||
input=DOCKER_CREATE_SCHEMA,
|
||||
output=DOCKER_OBJECT_SCHEMA)
|
||||
def start(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_vm(
|
||||
request.match_info["id"],
|
||||
project_id=request.match_info["project_id"])
|
||||
yield from container.start()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/vms/{id}/stop",
|
||||
parameters={
|
||||
"project_id": "UUID of the project",
|
||||
"id": "ID of the container"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a Docker container",
|
||||
input=DOCKER_CREATE_SCHEMA,
|
||||
output=DOCKER_OBJECT_SCHEMA)
|
||||
def stop(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_vm(
|
||||
request.match_info["id"],
|
||||
project_id=request.match_info["project_id"])
|
||||
yield from container.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/vms/{id}/reload",
|
||||
parameters={
|
||||
"project_id": "UUID of the project",
|
||||
"id": "ID of the container"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance restarted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Restart a Docker container",
|
||||
input=DOCKER_CREATE_SCHEMA,
|
||||
output=DOCKER_OBJECT_SCHEMA)
|
||||
def reload(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_vm(
|
||||
request.match_info["id"],
|
||||
project_id=request.match_info["project_id"])
|
||||
yield from container.restart()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/docker/vms/{id}",
|
||||
parameters={
|
||||
"id": "ID for the container",
|
||||
"project_id": "UUID for the project"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Delete a Docker container")
|
||||
def delete(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_vm(
|
||||
request.match_info["id"],
|
||||
project_id=request.match_info["project_id"])
|
||||
yield from container.delete()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/vms/{id}/suspend",
|
||||
parameters={
|
||||
"project_id": "UUID of the project",
|
||||
"id": "ID of the container"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance paused",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Pause a Docker container",
|
||||
input=DOCKER_CREATE_SCHEMA,
|
||||
output=DOCKER_OBJECT_SCHEMA)
|
||||
def suspend(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_vm(
|
||||
request.match_info["id"],
|
||||
project_id=request.match_info["project_id"])
|
||||
yield from container.pause()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"id": "ID of the container",
|
||||
"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 Docker container",
|
||||
input=NIO_SCHEMA,
|
||||
output=NIO_SCHEMA)
|
||||
def create_nio(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_vm(
|
||||
request.match_info["vm_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 = docker_manager.create_nio(
|
||||
int(request.match_info["adapter_number"]), request.json)
|
||||
adapter = container._ethernet_adapters[
|
||||
int(request.match_info["adapter_number"])
|
||||
]
|
||||
yield from container.adapter_add_nio_binding(
|
||||
int(request.match_info["adapter_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/docker/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"id": "ID of the container",
|
||||
"adapter_number": "Adapter where the nio should be added",
|
||||
"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 Docker container")
|
||||
def delete_nio(request, response):
|
||||
docker_manager = Docker.instance()
|
||||
container = docker_manager.get_vm(
|
||||
request.match_info["vm_id"],
|
||||
project_id=request.match_info["project_id"])
|
||||
yield from container.adapter_remove_nio_binding(
|
||||
int(request.match_info["adapter_number"]))
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/docker/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
200: "Instance updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Update a Docker instance",
|
||||
input=DOCKER_UPDATE_SCHEMA,
|
||||
output=DOCKER_OBJECT_SCHEMA)
|
||||
def update(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
vm = docker_manager.get_vm(request.match_info["vm_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.aux = request.json.get("aux", vm.aux)
|
||||
vm.console_resolution = request.json.get("console_resolution", vm.console_resolution)
|
||||
vm.start_command = request.json.get("start_command", vm.start_command)
|
||||
vm.environment = request.json.get("environment", vm.environment)
|
||||
vm.adapters = request.json.get("adapters", vm.adapters)
|
||||
yield from vm.update()
|
||||
response.json(vm)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"adapter_number": "Adapter to start a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "VM not started"
|
||||
},
|
||||
description="Start a packet capture on a IOU VM instance",
|
||||
input=VM_CAPTURE_SCHEMA)
|
||||
def start_capture(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
vm = docker_manager.get_vm(request.match_info["vm_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"])
|
||||
|
||||
if not vm.is_running():
|
||||
raise HTTPConflict(text="Cannot capture traffic on a non started VM")
|
||||
yield from vm.start_capture(adapter_number, pcap_file_path)
|
||||
response.json({"pcap_file_path": str(pcap_file_path)})
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/docker/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/stop_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"adapter_number": "Adapter to stop a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
204: "Capture stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "VM not started"
|
||||
},
|
||||
description="Stop a packet capture on a IOU VM instance")
|
||||
def stop_capture(request, response):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
vm = docker_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
if not vm.is_running():
|
||||
raise HTTPConflict(text="Cannot capture traffic on a non started VM")
|
||||
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
yield from vm.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
@@ -0,0 +1,230 @@
|
||||
# -*- 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 asyncio
|
||||
from ....web.route import Route
|
||||
from ....schemas.dynamips_device import DEVICE_CREATE_SCHEMA
|
||||
from ....schemas.dynamips_device import DEVICE_UPDATE_SCHEMA
|
||||
from ....schemas.dynamips_device import DEVICE_OBJECT_SCHEMA
|
||||
from ....schemas.dynamips_device import DEVICE_NIO_SCHEMA
|
||||
from ....schemas.vm import VM_CAPTURE_SCHEMA
|
||||
from ....compute.dynamips import Dynamips
|
||||
|
||||
|
||||
class DynamipsDeviceHandler:
|
||||
|
||||
"""
|
||||
API entry points for Dynamips devices.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/devices",
|
||||
parameters={
|
||||
"project_id": "UUID for the project"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new Dynamips device instance",
|
||||
input=DEVICE_CREATE_SCHEMA,
|
||||
output=DEVICE_OBJECT_SCHEMA)
|
||||
def create(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
device = yield from dynamips_manager.create_device(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("device_id"),
|
||||
request.json.get("device_type"))
|
||||
|
||||
response.set_status(201)
|
||||
response.json(device)
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/devices/{device_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"device_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
200: "Success",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Get a Dynamips device instance",
|
||||
output=DEVICE_OBJECT_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
device = dynamips_manager.get_device(request.match_info["device_id"], project_id=request.match_info["project_id"])
|
||||
response.json(device)
|
||||
|
||||
@classmethod
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/dynamips/devices/{device_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"device_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
200: "Instance updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Update a Dynamips device instance",
|
||||
input=DEVICE_UPDATE_SCHEMA,
|
||||
output=DEVICE_OBJECT_SCHEMA)
|
||||
def update(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
device = dynamips_manager.get_device(request.match_info["device_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
if "name" in request.json:
|
||||
yield from device.set_name(request.json["name"])
|
||||
|
||||
if "ports" in request.json:
|
||||
for port in request.json["ports"]:
|
||||
yield from device.set_port_settings(port["port"], port)
|
||||
|
||||
response.json(device)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/dynamips/devices/{device_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"device_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Delete a Dynamips device instance")
|
||||
def delete(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
yield from dynamips_manager.delete_device(request.match_info["device_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/devices/{device_id}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"device_id": "UUID for the instance",
|
||||
"port_number": "Port on the device"
|
||||
},
|
||||
status_codes={
|
||||
201: "NIO created",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Add a NIO to a Dynamips device instance",
|
||||
input=DEVICE_NIO_SCHEMA)
|
||||
def create_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
device = dynamips_manager.get_device(request.match_info["device_id"], project_id=request.match_info["project_id"])
|
||||
nio = yield from dynamips_manager.create_nio(device, request.json["nio"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
port_settings = request.json.get("port_settings")
|
||||
mappings = request.json.get("mappings")
|
||||
|
||||
if asyncio.iscoroutinefunction(device.add_nio):
|
||||
yield from device.add_nio(nio, port_number)
|
||||
else:
|
||||
device.add_nio(nio, port_number)
|
||||
|
||||
if port_settings:
|
||||
yield from device.set_port_settings(port_number, port_settings)
|
||||
elif mappings:
|
||||
yield from device.set_mappings(mappings)
|
||||
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/dynamips/devices/{device_id}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"device_id": "UUID for the instance",
|
||||
"port_number": "Port on the device"
|
||||
},
|
||||
status_codes={
|
||||
204: "NIO deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Remove a NIO from a Dynamips device instance")
|
||||
def delete_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
device = dynamips_manager.get_device(request.match_info["device_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = yield from device.remove_nio(port_number)
|
||||
yield from nio.delete()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/devices/{device_id}/ports/{port_number:\d+}/start_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"device_id": "UUID for the instance",
|
||||
"port_number": "Port on the device"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a packet capture on a Dynamips device instance",
|
||||
input=VM_CAPTURE_SCHEMA)
|
||||
def start_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
device = dynamips_manager.get_device(request.match_info["device_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
pcap_file_path = os.path.join(device.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
yield from device.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}/dynamips/devices/{device_id}/ports/{port_number:\d+}/stop_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"device_id": "UUID for the instance",
|
||||
"port_number": "Port on the device"
|
||||
},
|
||||
status_codes={
|
||||
204: "Capture stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a packet capture on a Dynamips device instance")
|
||||
def stop_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
device = dynamips_manager.get_device(request.match_info["device_id"], project_id=request.match_info["project_id"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
yield from device.stop_capture(port_number)
|
||||
response.set_status(204)
|
||||
@@ -0,0 +1,491 @@
|
||||
# -*- 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 base64
|
||||
|
||||
from ....web.route import Route
|
||||
from ....schemas.nio import NIO_SCHEMA
|
||||
from ....schemas.vm import VM_LIST_IMAGES_SCHEMA
|
||||
from ....schemas.dynamips_vm import VM_CREATE_SCHEMA
|
||||
from ....schemas.dynamips_vm import VM_UPDATE_SCHEMA
|
||||
from ....schemas.dynamips_vm import VM_OBJECT_SCHEMA
|
||||
from ....schemas.dynamips_vm import VM_CONFIGS_SCHEMA
|
||||
from ....schemas.vm import VM_CAPTURE_SCHEMA
|
||||
from ....compute.dynamips import Dynamips
|
||||
from ....compute.dynamips.dynamips_error import DynamipsError
|
||||
from ....compute.project_manager import ProjectManager
|
||||
|
||||
DEFAULT_CHASSIS = {
|
||||
"c1700": "1720",
|
||||
"c2600": "2610",
|
||||
"c3600": "3640"
|
||||
}
|
||||
|
||||
|
||||
class DynamipsVMHandler:
|
||||
|
||||
"""
|
||||
API entry points for Dynamips VMs.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/vms",
|
||||
parameters={
|
||||
"project_id": "UUID for the project"
|
||||
},
|
||||
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)
|
||||
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 = yield from dynamips_manager.create_vm(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("vm_id"),
|
||||
request.json.get("dynamips_id"),
|
||||
platform,
|
||||
console=request.json.get("console"),
|
||||
aux=request.json.get("aux"),
|
||||
chassis=request.json.pop("chassis", default_chassis))
|
||||
|
||||
yield from dynamips_manager.update_vm_settings(vm, request.json)
|
||||
response.set_status(201)
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
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_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
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)
|
||||
def update(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
yield from dynamips_manager.update_vm_settings(vm, request.json)
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Delete a Dynamips VM instance")
|
||||
def delete(request, response):
|
||||
|
||||
# check the project_id exists
|
||||
ProjectManager.instance().get_project(request.match_info["project_id"])
|
||||
|
||||
yield from Dynamips.instance().delete_vm(request.match_info["vm_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}/start",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a Dynamips VM instance")
|
||||
def start(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
try:
|
||||
yield from dynamips_manager.ghost_ios_support(vm)
|
||||
except GeneratorExit:
|
||||
pass
|
||||
yield from vm.start()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}/stop",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a Dynamips VM instance")
|
||||
def stop(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}/suspend",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance suspended",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Suspend a Dynamips VM instance")
|
||||
def suspend(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.suspend()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}/resume",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance resumed",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Resume a suspended Dynamips VM instance")
|
||||
def resume(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.resume()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}/reload",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a Dynamips VM instance")
|
||||
def reload(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.reload()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"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)
|
||||
def create_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
nio = yield from dynamips_manager.create_nio(vm, request.json)
|
||||
slot_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
yield from vm.slot_add_nio_binding(slot_number, port_number, nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"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")
|
||||
def delete_nio(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
slot_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
nio = yield from vm.slot_remove_nio_binding(slot_number, port_number)
|
||||
yield from nio.delete()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"adapter_number": "Adapter to start a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a packet capture on a Dynamips VM instance",
|
||||
input=VM_CAPTURE_SCHEMA)
|
||||
def start_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
slot_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
pcap_file_path = os.path.join(vm.project.capture_working_directory(), request.json["capture_file_name"])
|
||||
|
||||
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))
|
||||
|
||||
yield from vm.start_capture(slot_number, port_number, pcap_file_path, request.json["data_link_type"])
|
||||
response.json({"pcap_file_path": pcap_file_path})
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/stop_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"adapter_number": "Adapter to stop a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
204: "Capture stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a packet capture on a Dynamips VM instance")
|
||||
def stop_capture(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
slot_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
yield from vm.stop_capture(slot_number, port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}/configs",
|
||||
status_codes={
|
||||
200: "Configs retrieved",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
output=VM_CONFIGS_SCHEMA,
|
||||
description="Retrieve the startup and private configs content")
|
||||
def get_configs(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"],
|
||||
project_id=request.match_info["project_id"])
|
||||
|
||||
startup_config_base64, private_config_base64 = yield from vm.extract_config()
|
||||
module_workdir = vm.project.module_working_directory(dynamips_manager.module_name.lower())
|
||||
result = {}
|
||||
if startup_config_base64:
|
||||
startup_config_content = base64.b64decode(startup_config_base64).decode("utf-8", errors='replace')
|
||||
result["startup_config_content"] = startup_config_content
|
||||
else:
|
||||
# nvram doesn't contain anything if the router has not been started at least once
|
||||
# in this case just use the startup-config file
|
||||
if vm.startup_config:
|
||||
startup_config_path = os.path.join(module_workdir, vm.startup_config)
|
||||
if os.path.isfile(startup_config_path):
|
||||
try:
|
||||
with open(startup_config_path, "rb") as f:
|
||||
content = f.read().decode("utf-8", errors='replace')
|
||||
if content:
|
||||
result["startup_config_content"] = content
|
||||
except OSError as e:
|
||||
raise DynamipsError("Could not read the startup-config {}: {}".format(startup_config_path, e))
|
||||
|
||||
if private_config_base64:
|
||||
private_config_content = base64.b64decode(private_config_base64).decode("utf-8", errors='replace')
|
||||
result["private_config_content"] = private_config_content
|
||||
else:
|
||||
# nvram doesn't contain anything if the router has not been started at least once
|
||||
# in this case just use the private-config file
|
||||
if vm.private_config:
|
||||
private_config_path = os.path.join(module_workdir, vm.private_config)
|
||||
if os.path.isfile(private_config_path):
|
||||
try:
|
||||
with open(private_config_path, "rb") as f:
|
||||
content = f.read().decode("utf-8", errors='replace')
|
||||
if content:
|
||||
result["private_config_content"] = content
|
||||
except OSError as e:
|
||||
raise DynamipsError("Could not read the private-config {}: {}".format(private_config_path, e))
|
||||
|
||||
response.set_status(200)
|
||||
response.json(result)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}/configs/save",
|
||||
status_codes={
|
||||
200: "Configs saved",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Save the startup and private configs content")
|
||||
def save_configs(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"],
|
||||
project_id=request.match_info["project_id"])
|
||||
|
||||
yield from vm.save_configs()
|
||||
response.set_status(200)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}/idlepc_proposals",
|
||||
status_codes={
|
||||
200: "Idle-PCs retrieved",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Retrieve the idlepc proposals")
|
||||
def get_idlepcs(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"],
|
||||
project_id=request.match_info["project_id"])
|
||||
|
||||
yield from vm.set_idlepc("0x0")
|
||||
idlepcs = yield from vm.get_idle_pc_prop()
|
||||
response.set_status(200)
|
||||
response.json(idlepcs)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/dynamips/vms/{vm_id}/auto_idlepc",
|
||||
status_codes={
|
||||
200: "Best Idle-pc value found",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Retrieve the idlepc proposals")
|
||||
def get_auto_idlepc(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vm = dynamips_manager.get_vm(request.match_info["vm_id"],
|
||||
project_id=request.match_info["project_id"])
|
||||
idlepc = yield from dynamips_manager.auto_idlepc(vm)
|
||||
response.set_status(200)
|
||||
response.json({"idlepc": idlepc})
|
||||
|
||||
@Route.get(
|
||||
r"/dynamips/vms",
|
||||
status_codes={
|
||||
200: "List of Dynamips VM retrieved",
|
||||
},
|
||||
description="Retrieve the list of Dynamips VMS",
|
||||
output=VM_LIST_IMAGES_SCHEMA)
|
||||
def list_vms(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
vms = yield from dynamips_manager.list_images()
|
||||
response.set_status(200)
|
||||
response.json(vms)
|
||||
|
||||
@Route.post(
|
||||
r"/dynamips/vms/{path}",
|
||||
status_codes={
|
||||
204: "Image uploaded",
|
||||
},
|
||||
raw=True,
|
||||
description="Upload Dynamips image.")
|
||||
def upload_vm(request, response):
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
yield from dynamips_manager.write_image(request.match_info["path"], request.content)
|
||||
response.set_status(204)
|
||||
@@ -0,0 +1,59 @@
|
||||
#
|
||||
# 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 ....web.route import Route
|
||||
from ....schemas.file import FILE_STREAM_SCHEMA
|
||||
|
||||
|
||||
class FileHandler:
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/files/stream",
|
||||
description="Stream a file from the server",
|
||||
status_codes={
|
||||
200: "File retrieved",
|
||||
404: "File doesn't exist",
|
||||
409: "Can't access to file"
|
||||
},
|
||||
input=FILE_STREAM_SCHEMA
|
||||
)
|
||||
def read(request, response):
|
||||
response.enable_chunked_encoding()
|
||||
|
||||
try:
|
||||
with open(request.json.get("location"), "rb") as f:
|
||||
loop = asyncio.get_event_loop()
|
||||
response.content_type = "application/octet-stream"
|
||||
response.set_status(200)
|
||||
# Very important: do not send a content lenght otherwise QT close the connection but curl can consume the Feed
|
||||
response.content_length = None
|
||||
|
||||
response.start(request)
|
||||
|
||||
while True:
|
||||
data = yield from loop.run_in_executor(None, f.read, 16)
|
||||
if len(data) == 0:
|
||||
yield from asyncio.sleep(0.1)
|
||||
else:
|
||||
response.write(data)
|
||||
except FileNotFoundError:
|
||||
raise aiohttp.web.HTTPNotFound()
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text=str(e))
|
||||
@@ -0,0 +1,396 @@
|
||||
# -*- 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 ....web.route import Route
|
||||
from ....schemas.nio import NIO_SCHEMA
|
||||
from ....schemas.iou import IOU_CREATE_SCHEMA
|
||||
from ....schemas.iou import IOU_START_SCHEMA
|
||||
from ....schemas.iou import IOU_UPDATE_SCHEMA
|
||||
from ....schemas.iou import IOU_OBJECT_SCHEMA
|
||||
from ....schemas.iou import IOU_CONFIGS_SCHEMA
|
||||
from ....schemas.vm import VM_LIST_IMAGES_SCHEMA
|
||||
from ....schemas.vm import VM_CAPTURE_SCHEMA
|
||||
from ....compute.iou import IOU
|
||||
|
||||
|
||||
class IOUHandler:
|
||||
|
||||
"""
|
||||
API entry points for IOU.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/vms",
|
||||
parameters={
|
||||
"project_id": "UUID for the project"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new IOU instance",
|
||||
input=IOU_CREATE_SCHEMA,
|
||||
output=IOU_OBJECT_SCHEMA)
|
||||
def create(request, response):
|
||||
|
||||
iou = IOU.instance()
|
||||
vm = yield from iou.create_vm(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("vm_id"),
|
||||
console=request.json.get("console"))
|
||||
|
||||
for name, value in request.json.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
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
|
||||
setattr(vm, name, value)
|
||||
if "startup_config_content" in request.json:
|
||||
vm.startup_config = request.json.get("startup_config_content")
|
||||
if "private_config_content" in request.json:
|
||||
vm.private_config = request.json.get("private_config_content")
|
||||
response.set_status(201)
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/iou/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
200: "Success",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Get a IOU instance",
|
||||
output=IOU_OBJECT_SCHEMA)
|
||||
def show(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/iou/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
200: "Instance updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Update a IOU instance",
|
||||
input=IOU_UPDATE_SCHEMA,
|
||||
output=IOU_OBJECT_SCHEMA)
|
||||
def update(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_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)
|
||||
if "startup_config_content" in request.json:
|
||||
vm.startup_config = request.json.get("startup_config_content")
|
||||
if "private_config_content" in request.json:
|
||||
vm.private_config = request.json.get("private_config_content")
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/iou/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Delete a IOU instance")
|
||||
def delete(request, response):
|
||||
|
||||
yield from IOU.instance().delete_vm(request.match_info["vm_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/vms/{vm_id}/start",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
200: "Instance started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
input=IOU_START_SCHEMA,
|
||||
output=IOU_OBJECT_SCHEMA,
|
||||
description="Start a IOU instance")
|
||||
def start(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_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)
|
||||
print(name)
|
||||
print(vm.iourc_path)
|
||||
|
||||
yield from vm.start()
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/vms/{vm_id}/stop",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a IOU instance")
|
||||
def stop(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/vms/{vm_id}/reload",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a IOU instance")
|
||||
def reload(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.reload()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"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)
|
||||
def create_nio(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
nio_type = request.json["type"]
|
||||
if nio_type not in ("nio_udp", "nio_tap", "nio_generic_ethernet"):
|
||||
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
nio = iou_manager.create_nio(vm.iouyap_path, request.json)
|
||||
yield from 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)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/iou/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"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")
|
||||
def delete_nio(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from 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/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"adapter_number": "Adapter to start a packet capture",
|
||||
"port_number": "Port on the adapter"
|
||||
},
|
||||
status_codes={
|
||||
200: "Capture started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "VM not started"
|
||||
},
|
||||
description="Start a packet capture on a IOU VM instance",
|
||||
input=VM_CAPTURE_SCHEMA)
|
||||
def start_capture(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_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"])
|
||||
|
||||
if not vm.is_running():
|
||||
raise HTTPConflict(text="Cannot capture traffic on a non started VM")
|
||||
yield from 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/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/stop_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"adapter_number": "Adapter to stop a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
204: "Capture stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "VM not started"
|
||||
},
|
||||
description="Stop a packet capture on a IOU VM instance")
|
||||
def stop_capture(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
if not vm.is_running():
|
||||
raise HTTPConflict(text="Cannot capture traffic on a non started VM")
|
||||
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
port_number = int(request.match_info["port_number"])
|
||||
yield from vm.stop_capture(adapter_number, port_number)
|
||||
response.set_status(204)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/iou/vms/{vm_id}/configs",
|
||||
status_codes={
|
||||
200: "Configs retrieved",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
output=IOU_CONFIGS_SCHEMA,
|
||||
description="Retrieve the startup and private configs content")
|
||||
def get_configs(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
startup_config_content, private_config_content = vm.extract_configs()
|
||||
result = {}
|
||||
if startup_config_content:
|
||||
result["startup_config_content"] = startup_config_content.decode("utf-8", errors='replace')
|
||||
else:
|
||||
# nvram doesn't exists if the VM has not been started at least once
|
||||
# in this case just use the startup-config file
|
||||
startup_config_content = vm.startup_config_content
|
||||
if startup_config_content:
|
||||
result["startup_config_content"] = startup_config_content
|
||||
|
||||
if private_config_content:
|
||||
result["private_config_content"] = private_config_content.decode("utf-8", errors='replace')
|
||||
else:
|
||||
# nvram doesn't exists if the VM has not been started at least once
|
||||
# in this case just use the private-config file
|
||||
private_config_content = vm.private_config_content
|
||||
if private_config_content:
|
||||
result["private_config_content"] = private_config_content
|
||||
|
||||
response.set_status(200)
|
||||
response.json(result)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/iou/vms/{vm_id}/configs/save",
|
||||
status_codes={
|
||||
200: "Configs saved",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Save the startup and private configs content")
|
||||
def save_configs(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vm = iou_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
vm.save_configs()
|
||||
response.set_status(200)
|
||||
|
||||
@Route.get(
|
||||
r"/iou/vms",
|
||||
status_codes={
|
||||
200: "List of IOU VM retrieved",
|
||||
},
|
||||
description="Retrieve the list of IOU VMS",
|
||||
output=VM_LIST_IMAGES_SCHEMA)
|
||||
def list_vms(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
vms = yield from iou_manager.list_images()
|
||||
response.set_status(200)
|
||||
response.json(vms)
|
||||
|
||||
@Route.post(
|
||||
r"/iou/vms/{path}",
|
||||
status_codes={
|
||||
204: "Image uploaded",
|
||||
},
|
||||
raw=True,
|
||||
description="Upload IOU image.")
|
||||
def upload_vm(request, response):
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
yield from iou_manager.write_image(request.match_info["path"], request.content)
|
||||
response.set_status(204)
|
||||
@@ -0,0 +1,53 @@
|
||||
# -*- 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 ....web.route import Route
|
||||
from ....compute.port_manager import PortManager
|
||||
from ....compute.project_manager import ProjectManager
|
||||
from ....utils.interfaces import interfaces
|
||||
|
||||
|
||||
class NetworkHandler:
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/ports/udp",
|
||||
parameters={
|
||||
"project_id": "The UUID of the project",
|
||||
},
|
||||
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})
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/interfaces",
|
||||
description="List all the network interfaces available on the server")
|
||||
def network_interfaces(request, response):
|
||||
|
||||
network_interfaces = interfaces()
|
||||
response.json(network_interfaces)
|
||||
@@ -0,0 +1,43 @@
|
||||
# -*- 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
|
||||
|
||||
from ....web.route import Route
|
||||
from ....compute.notification_manager import NotificationManager
|
||||
from aiohttp.web import WebSocketResponse
|
||||
|
||||
|
||||
class NotificationHandler:
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/notifications/ws",
|
||||
description="Send notifications about what happend using websockets")
|
||||
def notifications(request, response):
|
||||
notifications = NotificationManager.instance()
|
||||
ws = WebSocketResponse()
|
||||
yield from ws.prepare(request)
|
||||
|
||||
with notifications.queue() as queue:
|
||||
while True:
|
||||
try:
|
||||
notif = yield from queue.get_json(5)
|
||||
except asyncio.futures.CancelledError as e:
|
||||
break
|
||||
ws.send_str(notif)
|
||||
return ws
|
||||
@@ -0,0 +1,413 @@
|
||||
# -*- 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
|
||||
import tempfile
|
||||
|
||||
from ....web.route import Route
|
||||
from ....schemas.project import PROJECT_OBJECT_SCHEMA, PROJECT_CREATE_SCHEMA, PROJECT_UPDATE_SCHEMA, PROJECT_FILE_LIST_SCHEMA, PROJECT_LIST_SCHEMA
|
||||
from ....compute.project_manager import ProjectManager
|
||||
from ....compute import MODULES
|
||||
|
||||
import logging
|
||||
log = logging.getLogger()
|
||||
|
||||
|
||||
class ProjectHandler:
|
||||
|
||||
# How many clients has subcribe to notifications
|
||||
_notifications_listening = {}
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects",
|
||||
description="List 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))
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects",
|
||||
description="Create a new project on the server",
|
||||
status_codes={
|
||||
201: "Project created",
|
||||
403: "You are not allowed to modify this property",
|
||||
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"),
|
||||
temporary=request.json.get("temporary", False)
|
||||
)
|
||||
response.set_status(201)
|
||||
response.json(p)
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects/{project_id}",
|
||||
description="Get project information",
|
||||
parameters={
|
||||
"project_id": "The UUID of the project",
|
||||
},
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
@Route.put(
|
||||
r"/projects/{project_id}",
|
||||
description="Update a project",
|
||||
parameters={
|
||||
"project_id": "The UUID of the project",
|
||||
},
|
||||
status_codes={
|
||||
200: "The project has been updated",
|
||||
403: "You are not allowed to modify this property",
|
||||
404: "The project doesn't exist"
|
||||
},
|
||||
output=PROJECT_OBJECT_SCHEMA,
|
||||
input=PROJECT_UPDATE_SCHEMA)
|
||||
def update(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
project.name = request.json.get("name", project.name)
|
||||
project_path = request.json.get("path", project.path)
|
||||
if project_path != project.path:
|
||||
old_path = project.path
|
||||
project.path = project_path
|
||||
for module in MODULES:
|
||||
yield from module.instance().project_moved(project)
|
||||
yield from project.clean_old_path(old_path)
|
||||
# Very important we need to remove temporary flag after moving the project
|
||||
project.temporary = request.json.get("temporary", project.temporary)
|
||||
response.json(project)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/commit",
|
||||
description="Write changes on disk",
|
||||
parameters={
|
||||
"project_id": "The UUID of the project",
|
||||
},
|
||||
status_codes={
|
||||
204: "Changes have been written on disk",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
def commit(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
yield from project.commit()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/close",
|
||||
description="Close a project",
|
||||
parameters={
|
||||
"project_id": "The UUID of the project",
|
||||
},
|
||||
status_codes={
|
||||
204: "The project has been closed",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
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:
|
||||
yield from project.close()
|
||||
pm.remove_project(project.id)
|
||||
del ProjectHandler._notifications_listening[project.id]
|
||||
else:
|
||||
log.warning("Skip project closing, another client is listening for project informations")
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}",
|
||||
description="Delete a project from disk",
|
||||
parameters={
|
||||
"project_id": "The UUID of the project",
|
||||
},
|
||||
status_codes={
|
||||
204: "Changes have been written on disk",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
def delete(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
yield from project.delete()
|
||||
pm.remove_project(project.id)
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/notifications",
|
||||
description="Receive notifications about the projects",
|
||||
parameters={
|
||||
"project_id": "The UUID of the project",
|
||||
},
|
||||
status_codes={
|
||||
200: "End of stream",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
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()
|
||||
# Very important: do not send a content lenght otherwise QT close the connection but curl can consume the Feed
|
||||
response.content_length = None
|
||||
|
||||
response.start(request)
|
||||
queue = project.get_listen_queue()
|
||||
ProjectHandler._notifications_listening.setdefault(project.id, 0)
|
||||
ProjectHandler._notifications_listening[project.id] += 1
|
||||
response.write("{}\n".format(json.dumps(ProjectHandler._getPingMessage())).encode("utf-8"))
|
||||
while True:
|
||||
try:
|
||||
(action, msg) = yield from 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)
|
||||
response.write(("{}\n".format(msg)).encode("utf-8"))
|
||||
except asyncio.futures.CancelledError as e:
|
||||
break
|
||||
except asyncio.futures.TimeoutError:
|
||||
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
|
||||
|
||||
@classmethod
|
||||
def _getPingMessage(cls):
|
||||
"""
|
||||
The ping message is regulary send to the client to
|
||||
keep the connection open. We send with it some informations
|
||||
about server load.
|
||||
|
||||
:returns: hash
|
||||
"""
|
||||
stats = {}
|
||||
# Non blocking call in order to get cpu usage. First call will return 0
|
||||
stats["cpu_usage_percent"] = psutil.cpu_percent(interval=None)
|
||||
stats["memory_usage_percent"] = psutil.virtual_memory().percent
|
||||
return {"action": "ping", "event": stats}
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/files",
|
||||
description="List files of a project",
|
||||
parameters={
|
||||
"project_id": "The UUID of the project",
|
||||
},
|
||||
status_codes={
|
||||
200: "Return list of files",
|
||||
404: "The project doesn't exist"
|
||||
},
|
||||
output=PROJECT_FILE_LIST_SCHEMA)
|
||||
def list_files(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
files = yield from project.list_files()
|
||||
response.json(files)
|
||||
response.set_status(200)
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/files/{path:.+}",
|
||||
description="Get a file of a project",
|
||||
parameters={
|
||||
"project_id": "The UUID of the project",
|
||||
},
|
||||
status_codes={
|
||||
200: "Return the file",
|
||||
403: "Permission denied",
|
||||
404: "The file doesn't exist"
|
||||
})
|
||||
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)
|
||||
|
||||
response.content_type = "application/octet-stream"
|
||||
response.set_status(200)
|
||||
response.enable_chunked_encoding()
|
||||
# Very important: do not send a content length otherwise QT close the connection but curl can consume the Feed
|
||||
response.content_length = None
|
||||
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
response.start(request)
|
||||
while True:
|
||||
data = f.read(4096)
|
||||
if not data:
|
||||
break
|
||||
yield from response.write(data)
|
||||
|
||||
except FileNotFoundError:
|
||||
raise aiohttp.web.HTTPNotFound()
|
||||
except PermissionError:
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/files/{path:.+}",
|
||||
description="Get a file of a project",
|
||||
parameters={
|
||||
"project_id": "The UUID of the project",
|
||||
},
|
||||
raw=True,
|
||||
status_codes={
|
||||
200: "Return the file",
|
||||
403: "Permission denied",
|
||||
404: "The path doesn't exist"
|
||||
})
|
||||
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:
|
||||
with open(path, 'wb+') as f:
|
||||
while True:
|
||||
packet = yield from request.content.read(512)
|
||||
if not packet:
|
||||
break
|
||||
f.write(packet)
|
||||
|
||||
except FileNotFoundError:
|
||||
raise aiohttp.web.HTTPNotFound()
|
||||
except PermissionError:
|
||||
raise aiohttp.web.HTTPForbidden()
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/export",
|
||||
description="Export a project as a portable archive",
|
||||
parameters={
|
||||
"project_id": "The UUID of the project",
|
||||
},
|
||||
raw=True,
|
||||
status_codes={
|
||||
200: "Return the file",
|
||||
404: "The project doesn't exist"
|
||||
})
|
||||
def export_project(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
response.content_type = 'application/gns3z'
|
||||
response.headers['CONTENT-DISPOSITION'] = 'attachment; filename="{}.gns3z"'.format(project.name)
|
||||
response.enable_chunked_encoding()
|
||||
# Very important: do not send a content length otherwise QT close the connection but curl can consume the Feed
|
||||
response.content_length = None
|
||||
response.start(request)
|
||||
|
||||
for data in project.export(include_images=bool(request.GET.get("images", "0"))):
|
||||
response.write(data)
|
||||
yield from response.drain()
|
||||
|
||||
yield from response.write_eof()
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/import",
|
||||
description="Import a project from a portable archive",
|
||||
parameters={
|
||||
"project_id": "The UUID of the project",
|
||||
},
|
||||
raw=True,
|
||||
output=PROJECT_OBJECT_SCHEMA,
|
||||
status_codes={
|
||||
200: "Project imported",
|
||||
403: "You are not allowed to modify this property"
|
||||
})
|
||||
def import_project(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project_id = request.match_info["project_id"]
|
||||
project = pm.create_project(project_id=project_id)
|
||||
|
||||
# We write the content to a temporary location
|
||||
# and after extract all. It could be more optimal to stream
|
||||
# this but it's not implemented in Python.
|
||||
#
|
||||
# Spooled mean the file is temporary keep in ram until max_size
|
||||
try:
|
||||
with tempfile.SpooledTemporaryFile(max_size=10000) as temp:
|
||||
while True:
|
||||
packet = yield from request.content.read(512)
|
||||
if not packet:
|
||||
break
|
||||
temp.write(packet)
|
||||
project.import_zip(temp, gns3vm=bool(request.GET.get("gns3vm", "1")))
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPInternalServerError(text="Could not import the project: {}".format(e))
|
||||
|
||||
response.json(project)
|
||||
response.set_status(201)
|
||||
@@ -0,0 +1,390 @@
|
||||
# -*- 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
|
||||
|
||||
from aiohttp.web import HTTPConflict
|
||||
from ....web.route import Route
|
||||
from ....compute.project_manager import ProjectManager
|
||||
from ....schemas.nio import NIO_SCHEMA
|
||||
from ....schemas.qemu import QEMU_CREATE_SCHEMA
|
||||
from ....schemas.qemu import QEMU_UPDATE_SCHEMA
|
||||
from ....schemas.qemu import QEMU_OBJECT_SCHEMA
|
||||
from ....schemas.qemu import QEMU_BINARY_FILTER_SCHEMA
|
||||
from ....schemas.qemu import QEMU_BINARY_LIST_SCHEMA
|
||||
from ....schemas.qemu import QEMU_CAPABILITY_LIST_SCHEMA
|
||||
from ....schemas.qemu import QEMU_IMAGE_CREATE_SCHEMA
|
||||
from ....schemas.vm import VM_LIST_IMAGES_SCHEMA
|
||||
from ....compute.qemu import Qemu
|
||||
from ....config import Config
|
||||
|
||||
|
||||
class QEMUHandler:
|
||||
|
||||
"""
|
||||
API entry points for QEMU.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/vms",
|
||||
parameters={
|
||||
"project_id": "UUID for the project"
|
||||
},
|
||||
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)
|
||||
def create(request, response):
|
||||
|
||||
qemu = Qemu.instance()
|
||||
vm = yield from qemu.create_vm(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.pop("vm_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"),
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/qemu/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
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_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/qemu/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
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)
|
||||
def update(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_vm(request.match_info["vm_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)
|
||||
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/qemu/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Delete a Qemu VM instance")
|
||||
def delete(request, response):
|
||||
|
||||
yield from Qemu.instance().delete_vm(request.match_info["vm_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/vms/{vm_id}/start",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
200: "Instance started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a Qemu VM instance",
|
||||
output=QEMU_OBJECT_SCHEMA)
|
||||
def start(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
if sys.platform.startswith("linux") and qemu_manager.config.get_section_config("Qemu").getboolean("enable_kvm", True) \
|
||||
and "-no-kvm" not in vm.options:
|
||||
pm = ProjectManager.instance()
|
||||
if pm.check_hardware_virtualization(vm) is False:
|
||||
raise HTTPConflict(text="Cannot start VM with KVM enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox")
|
||||
yield from vm.start()
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/vms/{vm_id}/stop",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a Qemu VM instance")
|
||||
def stop(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/vms/{vm_id}/reload",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a Qemu VM instance")
|
||||
def reload(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.reload()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/vms/{vm_id}/suspend",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance suspended",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Suspend a Qemu VM instance")
|
||||
def suspend(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.suspend()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/vms/{vm_id}/resume",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance resumed",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Resume a Qemu VM instance")
|
||||
def resume(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.resume()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"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)
|
||||
def create_nio(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
nio_type = request.json["type"]
|
||||
if nio_type not in ("nio_udp", "nio_tap", "nio_nat"):
|
||||
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
nio = qemu_manager.create_nio(vm.qemu_path, request.json)
|
||||
yield from vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/qemu/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"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")
|
||||
def delete_nio(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vm = qemu_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@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)
|
||||
def list_binaries(request, response):
|
||||
|
||||
binaries = yield from Qemu.binary_list(request.json.get("archs", None))
|
||||
response.json(binaries)
|
||||
|
||||
@classmethod
|
||||
@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)
|
||||
def list_img_binaries(request, response):
|
||||
|
||||
binaries = yield from 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
|
||||
)
|
||||
def get_capabilities(request, response):
|
||||
capabilities = {"kvm": []}
|
||||
kvms = yield from Qemu.get_kvm_archs()
|
||||
if kvms:
|
||||
capabilities["kvm"] = kvms
|
||||
response.json(capabilities)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/qemu/img",
|
||||
status_codes={
|
||||
201: "Image created",
|
||||
},
|
||||
description="Create a Qemu image",
|
||||
input=QEMU_IMAGE_CREATE_SCHEMA
|
||||
)
|
||||
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
|
||||
|
||||
yield from Qemu.instance().create_disk(qemu_img, path, request.json)
|
||||
response.set_status(201)
|
||||
|
||||
@Route.get(
|
||||
r"/qemu/vms",
|
||||
status_codes={
|
||||
200: "List of Qemu images retrieved",
|
||||
},
|
||||
description="Retrieve the list of Qemu images",
|
||||
output=VM_LIST_IMAGES_SCHEMA)
|
||||
def list_vms(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
vms = yield from qemu_manager.list_images()
|
||||
response.set_status(200)
|
||||
response.json(vms)
|
||||
|
||||
@Route.post(
|
||||
r"/qemu/vms/{path:.+}",
|
||||
status_codes={
|
||||
204: "Image uploaded",
|
||||
},
|
||||
raw=True,
|
||||
description="Upload Qemu image.")
|
||||
def upload_vm(request, response):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
yield from qemu_manager.write_image(request.match_info["path"], request.content)
|
||||
response.set_status(204)
|
||||
@@ -0,0 +1,51 @@
|
||||
# -*- 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 ....web.route import Route
|
||||
from ....config import Config
|
||||
from ....schemas.version import VERSION_SCHEMA
|
||||
from ....version import __version__
|
||||
from aiohttp.web import HTTPConflict
|
||||
|
||||
|
||||
class VersionHandler:
|
||||
|
||||
@classmethod
|
||||
@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})
|
||||
|
||||
@classmethod
|
||||
@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 {} differs with server version {}".format(request.json["version"], __version__))
|
||||
response.json({"version": __version__})
|
||||
@@ -0,0 +1,374 @@
|
||||
# -*- 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 ....web.route import Route
|
||||
from ....schemas.nio import NIO_SCHEMA
|
||||
from ....schemas.virtualbox import VBOX_CREATE_SCHEMA
|
||||
from ....schemas.virtualbox import VBOX_UPDATE_SCHEMA
|
||||
from ....schemas.virtualbox import VBOX_OBJECT_SCHEMA
|
||||
from ....schemas.vm import VM_CAPTURE_SCHEMA
|
||||
from ....compute.virtualbox import VirtualBox
|
||||
from ....compute.project_manager import ProjectManager
|
||||
|
||||
|
||||
class VirtualBoxHandler:
|
||||
|
||||
"""
|
||||
API entry points for VirtualBox.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/virtualbox/vms",
|
||||
status_codes={
|
||||
200: "Success",
|
||||
},
|
||||
description="Get all VirtualBox VMs available")
|
||||
def index(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vms = yield from vbox_manager.list_images()
|
||||
response.json(vms)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/vms",
|
||||
parameters={
|
||||
"project_id": "UUID for the project"
|
||||
},
|
||||
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)
|
||||
def create(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = yield from vbox_manager.create_vm(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("vm_id"),
|
||||
request.json.pop("vmname"),
|
||||
request.json.pop("linked_clone"),
|
||||
console=request.json.get("console", None),
|
||||
adapters=request.json.get("adapters", 0))
|
||||
|
||||
if "enable_remote_console" in request.json:
|
||||
yield from vm.set_enable_remote_console(request.json.pop("enable_remote_console"))
|
||||
|
||||
if "ram" in request.json:
|
||||
ram = request.json.pop("ram")
|
||||
if ram != vm.ram:
|
||||
yield from vm.set_ram(ram)
|
||||
|
||||
for name, value in request.json.items():
|
||||
if name != "vm_id":
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
response.set_status(201)
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/virtualbox/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
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_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/virtualbox/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
200: "Instance updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Update a VirtualBox VM instance",
|
||||
input=VBOX_UPDATE_SCHEMA,
|
||||
output=VBOX_OBJECT_SCHEMA)
|
||||
def update(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
|
||||
if "vmname" in request.json:
|
||||
vmname = request.json.pop("vmname")
|
||||
if vmname != vm.vmname:
|
||||
yield from vm.set_vmname(vmname)
|
||||
|
||||
if "enable_remote_console" in request.json:
|
||||
yield from vm.set_enable_remote_console(request.json.pop("enable_remote_console"))
|
||||
|
||||
if "adapters" in request.json:
|
||||
adapters = int(request.json.pop("adapters"))
|
||||
if adapters != vm.adapters:
|
||||
yield from vm.set_adapters(adapters)
|
||||
|
||||
if "ram" in request.json:
|
||||
ram = request.json.pop("ram")
|
||||
if ram != vm.ram:
|
||||
yield from vm.set_ram(ram)
|
||||
|
||||
for name, value in request.json.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/virtualbox/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Delete a VirtualBox VM instance")
|
||||
def delete(request, response):
|
||||
|
||||
# check the project_id exists
|
||||
ProjectManager.instance().get_project(request.match_info["project_id"])
|
||||
|
||||
yield from VirtualBox.instance().delete_vm(request.match_info["vm_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/vms/{vm_id}/start",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a VirtualBox VM instance")
|
||||
def start(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
if (yield from 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)")
|
||||
yield from vm.start()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/vms/{vm_id}/stop",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a VirtualBox VM instance")
|
||||
def stop(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/vms/{vm_id}/suspend",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance suspended",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Suspend a VirtualBox VM instance")
|
||||
def suspend(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.suspend()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/vms/{vm_id}/resume",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance resumed",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Resume a suspended VirtualBox VM instance")
|
||||
def resume(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.resume()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/vms/{vm_id}/reload",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a VirtualBox VM instance")
|
||||
def reload(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.reload()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"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)
|
||||
def create_nio(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_vm(request.match_info["vm_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(vbox_manager.vboxmanage_path, request.json)
|
||||
yield from vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/virtualbox/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"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")
|
||||
def delete_nio(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"adapter_number": "Adapter to start a packet capture",
|
||||
"port_number": "Port on the adapter (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=VM_CAPTURE_SCHEMA)
|
||||
def start_capture(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_vm(request.match_info["vm_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"])
|
||||
yield from vm.start_capture(adapter_number, pcap_file_path)
|
||||
response.json({"pcap_file_path": pcap_file_path})
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/virtualbox/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/stop_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"adapter_number": "Adapter to stop a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
204: "Capture stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a packet capture on a VirtualBox VM instance")
|
||||
def stop_capture(request, response):
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
vm.stop_capture(int(request.match_info["adapter_number"]))
|
||||
response.set_status(204)
|
||||
@@ -0,0 +1,368 @@
|
||||
# -*- 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 ....web.route import Route
|
||||
from ....schemas.vmware import VMWARE_CREATE_SCHEMA
|
||||
from ....schemas.vmware import VMWARE_UPDATE_SCHEMA
|
||||
from ....schemas.vmware import VMWARE_OBJECT_SCHEMA
|
||||
from ....schemas.vm import VM_CAPTURE_SCHEMA
|
||||
from ....schemas.nio import NIO_SCHEMA
|
||||
from ....compute.vmware import VMware
|
||||
from ....compute.project_manager import ProjectManager
|
||||
|
||||
|
||||
class VMwareHandler:
|
||||
|
||||
"""
|
||||
API entry points for VMware.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/vmware/vms",
|
||||
status_codes={
|
||||
200: "Success",
|
||||
},
|
||||
description="Get all VMware VMs available")
|
||||
def index(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vms = yield from vmware_manager.list_vms()
|
||||
response.json(vms)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/vms",
|
||||
parameters={
|
||||
"project_id": "UUID for the project"
|
||||
},
|
||||
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)
|
||||
def create(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = yield from vmware_manager.create_vm(request.json.pop("name"),
|
||||
request.match_info["project_id"],
|
||||
request.json.get("vm_id"),
|
||||
request.json.pop("vmx_path"),
|
||||
request.json.pop("linked_clone"),
|
||||
console=request.json.get("console", None))
|
||||
|
||||
for name, value in request.json.items():
|
||||
if name != "vm_id":
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
setattr(vm, name, value)
|
||||
|
||||
response.set_status(201)
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/vmware/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
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_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/vmware/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
200: "Instance updated",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Update a VMware VM instance",
|
||||
input=VMWARE_UPDATE_SCHEMA,
|
||||
output=VMWARE_OBJECT_SCHEMA)
|
||||
def update(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_vm(request.match_info["vm_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)
|
||||
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/vmware/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Delete a VMware VM instance")
|
||||
def delete(request, response):
|
||||
|
||||
# check the project_id exists
|
||||
ProjectManager.instance().get_project(request.match_info["project_id"])
|
||||
yield from VMware.instance().delete_vm(request.match_info["vm_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/vms/{vm_id}/start",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a VMware VM instance")
|
||||
def start(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_vm(request.match_info["vm_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)")
|
||||
yield from vm.start()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/vms/{vm_id}/stop",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a VMware VM instance")
|
||||
def stop(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/vms/{vm_id}/suspend",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance suspended",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Suspend a VMware VM instance")
|
||||
def suspend(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.suspend()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/vms/{vm_id}/resume",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance resumed",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Resume a suspended VMware VM instance")
|
||||
def resume(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.resume()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/vms/{vm_id}/reload",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a VMware VM instance")
|
||||
def reload(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.reload()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"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)
|
||||
def create_nio(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
nio_type = request.json["type"]
|
||||
if nio_type not in ("nio_udp", "nio_vmnet", "nio_nat"):
|
||||
raise HTTPConflict(text="NIO of type {} is not supported".format(nio_type))
|
||||
nio = vmware_manager.create_nio(None, request.json)
|
||||
yield from vm.adapter_add_nio_binding(int(request.match_info["adapter_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/vmware/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"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")
|
||||
def delete_nio(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.adapter_remove_nio_binding(int(request.match_info["adapter_number"]))
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/start_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"adapter_number": "Adapter to start a packet capture",
|
||||
"port_number": "Port on the adapter (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=VM_CAPTURE_SCHEMA)
|
||||
def start_capture(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_vm(request.match_info["vm_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"])
|
||||
yield from vm.start_capture(adapter_number, pcap_file_path)
|
||||
response.json({"pcap_file_path": pcap_file_path})
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/stop_capture",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"adapter_number": "Adapter to stop a packet capture",
|
||||
"port_number": "Port on the adapter (always 0)"
|
||||
},
|
||||
status_codes={
|
||||
204: "Capture stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist",
|
||||
},
|
||||
description="Stop a packet capture on a VMware VM instance")
|
||||
def stop_capture(request, response):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
adapter_number = int(request.match_info["adapter_number"])
|
||||
yield from vm.stop_capture(adapter_number)
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vmware/vms/{vm_id}/interfaces/vmnet",
|
||||
parameters={
|
||||
"project_id": "The UUID of the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
},
|
||||
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_vm(request.match_info["vm_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})
|
||||
@@ -0,0 +1,230 @@
|
||||
# -*- 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 aiohttp.web import HTTPConflict
|
||||
from ....web.route import Route
|
||||
from ....schemas.nio import NIO_SCHEMA
|
||||
from ....schemas.vpcs import VPCS_CREATE_SCHEMA
|
||||
from ....schemas.vpcs import VPCS_UPDATE_SCHEMA
|
||||
from ....schemas.vpcs import VPCS_OBJECT_SCHEMA
|
||||
from ....compute.vpcs import VPCS
|
||||
|
||||
|
||||
class VPCSHandler:
|
||||
|
||||
"""
|
||||
API entry points for VPCS.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/vms",
|
||||
parameters={
|
||||
"project_id": "UUID for the project"
|
||||
},
|
||||
status_codes={
|
||||
201: "Instance created",
|
||||
400: "Invalid request",
|
||||
409: "Conflict"
|
||||
},
|
||||
description="Create a new VPCS instance",
|
||||
input=VPCS_CREATE_SCHEMA,
|
||||
output=VPCS_OBJECT_SCHEMA)
|
||||
def create(request, response):
|
||||
|
||||
vpcs = VPCS.instance()
|
||||
vm = yield from vpcs.create_vm(request.json["name"],
|
||||
request.match_info["project_id"],
|
||||
request.json.get("vm_id"),
|
||||
console=request.json.get("console"),
|
||||
startup_script=request.json.get("startup_script"))
|
||||
response.set_status(201)
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.get(
|
||||
r"/projects/{project_id}/vpcs/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
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_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.put(
|
||||
r"/projects/{project_id}/vpcs/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
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_vm(request.match_info["vm_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.startup_script = request.json.get("startup_script", vm.startup_script)
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/vpcs/vms/{vm_id}",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance deleted",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Delete a VPCS instance")
|
||||
def delete(request, response):
|
||||
|
||||
yield from VPCS.instance().delete_vm(request.match_info["vm_id"])
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/vms/{vm_id}/start",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance started",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Start a VPCS instance",
|
||||
output=VPCS_OBJECT_SCHEMA)
|
||||
def start(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.start()
|
||||
response.json(vm)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/vms/{vm_id}/stop",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance"
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance stopped",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Stop a VPCS instance")
|
||||
def stop(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.stop()
|
||||
response.set_status(204)
|
||||
|
||||
@classmethod
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/vms/{vm_id}/reload",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
},
|
||||
status_codes={
|
||||
204: "Instance reloaded",
|
||||
400: "Invalid request",
|
||||
404: "Instance doesn't exist"
|
||||
},
|
||||
description="Reload a VPCS instance")
|
||||
def reload(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
yield from vm.reload()
|
||||
response.set_status(204)
|
||||
|
||||
@Route.post(
|
||||
r"/projects/{project_id}/vpcs/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"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)
|
||||
def create_nio(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_vm(request.match_info["vm_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))
|
||||
nio = vpcs_manager.create_nio(vm.vpcs_path, request.json)
|
||||
vm.port_add_nio_binding(int(request.match_info["port_number"]), nio)
|
||||
response.set_status(201)
|
||||
response.json(nio)
|
||||
|
||||
@classmethod
|
||||
@Route.delete(
|
||||
r"/projects/{project_id}/vpcs/vms/{vm_id}/adapters/{adapter_number:\d+}/ports/{port_number:\d+}/nio",
|
||||
parameters={
|
||||
"project_id": "UUID for the project",
|
||||
"vm_id": "UUID for the instance",
|
||||
"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")
|
||||
def delete_nio(request, response):
|
||||
|
||||
vpcs_manager = VPCS.instance()
|
||||
vm = vpcs_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
|
||||
vm.port_remove_nio_binding(int(request.match_info["port_number"]))
|
||||
response.set_status(204)
|
||||
Reference in new issue
Block a user