2020-10-02 09:37:50 +03:00
#
# Copyright (C) 2020 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/>.
"""
2020-11-19 06:51:03 +02:00
API routes for Qemu nodes .
2020-10-02 09:37:50 +03:00
"""
import os
2021-04-18 09:10:38 +03:00
from fastapi import APIRouter , WebSocket , Depends , Body , Path , status
2020-10-02 09:37:50 +03:00
from fastapi . encoders import jsonable_encoder
from fastapi . responses import StreamingResponse
from uuid import UUID
2020-10-31 07:32:21 +02:00
from gns3server import schemas
2020-10-02 09:37:50 +03:00
from gns3server . compute . project_manager import ProjectManager
from gns3server . compute . qemu import Qemu
2020-10-14 03:19:29 +03:00
from gns3server . compute . qemu . qemu_vm import QemuVM
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
responses = { 404 : { " model " : schemas . ErrorMessage , " description " : " Could not find project or Qemu node " } }
2020-10-14 03:19:29 +03:00
2021-04-13 09:49:56 +03:00
router = APIRouter ( responses = responses )
2020-10-14 03:19:29 +03:00
2021-04-18 09:10:38 +03:00
def dep_node ( project_id : UUID , node_id : UUID ) - > QemuVM :
2020-10-14 03:19:29 +03:00
"""
Dependency to retrieve a node .
"""
qemu_manager = Qemu . instance ( )
node = qemu_manager . get_node ( str ( node_id ) , project_id = str ( project_id ) )
return node
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.post (
" " ,
response_model = schemas . Qemu ,
status_code = status . HTTP_201_CREATED ,
responses = { 409 : { " model " : schemas . ErrorMessage , " description " : " Could not create Qemu node " } } ,
)
2021-04-18 09:10:38 +03:00
async def create_qemu_node ( project_id : UUID , node_data : schemas . QemuCreate ) - > schemas . Qemu :
2020-10-02 09:37:50 +03:00
"""
Create a new Qemu node .
"""
qemu = Qemu . instance ( )
node_data = jsonable_encoder ( node_data , exclude_unset = True )
2021-04-13 12:16:50 +03:00
vm = await qemu . create_node (
node_data . pop ( " name " ) ,
str ( project_id ) ,
node_data . pop ( " node_id " , None ) ,
linked_clone = node_data . get ( " linked_clone " , True ) ,
qemu_path = node_data . pop ( " qemu_path " , None ) ,
console = node_data . pop ( " console " , None ) ,
console_type = node_data . pop ( " console_type " , " telnet " ) ,
aux = node_data . get ( " aux " ) ,
aux_type = node_data . pop ( " aux_type " , " none " ) ,
platform = node_data . pop ( " platform " , None ) ,
)
2020-10-02 09:37:50 +03:00
for name , value in node_data . items ( ) :
if hasattr ( vm , name ) and getattr ( vm , name ) != value :
setattr ( vm , name , value )
2021-04-17 17:04:28 +03:00
return vm . asdict ( )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.get ( " / {node_id} " , response_model = schemas . Qemu )
2021-04-18 09:10:38 +03:00
def get_qemu_node ( node : QemuVM = Depends ( dep_node ) ) - > schemas . Qemu :
2020-10-02 09:37:50 +03:00
"""
Return a Qemu node .
"""
2021-04-17 17:04:28 +03:00
return node . asdict ( )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.put ( " / {node_id} " , response_model = schemas . Qemu )
2021-04-18 09:10:38 +03:00
async def update_qemu_node ( node_data : schemas . QemuUpdate , node : QemuVM = Depends ( dep_node ) ) - > schemas . Qemu :
2020-10-02 09:37:50 +03:00
"""
Update a Qemu node .
"""
node_data = jsonable_encoder ( node_data , exclude_unset = True )
# update the console first to avoid issue if updating console type
2020-10-14 03:19:29 +03:00
node . console = node_data . pop ( " console " , node . console )
2020-10-02 09:37:50 +03:00
for name , value in node_data . items ( ) :
2020-10-14 03:19:29 +03:00
if hasattr ( node , name ) and getattr ( node , name ) != value :
await node . update_property ( name , value )
node . updated ( )
2021-04-17 17:04:28 +03:00
return node . asdict ( )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.delete ( " / {node_id} " , status_code = status . HTTP_204_NO_CONTENT )
2021-04-18 09:10:38 +03:00
async def delete_qemu_node ( node : QemuVM = Depends ( dep_node ) ) - > None :
2020-10-02 09:37:50 +03:00
"""
Delete a Qemu node .
"""
2020-10-14 03:19:29 +03:00
await Qemu . instance ( ) . delete_node ( node . id )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.post ( " / {node_id} /duplicate " , response_model = schemas . Qemu , status_code = status . HTTP_201_CREATED )
2021-04-18 09:10:38 +03:00
async def duplicate_qemu_node (
destination_node_id : UUID = Body ( . . . , embed = True ) ,
node : QemuVM = Depends ( dep_node )
) - > schemas . Qemu :
2020-10-02 09:37:50 +03:00
"""
Duplicate a Qemu node .
"""
2020-10-14 03:19:29 +03:00
new_node = await Qemu . instance ( ) . duplicate_node ( node . id , str ( destination_node_id ) )
2021-04-17 17:04:28 +03:00
return new_node . asdict ( )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.post ( " / {node_id} /resize_disk " , status_code = status . HTTP_204_NO_CONTENT )
2021-04-18 09:10:38 +03:00
async def resize_qemu_node_disk ( node_data : schemas . QemuDiskResize , node : QemuVM = Depends ( dep_node ) ) - > None :
2020-10-02 09:37:50 +03:00
2020-10-14 03:19:29 +03:00
await node . resize_disk ( node_data . drive_name , node_data . extend )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.post ( " / {node_id} /start " , status_code = status . HTTP_204_NO_CONTENT )
2021-04-18 09:10:38 +03:00
async def start_qemu_node ( node : QemuVM = Depends ( dep_node ) ) - > None :
2020-10-02 09:37:50 +03:00
"""
Start a Qemu node .
"""
qemu_manager = Qemu . instance ( )
2021-04-12 10:32:23 +03:00
hardware_accel = qemu_manager . config . settings . Qemu . enable_hardware_acceleration
2020-10-14 03:19:29 +03:00
if hardware_accel and " -no-kvm " not in node . options and " -no-hax " not in node . options :
2020-10-02 09:37:50 +03:00
pm = ProjectManager . instance ( )
2020-10-14 03:19:29 +03:00
if pm . check_hardware_virtualization ( node ) is False :
2021-04-13 12:16:50 +03:00
pass # FIXME: check this
# raise ComputeError("Cannot start VM with hardware acceleration (KVM/HAX) enabled because hardware virtualization (VT-x/AMD-V) is already used by another software like VMware or VirtualBox")
2020-10-14 03:19:29 +03:00
await node . start ( )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.post ( " / {node_id} /stop " , status_code = status . HTTP_204_NO_CONTENT )
2021-04-18 09:10:38 +03:00
async def stop_qemu_node ( node : QemuVM = Depends ( dep_node ) ) - > None :
2020-10-02 09:37:50 +03:00
"""
Stop a Qemu node .
"""
2020-10-14 03:19:29 +03:00
await node . stop ( )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.post ( " / {node_id} /reload " , status_code = status . HTTP_204_NO_CONTENT )
2021-04-18 09:10:38 +03:00
async def reload_qemu_node ( node : QemuVM = Depends ( dep_node ) ) - > None :
2020-10-02 09:37:50 +03:00
"""
Reload a Qemu node .
"""
2020-10-14 03:19:29 +03:00
await node . reload ( )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.post ( " / {node_id} /suspend " , status_code = status . HTTP_204_NO_CONTENT )
2021-04-18 09:10:38 +03:00
async def suspend_qemu_node ( node : QemuVM = Depends ( dep_node ) ) - > None :
2020-10-02 09:37:50 +03:00
"""
Suspend a Qemu node .
"""
2020-10-14 03:19:29 +03:00
await node . suspend ( )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.post ( " / {node_id} /resume " , status_code = status . HTTP_204_NO_CONTENT )
2021-04-18 09:10:38 +03:00
async def resume_qemu_node ( node : QemuVM = Depends ( dep_node ) ) - > None :
2020-10-02 09:37:50 +03:00
"""
Resume a Qemu node .
"""
2020-10-14 03:19:29 +03:00
await node . resume ( )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.post (
" / {node_id} /adapters/ {adapter_number} /ports/ {port_number} /nio " ,
status_code = status . HTTP_201_CREATED ,
response_model = schemas . UDPNIO ,
)
async def create_qemu_node_nio (
2021-04-18 09:10:38 +03:00
* ,
adapter_number : int ,
port_number : int = Path ( . . . , ge = 0 , le = 0 ) ,
nio_data : schemas . UDPNIO ,
node : QemuVM = Depends ( dep_node )
) - > schemas . UDPNIO :
2020-10-02 09:37:50 +03:00
"""
Add a NIO ( Network Input / Output ) to the node .
The port number on the Qemu node is always 0.
"""
2020-10-14 03:19:29 +03:00
nio = Qemu . instance ( ) . create_nio ( jsonable_encoder ( nio_data , exclude_unset = True ) )
await node . adapter_add_nio_binding ( adapter_number , nio )
2021-04-17 17:04:28 +03:00
return nio . asdict ( )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.put (
" / {node_id} /adapters/ {adapter_number} /ports/ {port_number} /nio " ,
status_code = status . HTTP_201_CREATED ,
response_model = schemas . UDPNIO ,
)
async def update_qemu_node_nio (
2021-04-18 09:10:38 +03:00
* ,
adapter_number : int ,
port_number : int = Path ( . . . , ge = 0 , le = 0 ) ,
nio_data : schemas . UDPNIO ,
node : QemuVM = Depends ( dep_node )
) - > schemas . UDPNIO :
2020-10-02 09:37:50 +03:00
"""
Update a NIO ( Network Input / Output ) on the node .
The port number on the Qemu node is always 0.
"""
2020-10-14 03:19:29 +03:00
nio = node . get_nio ( adapter_number )
2020-10-02 09:37:50 +03:00
if nio_data . filters :
nio . filters = nio_data . filters
if nio_data . suspend :
nio . suspend = nio_data . suspend
2020-10-14 03:19:29 +03:00
await node . adapter_update_nio_binding ( adapter_number , nio )
2021-04-17 17:04:28 +03:00
return nio . asdict ( )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.delete ( " / {node_id} /adapters/ {adapter_number} /ports/ {port_number} /nio " , status_code = status . HTTP_204_NO_CONTENT )
2021-04-18 09:10:38 +03:00
async def delete_qemu_node_nio (
adapter_number : int ,
port_number : int = Path ( . . . , ge = 0 , le = 0 ) ,
node : QemuVM = Depends ( dep_node )
) - > None :
2020-10-02 09:37:50 +03:00
"""
Delete a NIO ( Network Input / Output ) from the node .
The port number on the Qemu node is always 0.
"""
2020-10-14 03:19:29 +03:00
await node . adapter_remove_nio_binding ( adapter_number )
2020-10-02 09:37:50 +03:00
2021-04-13 09:49:56 +03:00
@router.post ( " / {node_id} /adapters/ {adapter_number} /ports/ {port_number} /capture/start " )
2021-04-13 12:16:50 +03:00
async def start_qemu_node_capture (
2021-04-18 09:10:38 +03:00
* ,
adapter_number : int ,
port_number : int = Path ( . . . , ge = 0 , le = 0 ) ,
node_capture_data : schemas . NodeCapture ,
node : QemuVM = Depends ( dep_node )
) - > dict :
2020-10-02 09:37:50 +03:00
"""
Start a packet capture on the node .
The port number on the Qemu node is always 0.
"""
2020-10-14 03:19:29 +03:00
pcap_file_path = os . path . join ( node . project . capture_working_directory ( ) , node_capture_data . capture_file_name )
await node . start_capture ( adapter_number , pcap_file_path )
2020-10-02 09:37:50 +03:00
return { " pcap_file_path " : str ( pcap_file_path ) }
2021-04-13 12:16:50 +03:00
@router.post (
" / {node_id} /adapters/ {adapter_number} /ports/ {port_number} /capture/stop " , status_code = status . HTTP_204_NO_CONTENT
)
2021-04-18 09:10:38 +03:00
async def stop_qemu_node_capture (
adapter_number : int ,
port_number : int = Path ( . . . , ge = 0 , le = 0 ) ,
node : QemuVM = Depends ( dep_node )
) - > None :
2020-10-02 09:37:50 +03:00
"""
Stop a packet capture on the node .
The port number on the Qemu node is always 0.
"""
2020-10-14 03:19:29 +03:00
await node . stop_capture ( adapter_number )
2020-10-02 09:37:50 +03:00
2021-04-13 09:49:56 +03:00
@router.get ( " / {node_id} /adapters/ {adapter_number} /ports/ {port_number} /capture/stream " )
2021-04-18 09:10:38 +03:00
async def stream_pcap_file (
adapter_number : int ,
port_number : int = Path ( . . . , ge = 0 , le = 0 ) ,
node : QemuVM = Depends ( dep_node ) ) - > StreamingResponse :
2020-10-02 09:37:50 +03:00
"""
Stream the pcap capture file .
The port number on the Qemu node is always 0.
"""
2020-10-14 03:19:29 +03:00
nio = node . get_nio ( adapter_number )
stream = Qemu . instance ( ) . stream_pcap_file ( nio , node . project . id )
2020-10-02 09:37:50 +03:00
return StreamingResponse ( stream , media_type = " application/vnd.tcpdump.pcap " )
2020-10-19 07:30:41 +03:00
@router.websocket ( " / {node_id} /console/ws " )
2021-04-18 09:10:38 +03:00
async def console_ws ( websocket : WebSocket , node : QemuVM = Depends ( dep_node ) ) - > None :
2020-10-19 07:30:41 +03:00
"""
Console WebSocket .
"""
await node . start_websocket_console ( websocket )
2020-10-02 09:37:50 +03:00
2021-04-13 12:16:50 +03:00
@router.post ( " / {node_id} /console/reset " , status_code = status . HTTP_204_NO_CONTENT )
2021-04-18 09:10:38 +03:00
async def reset_console ( node : QemuVM = Depends ( dep_node ) ) - > None :
2020-10-19 07:30:41 +03:00
await node . reset_console ( )