From 5341ccdbd6c04932fb577a359f2168d25df51a60 Mon Sep 17 00:00:00 2001 From: grossmj Date: Wed, 14 Oct 2020 10:49:29 +1030 Subject: [PATCH] Use dependencies and group common HTTP responses in endpoints. --- .../endpoints/compute/atm_switch_nodes.py | 100 +++--- gns3server/endpoints/compute/cloud_nodes.py | 106 ++++--- gns3server/endpoints/compute/compute.py | 4 +- gns3server/endpoints/compute/docker_nodes.py | 177 +++++------ .../endpoints/compute/dynamips_nodes.py | 179 +++++------ .../endpoints/compute/ethernet_hub_nodes.py | 102 ++++--- .../compute/ethernet_switch_nodes.py | 105 ++++--- .../compute/frame_relay_switch_nodes.py | 102 ++++--- gns3server/endpoints/compute/images.py | 2 +- gns3server/endpoints/compute/iou_nodes.py | 170 +++++------ gns3server/endpoints/compute/nat_nodes.py | 106 ++++--- gns3server/endpoints/compute/projects.py | 61 ++-- gns3server/endpoints/compute/qemu_nodes.py | 171 +++++------ .../endpoints/compute/virtualbox_nodes.py | 191 ++++++------ gns3server/endpoints/compute/vmware_nodes.py | 172 +++++------ gns3server/endpoints/compute/vpcs_nodes.py | 155 +++++----- gns3server/endpoints/controller/__init__.py | 8 +- gns3server/endpoints/controller/appliances.py | 7 +- gns3server/endpoints/controller/computes.py | 59 ++-- gns3server/endpoints/controller/controller.py | 7 +- gns3server/endpoints/controller/drawings.py | 41 ++- gns3server/endpoints/controller/gns3vm.py | 19 +- gns3server/endpoints/controller/links.py | 133 ++++---- gns3server/endpoints/controller/nodes.py | 289 +++++++++--------- gns3server/endpoints/controller/projects.py | 178 ++++++----- gns3server/endpoints/controller/snapshots.py | 62 ++-- gns3server/endpoints/controller/symbols.py | 14 +- gns3server/endpoints/controller/templates.py | 42 ++- 28 files changed, 1376 insertions(+), 1386 deletions(-) diff --git a/gns3server/endpoints/compute/atm_switch_nodes.py b/gns3server/endpoints/compute/atm_switch_nodes.py index 8471ea2e2..cf3621fec 100644 --- a/gns3server/endpoints/compute/atm_switch_nodes.py +++ b/gns3server/endpoints/compute/atm_switch_nodes.py @@ -21,16 +21,31 @@ API endpoints for ATM switch nodes. import os -from fastapi import APIRouter, Body, status +from fastapi import APIRouter, Depends, Body, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID from gns3server.endpoints import schemas from gns3server.compute.dynamips import Dynamips +from gns3server.compute.dynamips.nodes.atm_switch import ATMSwitch router = APIRouter() +responses = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or ATM switch node"} +} + + +async def dep_node(project_id: UUID, node_id: UUID): + """ + Dependency to retrieve a node. + """ + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) + return node + @router.post("/", response_model=schemas.ATMSwitch, @@ -54,40 +69,36 @@ async def create_atm_switch(project_id: UUID, node_data: schemas.ATMSwitchCreate @router.get("/{node_id}", response_model=schemas.ATMSwitch, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def get_atm_switch(project_id: UUID, node_id: UUID): + responses=responses) +def get_atm_switch(node: ATMSwitch = Depends(dep_node)): """ Return an ATM switch node. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) return node.__json__() @router.post("/{node_id}/duplicate", response_model=schemas.ATMSwitch, status_code=status.HTTP_201_CREATED, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def duplicate_atm_switch(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)): + responses=responses) +async def duplicate_atm_switch(destination_node_id: UUID = Body(..., embed=True), node: ATMSwitch = Depends(dep_node)): """ Duplicate an ATM switch node. """ - new_node = await Dynamips.instance().duplicate_node(str(node_id), str(destination_node_id)) + new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) return new_node.__json__() @router.put("/{node_id}", response_model=schemas.ATMSwitch, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_atm_switch(project_id: UUID, node_id: UUID, node_data: schemas.ATMSwitchUpdate): + responses=responses) +async def update_atm_switch(node_data: schemas.ATMSwitchUpdate, node: ATMSwitch = Depends(dep_node)): """ Update an ATM switch node. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) if "name" in node_data and node.name != node_data["name"]: await node.set_name(node_data["name"]) @@ -99,94 +110,93 @@ async def update_atm_switch(project_id: UUID, node_id: UUID, node_data: schemas. @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_atm_switch_node(project_id: UUID, node_id: UUID): + responses=responses) +async def delete_atm_switch_node(node: ATMSwitch = Depends(dep_node)): """ Delete an ATM switch node. """ - dynamips_manager = Dynamips.instance() - await dynamips_manager.delete_node(str(node_id)) + await Dynamips.instance().delete_node(node.id) @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def start_atm_switch(project_id: UUID, node_id: UUID): + responses=responses) +def start_atm_switch(node: ATMSwitch = Depends(dep_node)): """ Start an ATM switch node. This endpoint results in no action since ATM switch nodes are always on. """ - Dynamips.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def stop_atm_switch(project_id: UUID, node_id: UUID): + responses=responses) +def stop_atm_switch(node: ATMSwitch = Depends(dep_node)): """ Stop an ATM switch node. This endpoint results in no action since ATM switch nodes are always on. """ - Dynamips.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def suspend_atm_switch(project_id: UUID, node_id: UUID): + responses=responses) +def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)): """ Suspend an ATM switch node. This endpoint results in no action since ATM switch nodes are always on. """ - Dynamips.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def create_nio(adapter_number: int, + port_number: int, + nio_data: schemas.UDPNIO, + node: ATMSwitch = Depends(dep_node)): """ Add a NIO (Network Input/Output) to the node. The adapter number on the switch is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - nio = await dynamips_manager.create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) + nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) await node.add_nio(nio, port_number) return nio.__json__() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)): """ Remove a NIO (Network Input/Output) from the node. The adapter number on the switch is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) nio = await node.remove_nio(port_number) await nio.delete() @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture): + responses=responses) +async def start_capture(adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: ATMSwitch = Depends(dep_node)): """ Start a packet capture on the node. The adapter number on the switch is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name) await node.start_capture(port_number, pcap_file_path, node_capture_data.data_link_type) return {"pcap_file_path": pcap_file_path} @@ -194,28 +204,24 @@ async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, po @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stop_capture(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)): """ Stop a packet capture on the node. The adapter number on the switch is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) await node.stop_capture(port_number) @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stream_pcap_file(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)): """ Stream the pcap capture file. The adapter number on the switch is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) nio = node.get_nio(port_number) - stream = dynamips_manager.stream_pcap_file(nio, node.project.id) + stream = Dynamips.instance().stream_pcap_file(nio, node.project.id) return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") diff --git a/gns3server/endpoints/compute/cloud_nodes.py b/gns3server/endpoints/compute/cloud_nodes.py index 6e29fa31f..2fb0713ba 100644 --- a/gns3server/endpoints/compute/cloud_nodes.py +++ b/gns3server/endpoints/compute/cloud_nodes.py @@ -21,7 +21,7 @@ API endpoints for cloud nodes. import os -from fastapi import APIRouter, status +from fastapi import APIRouter, Depends, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from typing import Union @@ -29,9 +29,24 @@ from uuid import UUID from gns3server.endpoints import schemas from gns3server.compute.builtin import Builtin +from gns3server.compute.builtin.nodes.cloud import Cloud router = APIRouter() +responses = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or cloud node"} +} + + +def dep_node(project_id: UUID, node_id: UUID): + """ + Dependency to retrieve a node. + """ + + builtin_manager = Builtin.instance() + node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) + return node + @router.post("/", response_model=schemas.Cloud, @@ -61,27 +76,23 @@ async def create_cloud(project_id: UUID, node_data: schemas.CloudCreate): @router.get("/{node_id}", response_model=schemas.Cloud, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def get_cloud(project_id: UUID, node_id: UUID): + responses=responses) +def get_cloud(node: Cloud = Depends(dep_node)): """ Return a cloud node. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) return node.__json__() @router.put("/{node_id}", response_model=schemas.Cloud, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def update_cloud(project_id: UUID, node_id: UUID, node_data: schemas.CloudUpdate): + responses=responses) +def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)): """ Update a cloud node. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) for name, value in node_data.items(): if hasattr(node, name) and getattr(node, name) != value: @@ -92,69 +103,64 @@ def update_cloud(project_id: UUID, node_id: UUID, node_data: schemas.CloudUpdate @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_node(project_id: UUID, node_id: UUID): + responses=responses) +async def delete_node(node: Cloud = Depends(dep_node)): """ Delete a cloud node. """ - builtin_manager = Builtin.instance() - await builtin_manager.delete_node(str(node_id)) + await Builtin.instance().delete_node(node.id) @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_cloud(project_id: UUID, node_id: UUID): + responses=responses) +async def start_cloud(node: Cloud = Depends(dep_node)): """ Start a cloud node. """ - node = Builtin.instance().get_node(str(node_id), project_id=str(project_id)) await node.start() @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_cloud(project_id: UUID, node_id: UUID): + responses=responses) +async def stop_cloud(node: Cloud = Depends(dep_node)): """ Stop a cloud node. This endpoint results in no action since cloud nodes cannot be stopped. """ - Builtin.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def suspend_cloud(project_id: UUID, node_id: UUID): + responses=responses) +async def suspend_cloud(node: Cloud = Depends(dep_node)): """ Suspend a cloud node. This endpoint results in no action since cloud nodes cannot be suspended. """ - Builtin.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def create_nio(project_id: UUID, - node_id: UUID, - adapter_number: int, + responses=responses) +async def create_nio(adapter_number: int, port_number: int, - nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]): + nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], + node: Cloud = Depends(dep_node)): """ Add a NIO (Network Input/Output) to the node. The adapter number on the cloud is always 0. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) - nio = builtin_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True)) + nio = Builtin.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) await node.add_nio(nio, port_number) return nio.__json__() @@ -162,19 +168,16 @@ async def create_nio(project_id: UUID, @router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_nio(project_id: UUID, - node_id: UUID, - adapter_number: int, + responses=responses) +async def update_nio(adapter_number: int, port_number: int, - nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]): + nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], + node: Cloud = Depends(dep_node)): """ Update a NIO (Network Input/Output) to the node. The adapter number on the cloud is always 0. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) nio = node.get_nio(port_number) if nio_data.filters: nio.filters = nio_data.filters @@ -184,28 +187,27 @@ async def update_nio(project_id: UUID, @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def delete_nio(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)): """ Remove a NIO (Network Input/Output) from the node. The adapter number on the cloud is always 0. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) await node.remove_nio(port_number) @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture): + responses=responses) +async def start_capture(adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: Cloud = Depends(dep_node)): """ Start a packet capture on the node. The adapter number on the cloud is always 0. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name) await node.start_capture(port_number, pcap_file_path, node_capture_data.data_link_type) return {"pcap_file_path": pcap_file_path} @@ -213,28 +215,24 @@ async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, po @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stop_capture(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)): """ Stop a packet capture on the node. The adapter number on the cloud is always 0. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) await node.stop_capture(port_number) @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stream_pcap_file(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)): """ Stream the pcap capture file. The adapter number on the cloud is always 0. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) nio = node.get_nio(port_number) - stream = builtin_manager.stream_pcap_file(nio, node.project.id) + stream = Builtin.instance().stream_pcap_file(nio, node.project.id) return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") diff --git a/gns3server/endpoints/compute/compute.py b/gns3server/endpoints/compute/compute.py index 2b4a1d7e3..225c9bd95 100644 --- a/gns3server/endpoints/compute/compute.py +++ b/gns3server/endpoints/compute/compute.py @@ -124,13 +124,13 @@ def statistics() -> dict: @router.get("/qemu/binaries") -async def list_binaries(archs: Optional[List[str]] = Body(None, embed=True)): +async def get_binaries(archs: Optional[List[str]] = Body(None, embed=True)): return await Qemu.binary_list(archs) @router.get("/qemu/img-binaries") -async def list_img_binaries(): +async def get_img_binaries(): return await Qemu.img_binary_list() diff --git a/gns3server/endpoints/compute/docker_nodes.py b/gns3server/endpoints/compute/docker_nodes.py index f6014a822..21a8cbcc9 100644 --- a/gns3server/endpoints/compute/docker_nodes.py +++ b/gns3server/endpoints/compute/docker_nodes.py @@ -21,16 +21,31 @@ API endpoints for Docker nodes. import os -from fastapi import APIRouter, Body, status +from fastapi import APIRouter, Depends, Body, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID from gns3server.endpoints import schemas from gns3server.compute.docker import Docker +from gns3server.compute.docker.docker_vm import DockerVM router = APIRouter() +responses = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or Docker node"} +} + + +def dep_node(project_id: UUID, node_id: UUID): + """ + Dependency to retrieve a node. + """ + + docker_manager = Docker.instance() + node = docker_manager.get_node(str(node_id), project_id=str(project_id)) + return node + @router.post("/", response_model=schemas.Docker, @@ -71,28 +86,23 @@ async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate): @router.get("/{node_id}", response_model=schemas.Docker, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def get_docker_node(project_id: UUID, node_id: UUID): + responses=responses) +def get_docker_node(node: DockerVM = Depends(dep_node)): """ Return a Docker node. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - return container.__json__() + return node.__json__() @router.put("/{node_id}", response_model=schemas.Docker, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_docker(project_id: UUID, node_id: UUID, node_data: schemas.DockerUpdate): + responses=responses) +async def update_docker(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)): """ Update a Docker node. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - props = [ "name", "console", "console_type", "aux", "aux_type", "console_resolution", "console_http_port", "console_http_path", "start_command", @@ -103,222 +113,201 @@ async def update_docker(project_id: UUID, node_id: UUID, node_data: schemas.Dock changed = False node_data = jsonable_encoder(node_data, exclude_unset=True) for prop in props: - if prop in node_data and node_data[prop] != getattr(container, prop): - setattr(container, prop, node_data[prop]) + if prop in node_data and node_data[prop] != getattr(node, prop): + setattr(node, prop, node_data[prop]) changed = True # We don't call container.update for nothing because it will restart the container if changed: - await container.update() - container.updated() - return container.__json__() + await node.update() + node.updated() + return node.__json__() @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_docker_node(project_id: UUID, node_id: UUID): + responses=responses) +async def start_docker_node(node: DockerVM = Depends(dep_node)): """ Start a Docker node. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - await container.start() + await node.start() @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_docker_node(project_id: UUID, node_id: UUID): + responses=responses) +async def stop_docker_node(node: DockerVM = Depends(dep_node)): """ Stop a Docker node. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - await container.stop() + await node.stop() @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def suspend_docker_node(project_id: UUID, node_id: UUID): + responses=responses) +async def suspend_docker_node(node: DockerVM = Depends(dep_node)): """ Suspend a Docker node. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - await container.pause() + await node.pause() @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reload_docker_node(project_id: UUID, node_id: UUID): + responses=responses) +async def reload_docker_node(node: DockerVM = Depends(dep_node)): """ Reload a Docker node. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - await container.restart() + await node.restart() @router.post("/{node_id}/pause", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def pause_docker_node(project_id: UUID, node_id: UUID): + responses=responses) +async def pause_docker_node(node: DockerVM = Depends(dep_node)): """ Pause a Docker node. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - await container.pause() + await node.pause() @router.post("/{node_id}/unpause", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def unpause_docker_node(project_id: UUID, node_id: UUID): + responses=responses) +async def unpause_docker_node(node: DockerVM = Depends(dep_node)): """ Unpause a Docker node. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - await container.unpause() + await node.unpause() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_docker_node(project_id: UUID, node_id: UUID): + responses=responses) +async def delete_docker_node(node: DockerVM = Depends(dep_node)): """ Delete a Docker node. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - await container.delete() + await node.delete() @router.post("/{node_id}/duplicate", response_model=schemas.Docker, status_code=status.HTTP_201_CREATED, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def duplicate_docker_node(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)): + responses=responses) +async def duplicate_docker_node(destination_node_id: UUID = Body(..., embed=True), node: DockerVM = Depends(dep_node)): """ Duplicate a Docker node. """ - docker_manager = Docker.instance() - new_node = await docker_manager.duplicate_node(str(node_id), str(destination_node_id)) + new_node = await Docker.instance().duplicate_node(node.id, str(destination_node_id)) return new_node.__json__() @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def create_nio(adapter_number: int, + port_number: int, + nio_data: schemas.UDPNIO, + node: DockerVM = Depends(dep_node)): """ Add a NIO (Network Input/Output) to the node. The port number on the Docker node is always 0. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - nio = docker_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True)) - await container.adapter_add_nio_binding(adapter_number, nio) + nio = Docker.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) + await node.adapter_add_nio_binding(adapter_number, nio) return nio.__json__() @router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def update_nio(adapter_number: int, + port_number: int, nio_data: schemas.UDPNIO, + node: DockerVM = Depends(dep_node)): """ Update a NIO (Network Input/Output) on the node. The port number on the Docker node is always 0. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - nio = container.get_nio(adapter_number) + nio = node.get_nio(adapter_number) if nio_data.filters: nio.filters = nio_data.filters - await container.adapter_update_nio_binding(adapter_number, nio) + await node.adapter_update_nio_binding(adapter_number, nio) return nio.__json__() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def delete_nio(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)): """ Delete a NIO (Network Input/Output) from the node. The port number on the Docker node is always 0. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - await container.adapter_remove_nio_binding(adapter_number) + await node.adapter_remove_nio_binding(adapter_number) @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture): + responses=responses) +async def start_capture(adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: DockerVM = Depends(dep_node)): """ Start a packet capture on the node. The port number on the Docker node is always 0. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - pcap_file_path = os.path.join(container.project.capture_working_directory(), node_capture_data.capture_file_name) - await container.start_capture(adapter_number, pcap_file_path) + 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) return {"pcap_file_path": str(pcap_file_path)} @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stop_capture(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)): """ Stop a packet capture on the node. The port number on the Docker node is always 0. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - await container.stop_capture(adapter_number) + await node.stop_capture(adapter_number) @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reset_console(project_id: UUID, node_id: UUID): + responses=responses) +async def reset_console(node: DockerVM = Depends(dep_node)): - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - await container.reset_console() + await node.reset_console() @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stream_pcap_file(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)): """ Stream the pcap capture file. The port number on the Docker node is always 0. """ - docker_manager = Docker.instance() - container = docker_manager.get_node(str(node_id), project_id=str(project_id)) - nio = container.get_nio(adapter_number) - stream = docker_manager.stream_pcap_file(nio, container.project.id) + nio = node.get_nio(adapter_number) + stream = Docker.instance().stream_pcap_file(nio, node.project.id) return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") # @Route.get( diff --git a/gns3server/endpoints/compute/dynamips_nodes.py b/gns3server/endpoints/compute/dynamips_nodes.py index e7dc84c7b..86db81311 100644 --- a/gns3server/endpoints/compute/dynamips_nodes.py +++ b/gns3server/endpoints/compute/dynamips_nodes.py @@ -22,19 +22,23 @@ API endpoints for Dynamips nodes. import os import sys -from fastapi import APIRouter, status +from fastapi import APIRouter, Depends, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from typing import List from uuid import UUID from gns3server.compute.dynamips import Dynamips +from gns3server.compute.dynamips.nodes.router import Router from gns3server.compute.dynamips.dynamips_error import DynamipsError -from gns3server.compute.project_manager import ProjectManager from gns3server.endpoints import schemas router = APIRouter() +responses = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"} +} + DEFAULT_CHASSIS = { "c1700": "1720", "c2600": "2610", @@ -42,6 +46,16 @@ DEFAULT_CHASSIS = { } +def dep_node(project_id: UUID, node_id: UUID): + """ + Dependency to retrieve a node. + """ + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) + return node + + @router.post("/", response_model=schemas.Dynamips, status_code=status.HTTP_201_CREATED, @@ -74,169 +88,148 @@ async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate): @router.get("/{node_id}", response_model=schemas.Dynamips, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def get_router(project_id: UUID, node_id: UUID): + responses=responses) +def get_router(node: Router = Depends(dep_node)): """ Return Dynamips router. """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - return vm.__json__() + return node.__json__() @router.put("/{node_id}", response_model=schemas.Dynamips, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_router(project_id: UUID, node_id: UUID, node_data: schemas.DynamipsUpdate): + responses=responses) +async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depends(dep_node)): """ Update a Dynamips router. """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - await dynamips_manager.update_vm_settings(vm, jsonable_encoder(node_data, exclude_unset=True)) - vm.updated() - return vm.__json__() + await Dynamips.instance().update_vm_settings(node, jsonable_encoder(node_data, exclude_unset=True)) + node.updated() + return node.__json__() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_router(project_id: UUID, node_id: UUID): + responses=responses) +async def delete_router(node: Router = Depends(dep_node)): """ Delete a Dynamips router. """ - # check the project_id exists - ProjectManager.instance().get_project(str(project_id)) - await Dynamips.instance().delete_node(str(node_id)) + await Dynamips.instance().delete_node(node.id) @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_router(project_id: UUID, node_id: UUID): + responses=responses) +async def start_router(node: Router = Depends(dep_node)): """ Start a Dynamips router. """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) try: - await dynamips_manager.ghost_ios_support(vm) + await Dynamips.instance().ghost_ios_support(node) except GeneratorExit: pass - await vm.start() + await node.start() @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_router(project_id: UUID, node_id: UUID): + responses=responses) +async def stop_router(node: Router = Depends(dep_node)): """ Stop a Dynamips router. """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.stop() + await node.stop() @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def suspend_router(project_id: UUID, node_id: UUID): + responses=responses) +async def suspend_router(node: Router = Depends(dep_node)): - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.suspend() + await node.suspend() @router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def resume_router(project_id: UUID, node_id: UUID): + responses=responses) +async def resume_router(node: Router = Depends(dep_node)): """ Resume a suspended Dynamips router. """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.resume() + await node.resume() @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reload(project_id: UUID, node_id: UUID): + responses=responses) +async def reload_router(node: Router = Depends(dep_node)): """ Reload a suspended Dynamips router. """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.reload() + await node.reload() @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: Router = Depends(dep_node)): """ Add a NIO (Network Input/Output) to the node. """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - nio = await dynamips_manager.create_nio(vm, jsonable_encoder(nio_data, exclude_unset=True)) - await vm.slot_add_nio_binding(adapter_number, port_number, nio) + nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) + await node.slot_add_nio_binding(adapter_number, port_number, nio) return nio.__json__() @router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: Router = Depends(dep_node)): """ Update a NIO (Network Input/Output) on the node. """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vm.get_nio(adapter_number, port_number) + nio = node.get_nio(adapter_number, port_number) if nio_data.filters: nio.filters = nio_data.filters - await vm.slot_update_nio_binding(adapter_number, port_number, nio) + await node.slot_update_nio_binding(adapter_number, port_number, nio) return nio.__json__() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def delete_nio(adapter_number: int, port_number: int, node: Router = Depends(dep_node)): """ Delete a NIO (Network Input/Output) from the node. """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - nio = await vm.slot_remove_nio_binding(adapter_number, port_number) + nio = await node.slot_remove_nio_binding(adapter_number, port_number) await nio.delete() @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture): + responses=responses) +async def start_capture(adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: Router = Depends(dep_node)): """ Start a packet capture on the node. """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - pcap_file_path = os.path.join(vm.project.capture_working_directory(), node_capture_data.capture_file_name) + pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name) if sys.platform.startswith('win'): # FIXME: Dynamips (Cygwin actually) doesn't like non ascii paths on Windows @@ -245,72 +238,64 @@ async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, po except UnicodeEncodeError: raise DynamipsError('The capture file path "{}" must only contain ASCII (English) characters'.format(pcap_file_path)) - await vm.start_capture(adapter_number, port_number, pcap_file_path, node_capture_data.data_link_type) + await node.start_capture(adapter_number, port_number, pcap_file_path, node_capture_data.data_link_type) return {"pcap_file_path": pcap_file_path} @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stop_capture(adapter_number: int, port_number: int, node: Router = Depends(dep_node)): """ Stop a packet capture on the node. """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.stop_capture(adapter_number, port_number) + await node.stop_capture(adapter_number, port_number) @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stream_pcap_file(adapter_number: int, port_number: int, node: Router = Depends(dep_node)): """ Stream the pcap capture file. """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vm.get_nio(adapter_number, port_number) - stream = dynamips_manager.stream_pcap_file(nio, vm.project.id) + nio = node.get_nio(adapter_number, port_number) + stream = Dynamips.instance().stream_pcap_file(nio, node.project.id) return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") @router.get("/{node_id}/idlepc_proposals", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def get_idlepcs(project_id: UUID, node_id: UUID) -> List[str]: + responses=responses) +async def get_idlepcs(node: Router = Depends(dep_node)) -> List[str]: """ Retrieve Dynamips idle-pc proposals """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.set_idlepc("0x0") - return await vm.get_idle_pc_prop() + await node.set_idlepc("0x0") + return await node.get_idle_pc_prop() @router.get("/{node_id}/auto_idlepc", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def get_auto_idlepc(project_id: UUID, node_id: UUID) -> dict: + responses=responses) +async def get_auto_idlepc(node: Router = Depends(dep_node)) -> dict: """ Get an automatically guessed best idle-pc value. """ - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - idlepc = await dynamips_manager.auto_idlepc(vm) + idlepc = await Dynamips.instance().auto_idlepc(node) return {"idlepc": idlepc} @router.post("/{node_id}/duplicate", status_code=status.HTTP_201_CREATED, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def duplicate_router(project_id: UUID, node_id: UUID, destination_node_id: UUID): + responses=responses) +async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep_node)): """ Duplicate a router. """ - new_node = await Dynamips.instance().duplicate_node(str(node_id), str(destination_node_id)) + new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) return new_node.__json__() @@ -330,9 +315,7 @@ async def duplicate_router(project_id: UUID, node_id: UUID, destination_node_id: @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reset_console(project_id: UUID, node_id: UUID): + responses=responses) +async def reset_console(node: Router = Depends(dep_node)): - dynamips_manager = Dynamips.instance() - vm = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.reset_console() + await node.reset_console() diff --git a/gns3server/endpoints/compute/ethernet_hub_nodes.py b/gns3server/endpoints/compute/ethernet_hub_nodes.py index 4b65d0a46..796de758d 100644 --- a/gns3server/endpoints/compute/ethernet_hub_nodes.py +++ b/gns3server/endpoints/compute/ethernet_hub_nodes.py @@ -21,16 +21,31 @@ API endpoints for Ethernet hub nodes. import os -from fastapi import APIRouter, Body, status +from fastapi import APIRouter, Depends, Body, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID from gns3server.compute.dynamips import Dynamips +from gns3server.compute.dynamips.nodes.ethernet_hub import EthernetHub from gns3server.endpoints import schemas router = APIRouter() +responses = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or Ethernet hub node"} +} + + +def dep_node(project_id: UUID, node_id: UUID): + """ + Dependency to retrieve a node. + """ + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) + return node + @router.post("/", response_model=schemas.EthernetHub, @@ -54,40 +69,37 @@ async def create_ethernet_hub(project_id: UUID, node_data: schemas.EthernetHubCr @router.get("/{node_id}", response_model=schemas.EthernetHub, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def get_ethernet_hub(project_id: UUID, node_id: UUID): + responses=responses) +def get_ethernet_hub(node: EthernetHub = Depends(dep_node)): """ Return an Ethernet hub. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) return node.__json__() @router.post("/{node_id}/duplicate", response_model=schemas.EthernetHub, status_code=status.HTTP_201_CREATED, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def duplicate_ethernet_hub(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)): + responses=responses) +async def duplicate_ethernet_hub(destination_node_id: UUID = Body(..., embed=True), + node: EthernetHub = Depends(dep_node)): """ Duplicate an Ethernet hub. """ - new_node = await Dynamips.instance().duplicate_node(str(node_id), str(destination_node_id)) + new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) return new_node.__json__() @router.put("/{node_id}", response_model=schemas.EthernetHub, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_ethernet_hub(project_id: UUID, node_id: UUID, node_data: schemas.EthernetHubUpdate): + responses=responses) +async def update_ethernet_hub(node_data: schemas.EthernetHubUpdate, node: EthernetHub = Depends(dep_node)): """ Update an Ethernet hub. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) if "name" in node_data and node.name != node_data["name"]: await node.set_name(node_data["name"]) @@ -99,94 +111,93 @@ async def update_ethernet_hub(project_id: UUID, node_id: UUID, node_data: schema @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_ethernet_hub(project_id: UUID, node_id: UUID): + responses=responses) +async def delete_ethernet_hub(node: EthernetHub = Depends(dep_node)): """ Delete an Ethernet hub. """ - dynamips_manager = Dynamips.instance() - await dynamips_manager.delete_node(str(node_id)) + await Dynamips.instance().delete_node(node.id) @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def start_ethernet_hub(project_id: UUID, node_id: UUID): + responses=responses) +def start_ethernet_hub(node: EthernetHub = Depends(dep_node)): """ Start an Ethernet hub. This endpoint results in no action since Ethernet hub nodes are always on. """ - Dynamips.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def stop_ethernet_hub(project_id: UUID, node_id: UUID): + responses=responses) +def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)): """ Stop an Ethernet hub. This endpoint results in no action since Ethernet hub nodes are always on. """ - Dynamips.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def suspend_ethernet_hub(project_id: UUID, node_id: UUID): + responses=responses) +def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)): """ Suspend an Ethernet hub. This endpoint results in no action since Ethernet hub nodes are always on. """ - Dynamips.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def create_nio(adapter_number: int, + port_number: int, + nio_data: schemas.UDPNIO, + node: EthernetHub = Depends(dep_node)): """ Add a NIO (Network Input/Output) to the node. The adapter number on the hub is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - nio = await dynamips_manager.create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) + nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) await node.add_nio(nio, port_number) return nio.__json__() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def delete_nio(adapter_number: int, port_number: int, node: EthernetHub = Depends(dep_node)): """ Delete a NIO (Network Input/Output) from the node. The adapter number on the hub is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) nio = await node.remove_nio(port_number) await nio.delete() @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture): + responses=responses) +async def start_capture(adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: EthernetHub = Depends(dep_node)): """ Start a packet capture on the node. The adapter number on the hub is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name) await node.start_capture(port_number, pcap_file_path, node_capture_data.data_link_type) return {"pcap_file_path": pcap_file_path} @@ -194,29 +205,24 @@ async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, po @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stop_capture(adapter_number: int, port_number: int, node: EthernetHub = Depends(dep_node)): """ Stop a packet capture on the node. The adapter number on the hub is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) await node.stop_capture(port_number) @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stream_pcap_file(adapter_number: int, port_number: int, node: EthernetHub = Depends(dep_node)): """ Stream the pcap capture file. The adapter number on the hub is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) nio = node.get_nio(port_number) - stream = dynamips_manager.stream_pcap_file(nio, node.project.id) + stream = Dynamips.instance().stream_pcap_file(nio, node.project.id) return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") - diff --git a/gns3server/endpoints/compute/ethernet_switch_nodes.py b/gns3server/endpoints/compute/ethernet_switch_nodes.py index 84b11b6c9..a11fb4da6 100644 --- a/gns3server/endpoints/compute/ethernet_switch_nodes.py +++ b/gns3server/endpoints/compute/ethernet_switch_nodes.py @@ -21,21 +21,36 @@ API endpoints for Ethernet switch nodes. import os -from fastapi import APIRouter, Body, status +from fastapi import APIRouter, Depends, Body, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID from gns3server.compute.dynamips import Dynamips +from gns3server.compute.dynamips.nodes.ethernet_switch import EthernetSwitch from gns3server.endpoints import schemas router = APIRouter() +responses = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or Ethernet switch node"} +} + + +def dep_node(project_id: UUID, node_id: UUID): + """ + Dependency to retrieve a node. + """ + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) + return node + @router.post("/", response_model=schemas.EthernetSwitch, status_code=status.HTTP_201_CREATED, - responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Ethernet hub node"}}) + responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Ethernet switch node"}}) async def create_ethernet_switch(project_id: UUID, node_data: schemas.EthernetSwitchCreate): """ Create a new Ethernet switch. @@ -57,37 +72,34 @@ async def create_ethernet_switch(project_id: UUID, node_data: schemas.EthernetSw @router.get("/{node_id}", response_model=schemas.EthernetSwitch, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def get_ethernet_switch(project_id: UUID, node_id: UUID): + responses=responses) +def get_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) return node.__json__() @router.post("/{node_id}/duplicate", response_model=schemas.EthernetSwitch, status_code=status.HTTP_201_CREATED, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def duplicate_ethernet_switch(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)): + responses=responses) +async def duplicate_ethernet_switch(destination_node_id: UUID = Body(..., embed=True), + node: EthernetSwitch = Depends(dep_node)): """ Duplicate an Ethernet switch. """ - new_node = await Dynamips.instance().duplicate_node(str(node_id), str(destination_node_id)) + new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) return new_node.__json__() @router.put("/{node_id}", response_model=schemas.EthernetSwitch, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_ethernet_switch(project_id: UUID, node_id: UUID, node_data: schemas.EthernetSwitchUpdate): + responses=responses) +async def update_ethernet_switch(node_data: schemas.EthernetSwitchUpdate, node: EthernetSwitch = Depends(dep_node)): """ Update an Ethernet switch. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) if "name" in node_data and node.name != node_data["name"]: await node.set_name(node_data["name"]) @@ -102,119 +114,114 @@ async def update_ethernet_switch(project_id: UUID, node_id: UUID, node_data: sch @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_ethernet_switch(project_id: UUID, node_id: UUID): + responses=responses) +async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): """ Delete an Ethernet switch. """ - dynamips_manager = Dynamips.instance() - await dynamips_manager.delete_node(str(node_id)) + await Dynamips.instance().delete_node(node.id) @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def start_ethernet_switch(project_id: UUID, node_id: UUID): + responses=responses) +def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): """ Start an Ethernet switch. This endpoint results in no action since Ethernet switch nodes are always on. """ - Dynamips.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def stop_ethernet_switch(project_id: UUID, node_id: UUID): + responses=responses) +def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): """ Stop an Ethernet switch. This endpoint results in no action since Ethernet switch nodes are always on. """ - Dynamips.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def suspend(project_id: UUID, node_id: UUID): + responses=responses) +def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)): """ Suspend an Ethernet switch. This endpoint results in no action since Ethernet switch nodes are always on. """ - Dynamips.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def create_nio(adapter_number: int, + port_number: int, + nio_data: schemas.UDPNIO, + node: EthernetSwitch = Depends(dep_node)): - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - nio = await dynamips_manager.create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) + nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) await node.add_nio(nio, port_number) return nio.__json__() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def delete_nio(adapter_number: int, port_number: int, node: EthernetSwitch = Depends(dep_node)): """ Delete a NIO (Network Input/Output) from the node. The adapter number on the switch is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) nio = await node.remove_nio(port_number) await nio.delete() @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture): + responses=responses) +async def start_capture(adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: EthernetSwitch = Depends(dep_node)): """ Start a packet capture on the node. The adapter number on the switch is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name) await node.start_capture(port_number, pcap_file_path, node_capture_data.data_link_type) return {"pcap_file_path": pcap_file_path} + @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stop_capture(adapter_number: int,port_number: int, node: EthernetSwitch = Depends(dep_node)): """ Stop a packet capture on the node. The adapter number on the switch is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) await node.stop_capture(port_number) @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stream_pcap_file(adapter_number: int, port_number: int, node: EthernetSwitch = Depends(dep_node)): """ Stream the pcap capture file. The adapter number on the switch is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) nio = node.get_nio(port_number) - stream = dynamips_manager.stream_pcap_file(nio, node.project.id) + stream = Dynamips.instance().stream_pcap_file(nio, node.project.id) return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") - diff --git a/gns3server/endpoints/compute/frame_relay_switch_nodes.py b/gns3server/endpoints/compute/frame_relay_switch_nodes.py index 451e896c7..fc0a18212 100644 --- a/gns3server/endpoints/compute/frame_relay_switch_nodes.py +++ b/gns3server/endpoints/compute/frame_relay_switch_nodes.py @@ -21,16 +21,31 @@ API endpoints for Frame Relay switch nodes. import os -from fastapi import APIRouter, Body, status +from fastapi import APIRouter, Depends, Body, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID from gns3server.endpoints import schemas from gns3server.compute.dynamips import Dynamips +from gns3server.compute.dynamips.nodes.frame_relay_switch import FrameRelaySwitch router = APIRouter() +responses = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or Frame Relay switch node"} +} + + +def dep_node(project_id: UUID, node_id: UUID): + """ + Dependency to retrieve a node. + """ + + dynamips_manager = Dynamips.instance() + node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) + return node + @router.post("/", response_model=schemas.FrameRelaySwitch, @@ -54,40 +69,38 @@ async def create_frame_relay_switch(project_id: UUID, node_data: schemas.FrameRe @router.get("/{node_id}", response_model=schemas.FrameRelaySwitch, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def get_frame_relay_switch(project_id: UUID, node_id: UUID): + responses=responses) +def get_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): """ Return a Frame Relay switch node. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) return node.__json__() @router.post("/{node_id}/duplicate", response_model=schemas.FrameRelaySwitch, status_code=status.HTTP_201_CREATED, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def duplicate_frame_relay_switch(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)): + responses=responses) +async def duplicate_frame_relay_switch(destination_node_id: UUID = Body(..., embed=True), + node: FrameRelaySwitch = Depends(dep_node)): """ Duplicate a Frame Relay switch node. """ - new_node = await Dynamips.instance().duplicate_node(str(node_id), str(destination_node_id)) + new_node = await Dynamips.instance().duplicate_node(node.id, str(destination_node_id)) return new_node.__json__() @router.put("/{node_id}", response_model=schemas.FrameRelaySwitch, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_frame_relay_switch(project_id: UUID, node_id: UUID, node_data: schemas.FrameRelaySwitchUpdate): + responses=responses) +async def update_frame_relay_switch(node_data: schemas.FrameRelaySwitchUpdate, + node: FrameRelaySwitch = Depends(dep_node)): """ Update an Frame Relay switch node. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) if "name" in node_data and node.name != node_data["name"]: await node.set_name(node_data["name"]) @@ -99,94 +112,93 @@ async def update_frame_relay_switch(project_id: UUID, node_id: UUID, node_data: @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_frame_relay_switch_node(project_id: UUID, node_id: UUID): + responses=responses) +async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): """ Delete a Frame Relay switch node. """ - dynamips_manager = Dynamips.instance() - await dynamips_manager.delete_node(str(node_id)) + await Dynamips.instance().delete_node(node.id) @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def start_frame_relay_switch(project_id: UUID, node_id: UUID): + responses=responses) +def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): """ Start a Frame Relay switch node. This endpoint results in no action since Frame Relay switch nodes are always on. """ - Dynamips.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def stop_frame_relay_switch(project_id: UUID, node_id: UUID): + responses=responses) +def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): """ Stop a Frame Relay switch node. This endpoint results in no action since Frame Relay switch nodes are always on. """ - Dynamips.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def suspend_frame_relay_switch(project_id: UUID, node_id: UUID): + responses=responses) +def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)): """ Suspend a Frame Relay switch node. This endpoint results in no action since Frame Relay switch nodes are always on. """ - Dynamips.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def create_nio(adapter_number: int, + port_number: int, + nio_data: schemas.UDPNIO, + node: FrameRelaySwitch = Depends(dep_node)): """ Add a NIO (Network Input/Output) to the node. The adapter number on the switch is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) - nio = await dynamips_manager.create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) + nio = await Dynamips.instance().create_nio(node, jsonable_encoder(nio_data, exclude_unset=True)) await node.add_nio(nio, port_number) return nio.__json__() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def delete_nio(adapter_number: int, port_number: int, node: FrameRelaySwitch = Depends(dep_node)): """ Remove a NIO (Network Input/Output) from the node. The adapter number on the switch is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) nio = await node.remove_nio(port_number) await nio.delete() @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture): + responses=responses) +async def start_capture(adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: FrameRelaySwitch = Depends(dep_node)): """ Start a packet capture on the node. The adapter number on the switch is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name) await node.start_capture(port_number, pcap_file_path, node_capture_data.data_link_type) return {"pcap_file_path": pcap_file_path} @@ -194,28 +206,24 @@ async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, po @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stop_capture(adapter_number: int, port_number: int, node: FrameRelaySwitch = Depends(dep_node)): """ Stop a packet capture on the node. The adapter number on the switch is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) await node.stop_capture(port_number) @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stream_pcap_file(adapter_number: int, port_number: int, node: FrameRelaySwitch = Depends(dep_node)): """ Stream the pcap capture file. The adapter number on the hub is always 0. """ - dynamips_manager = Dynamips.instance() - node = dynamips_manager.get_node(str(node_id), project_id=str(project_id)) nio = node.get_nio(port_number) - stream = dynamips_manager.stream_pcap_file(nio, node.project.id) + stream = Dynamips.instance().stream_pcap_file(nio, node.project.id) return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") diff --git a/gns3server/endpoints/compute/images.py b/gns3server/endpoints/compute/images.py index 6cd2e62bd..dc71d71b0 100644 --- a/gns3server/endpoints/compute/images.py +++ b/gns3server/endpoints/compute/images.py @@ -125,7 +125,7 @@ async def download_iou_image(filename: str): @router.get("/qemu/images") -async def list_qemu_images() -> List[str]: +async def get_qemu_images() -> List[str]: qemu_manager = Qemu.instance() return await qemu_manager.list_images() diff --git a/gns3server/endpoints/compute/iou_nodes.py b/gns3server/endpoints/compute/iou_nodes.py index c358e0d9b..3ac227776 100644 --- a/gns3server/endpoints/compute/iou_nodes.py +++ b/gns3server/endpoints/compute/iou_nodes.py @@ -21,7 +21,7 @@ API endpoints for IOU nodes. import os -from fastapi import APIRouter, Body, status +from fastapi import APIRouter, Depends, Body, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from typing import Union @@ -29,9 +29,24 @@ from uuid import UUID from gns3server.endpoints import schemas from gns3server.compute.iou import IOU +from gns3server.compute.iou.iou_vm import IOUVM router = APIRouter() +responses = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or IOU node"} +} + + +def dep_node(project_id: UUID, node_id: UUID): + """ + Dependency to retrieve a node. + """ + + iou_manager = IOU.instance() + node = iou_manager.get_node(str(node_id), project_id=str(project_id)) + return node + @router.post("/", response_model=schemas.IOU, @@ -68,229 +83,204 @@ async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate): @router.get("/{node_id}", response_model=schemas.IOU, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def get_iou_node(project_id: UUID, node_id: UUID): + responses=responses) +def get_iou_node(node: IOUVM = Depends(dep_node)): """ Return an IOU node. """ - iou_manager = IOU.instance() - vm = iou_manager.get_node(str(node_id), project_id=str(project_id)) - return vm.__json__() + return node.__json__() @router.put("/{node_id}", response_model=schemas.IOU, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_iou_node(project_id: UUID, node_id: UUID, node_data: schemas.IOUUpdate): + responses=responses) +async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(dep_node)): """ Update an IOU node. """ - iou_manager = IOU.instance() - vm = iou_manager.get_node(str(node_id), project_id=str(project_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) for name, value in node_data.items(): - if hasattr(vm, name) and getattr(vm, name) != value: + if hasattr(node, name) and getattr(node, name) != value: if name == "application_id": continue # we must ignore this to avoid overwriting the application_id allocated by the IOU manager - setattr(vm, name, value) + setattr(node, name, value) - if vm.use_default_iou_values: + if node.use_default_iou_values: # update the default IOU values in case the image or use_default_iou_values have changed # this is important to have the correct NVRAM amount in order to correctly push the configs to the NVRAM - await vm.update_default_iou_values() - vm.updated() - return vm.__json__() + await node.update_default_iou_values() + node.updated() + return node.__json__() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_iou_node(project_id: UUID, node_id: UUID): + responses=responses) +async def delete_iou_node(node: IOUVM = Depends(dep_node)): """ Delete an IOU node. """ - await IOU.instance().delete_node(str(node_id)) + await IOU.instance().delete_node(node.id) @router.post("/{node_id}/duplicate", response_model=schemas.IOU, status_code=status.HTTP_201_CREATED, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def duplicate_iou_node(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)): + responses=responses) +async def duplicate_iou_node(destination_node_id: UUID = Body(..., embed=True), node: IOUVM = Depends(dep_node)): """ Duplicate an IOU node. """ - new_node = await IOU.instance().duplicate_node(str(node_id), str(destination_node_id)) + new_node = await IOU.instance().duplicate_node(node.id, str(destination_node_id)) return new_node.__json__() @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_iou_node(project_id: UUID, node_id: UUID, start_data: schemas.IOUStart): + responses=responses) +async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)): """ Start an IOU node. """ - iou_manager = IOU.instance() - vm = iou_manager.get_node(str(node_id), project_id=str(project_id)) start_data = jsonable_encoder(start_data, exclude_unset=True) for name, value in start_data.items(): - if hasattr(vm, name) and getattr(vm, name) != value: - setattr(vm, name, value) + if hasattr(node, name) and getattr(node, name) != value: + setattr(node, name, value) - await vm.start() - return vm.__json__() + await node.start() + return node.__json__() @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop(project_id: UUID, node_id: UUID): + responses=responses) +async def stop(node: IOUVM = Depends(dep_node)): """ Stop an IOU node. """ - iou_manager = IOU.instance() - vm = iou_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.stop() + await node.stop() @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def suspend_iou_node(project_id: UUID, node_id: UUID): + responses=responses) +def suspend_iou_node(node: IOUVM = Depends(dep_node)): """ Suspend an IOU node. Does nothing since IOU doesn't support being suspended. """ - iou_manager = IOU.instance() - iou_manager.get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reload_iou_node(project_id: UUID, node_id: UUID): + responses=responses) +async def reload_iou_node(node: IOUVM = Depends(dep_node)): """ Reload an IOU node. """ - iou_manager = IOU.instance() - vm = iou_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.reload() + await node.reload() @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def create_nio(project_id: UUID, - node_id: UUID, - adapter_number: int, + responses=responses) +async def create_nio(adapter_number: int, port_number: int, - nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]): + nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], + node: IOUVM = Depends(dep_node)): """ Add a NIO (Network Input/Output) to the node. """ - iou_manager = IOU.instance() - vm = iou_manager.get_node(str(node_id), project_id=str(project_id)) - nio = iou_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True)) - await vm.adapter_add_nio_binding(adapter_number, port_number, nio) + nio = IOU.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) + await node.adapter_add_nio_binding(adapter_number, port_number, nio) return nio.__json__() @router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_nio(project_id: UUID, - node_id: UUID, - adapter_number: int, + responses=responses) +async def update_nio(adapter_number: int, port_number: int, - nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]): + nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], + node: IOUVM = Depends(dep_node)): """ Update a NIO (Network Input/Output) on the node. """ - iou_manager = IOU.instance() - vm = iou_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vm.get_nio(adapter_number, port_number) + nio = node.get_nio(adapter_number, port_number) if nio_data.filters: nio.filters = nio_data.filters - await vm.adapter_update_nio_binding(adapter_number, port_number, nio) - return nio.__json__() + await node.adapter_update_nio_binding(adapter_number, port_number, nio) return nio.__json__() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def delete_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)): """ Delete a NIO (Network Input/Output) from the node. """ - iou_manager = IOU.instance() - vm = iou_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.adapter_remove_nio_binding(adapter_number, port_number) + await node.adapter_remove_nio_binding(adapter_number, port_number) @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture): + responses=responses) +async def start_capture(adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: IOUVM = Depends(dep_node)): """ Start a packet capture on the node. """ - iou_manager = IOU.instance() - vm = iou_manager.get_node(str(node_id), project_id=str(project_id)) - pcap_file_path = os.path.join(vm.project.capture_working_directory(), node_capture_data.capture_file_name) - await vm.start_capture(adapter_number, pcap_file_path) + 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) return {"pcap_file_path": str(pcap_file_path)} @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stop_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)): """ Stop a packet capture on the node. """ - iou_manager = IOU.instance() - vm = iou_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.stop_capture(adapter_number, port_number) + await node.stop_capture(adapter_number, port_number) @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stream_pcap_file(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)): """ Stream the pcap capture file. """ - iou_manager = IOU.instance() - vm = iou_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vm.get_nio(adapter_number, port_number) - stream = iou_manager.stream_pcap_file(nio, vm.project.id) + nio = node.get_nio(adapter_number, port_number) + stream = IOU.instance().stream_pcap_file(nio, node.project.id) return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reset_console(project_id: UUID, node_id: UUID): + responses=responses) +async def reset_console(node: IOUVM = Depends(dep_node)): - iou_manager = IOU.instance() - vm = iou_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.reset_console() + await node.reset_console() # @Route.get( diff --git a/gns3server/endpoints/compute/nat_nodes.py b/gns3server/endpoints/compute/nat_nodes.py index c6826649e..2748cded7 100644 --- a/gns3server/endpoints/compute/nat_nodes.py +++ b/gns3server/endpoints/compute/nat_nodes.py @@ -21,7 +21,7 @@ API endpoints for NAT nodes. import os -from fastapi import APIRouter, status +from fastapi import APIRouter, Depends, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from typing import Union @@ -29,9 +29,24 @@ from uuid import UUID from gns3server.endpoints import schemas from gns3server.compute.builtin import Builtin +from gns3server.compute.builtin.nodes.nat import Nat router = APIRouter() +responses = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or NAT node"} +} + + +def dep_node(project_id: UUID, node_id: UUID): + """ + Dependency to retrieve a node. + """ + + builtin_manager = Builtin.instance() + node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) + return node + @router.post("/", response_model=schemas.NAT, @@ -56,27 +71,23 @@ async def create_nat(project_id: UUID, node_data: schemas.NATCreate): @router.get("/{node_id}", response_model=schemas.NAT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def get_nat(project_id: UUID, node_id: UUID): + responses=responses) +def get_nat(node: Nat = Depends(dep_node)): """ Return a NAT node. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) return node.__json__() @router.put("/{node_id}", response_model=schemas.NAT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def update_nat(project_id: UUID, node_id: UUID, node_data: schemas.NATUpdate): + responses=responses) +def update_nat(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)): """ Update a NAT node. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) for name, value in node_data.items(): if hasattr(node, name) and getattr(node, name) != value: @@ -87,69 +98,64 @@ def update_nat(project_id: UUID, node_id: UUID, node_data: schemas.NATUpdate): @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nat(project_id: UUID, node_id: UUID): + responses=responses) +async def delete_nat(node: Nat = Depends(dep_node)): """ Delete a cloud node. """ - builtin_manager = Builtin.instance() - await builtin_manager.delete_node(str(node_id)) + await Builtin.instance().delete_node(node.id) @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_nat(project_id: UUID, node_id: UUID): + responses=responses) +async def start_nat(node: Nat = Depends(dep_node)): """ Start a NAT node. """ - node = Builtin.instance().get_node(str(node_id), project_id=str(project_id)) await node.start() @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_nat(project_id: UUID, node_id: UUID): + responses=responses) +async def stop_nat(node: Nat = Depends(dep_node)): """ Stop a NAT node. This endpoint results in no action since cloud nodes cannot be stopped. """ - Builtin.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def suspend_nat(project_id: UUID, node_id: UUID): + responses=responses) +async def suspend_nat(node: Nat = Depends(dep_node)): """ Suspend a NAT node. This endpoint results in no action since NAT nodes cannot be suspended. """ - Builtin.instance().get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def create_nio(project_id: UUID, - node_id: UUID, - adapter_number: int, + responses=responses) +async def create_nio(adapter_number: int, port_number: int, - nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]): + nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], + node: Nat = Depends(dep_node)): """ Add a NIO (Network Input/Output) to the node. The adapter number on the cloud is always 0. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) - nio = builtin_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True)) + nio = Builtin.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) await node.add_nio(nio, port_number) return nio.__json__() @@ -157,19 +163,16 @@ async def create_nio(project_id: UUID, @router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_nio(project_id: UUID, - node_id: UUID, - adapter_number: int, + responses=responses) +async def update_nio(adapter_number: int, port_number: int, - nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]): + nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO], + node: Nat = Depends(dep_node)): """ Update a NIO (Network Input/Output) to the node. The adapter number on the cloud is always 0. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) nio = node.get_nio(port_number) if nio_data.filters: nio.filters = nio_data.filters @@ -179,28 +182,27 @@ async def update_nio(project_id: UUID, @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def delete_nio(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)): """ Remove a NIO (Network Input/Output) from the node. The adapter number on the cloud is always 0. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) await node.remove_nio(port_number) @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture): + responses=responses) +async def start_capture(adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: Nat = Depends(dep_node)): """ Start a packet capture on the node. The adapter number on the cloud is always 0. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name) await node.start_capture(port_number, pcap_file_path, node_capture_data.data_link_type) return {"pcap_file_path": pcap_file_path} @@ -208,28 +210,24 @@ async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, po @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stop_capture(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)): """ Stop a packet capture on the node. The adapter number on the cloud is always 0. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) await node.stop_capture(port_number) @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stream_pcap_file(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)): """ Stream the pcap capture file. The adapter number on the cloud is always 0. """ - builtin_manager = Builtin.instance() - node = builtin_manager.get_node(str(node_id), project_id=str(project_id)) nio = node.get_nio(port_number) - stream = builtin_manager.stream_pcap_file(nio, node.project.id) + stream = Builtin.instance().stream_pcap_file(nio, node.project.id) return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") diff --git a/gns3server/endpoints/compute/projects.py b/gns3server/endpoints/compute/projects.py index 8aa9f833d..76dec4c2a 100644 --- a/gns3server/endpoints/compute/projects.py +++ b/gns3server/endpoints/compute/projects.py @@ -20,30 +20,39 @@ API endpoints for projects. """ import shutil +import aiohttp +import os -from fastapi import APIRouter, HTTPException, Request, status +import logging +log = logging.getLogger() + +from fastapi import APIRouter, Depends, HTTPException, Request, status from fastapi.encoders import jsonable_encoder from fastapi.responses import FileResponse from typing import List from uuid import UUID +from gns3server.compute.project_manager import ProjectManager +from gns3server.compute.project import Project from gns3server.endpoints import schemas + router = APIRouter() -import aiohttp -import os - -from gns3server.compute.project_manager import ProjectManager - -import logging -log = logging.getLogger() - - # How many clients have subscribed to notifications _notifications_listening = {} +def dep_project(project_id: UUID): + """ + Dependency to retrieve a project. + """ + + pm = ProjectManager.instance() + project = pm.get_project(str(project_id)) + return project + + @router.get("/projects", response_model=List[schemas.Project]) def get_projects(): """ @@ -73,41 +82,35 @@ def create_project(project_data: schemas.ProjectCreate): @router.put("/projects/{project_id}", response_model=schemas.Project) -async def update_project(project_id: UUID, project_data: schemas.ProjectUpdate): +async def update_project(project_data: schemas.ProjectUpdate, project: Project = Depends(dep_project)): """ Update project on the compute. """ - pm = ProjectManager.instance() - project = pm.get_project(str(project_id)) await project.update(variables=project_data.variables) return project.__json__() @router.get("/projects/{project_id}", response_model=schemas.Project) -def get_project(project_id: UUID): +def get_project(project: Project = Depends(dep_project)): """ Return a project from the compute. """ - pm = ProjectManager.instance() - project = pm.get_project(str(project_id)) return project.__json__() @router.post("/projects/{project_id}/close", status_code=status.HTTP_204_NO_CONTENT) -async def close_project(project_id: UUID): +async def close_project(project: Project = Depends(dep_project)): """ Close a project on the compute. """ - pm = ProjectManager.instance() - project = pm.get_project(str(project_id)) if _notifications_listening.setdefault(project.id, 0) <= 1: await project.close() - pm.remove_project(project.id) + ProjectManager.instance().remove_project(project.id) try: del _notifications_listening[project.id] except KeyError: @@ -118,15 +121,13 @@ async def close_project(project_id: UUID): @router.delete("/projects/{project_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_project(project_id: UUID): +async def delete_project(project: Project = Depends(dep_project)): """ Delete project from the compute. """ - pm = ProjectManager.instance() - project = pm.get_project(str(project_id)) await project.delete() - pm.remove_project(project.id) + ProjectManager.instance().remove_project(project.id) # @Route.get( # r"/projects/{project_id}/notifications", @@ -184,24 +185,20 @@ async def delete_project(project_id: UUID): @router.get("/projects/{project_id}/files", response_model=List[schemas.ProjectFile]) -async def get_project_files(project_id: UUID): +async def get_project_files(project: Project = Depends(dep_project)): """ Return files belonging to a project. """ - pm = ProjectManager.instance() - project = pm.get_project(str(project_id)) return await project.list_files() @router.get("/projects/{project_id}/files/{file_path:path}") -async def get_file(project_id: UUID, file_path: str): +async def get_file(file_path: str, project: Project = Depends(dep_project)): """ Get a file from a project. """ - pm = ProjectManager.instance() - project = pm.get_project(str(project_id)) path = os.path.normpath(file_path) # Raise error if user try to escape @@ -217,10 +214,8 @@ async def get_file(project_id: UUID, file_path: str): @router.post("/projects/{project_id}/files/{file_path:path}", status_code=status.HTTP_204_NO_CONTENT) -async def write_file(project_id: UUID, file_path: str, request: Request): +async def write_file(file_path: str, request: Request, project: Project = Depends(dep_project)): - pm = ProjectManager.instance() - project = pm.get_project(str(project_id)) path = os.path.normpath(file_path) # Raise error if user try to escape diff --git a/gns3server/endpoints/compute/qemu_nodes.py b/gns3server/endpoints/compute/qemu_nodes.py index 57cca0a9e..f88d3391b 100644 --- a/gns3server/endpoints/compute/qemu_nodes.py +++ b/gns3server/endpoints/compute/qemu_nodes.py @@ -22,7 +22,7 @@ API endpoints for Qemu nodes. import os import sys -from fastapi import APIRouter, Body, status +from fastapi import APIRouter, Depends, Body, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -31,9 +31,24 @@ from gns3server.endpoints import schemas from gns3server.compute.project_manager import ProjectManager from gns3server.compute.compute_error import ComputeError from gns3server.compute.qemu import Qemu +from gns3server.compute.qemu.qemu_vm import QemuVM router = APIRouter() +responses = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or Qemu node"} +} + + +def dep_node(project_id: UUID, node_id: UUID): + """ + Dependency to retrieve a node. + """ + + qemu_manager = Qemu.instance() + node = qemu_manager.get_node(str(node_id), project_id=str(project_id)) + return node + @router.post("/", response_model=schemas.Qemu, @@ -66,250 +81,224 @@ async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate): @router.get("/{node_id}", response_model=schemas.Qemu, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def get_qemu_node(project_id: UUID, node_id: UUID): + responses=responses) +def get_qemu_node(node: QemuVM = Depends(dep_node)): """ Return a Qemu node. """ - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) - return vm.__json__() + return node.__json__() @router.put("/{node_id}", response_model=schemas.Qemu, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_qemu_node(project_id: UUID, node_id: UUID, node_data: schemas.QemuUpdate): + responses=responses) +async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends(dep_node)): """ Update a Qemu node. """ - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) # update the console first to avoid issue if updating console type - vm.console = node_data.pop("console", vm.console) + node.console = node_data.pop("console", node.console) for name, value in node_data.items(): - if hasattr(vm, name) and getattr(vm, name) != value: - await vm.update_property(name, value) - vm.updated() - return vm.__json__() + if hasattr(node, name) and getattr(node, name) != value: + await node.update_property(name, value) + node.updated() + return node.__json__() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_qemu_node(project_id: UUID, node_id: UUID): + responses=responses) +async def delete_qemu_node(node: QemuVM = Depends(dep_node)): """ Delete a Qemu node. """ - await Qemu.instance().delete_node(str(node_id)) + await Qemu.instance().delete_node(node.id) @router.post("/{node_id}/duplicate", response_model=schemas.Qemu, status_code=status.HTTP_201_CREATED, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def duplicate_qemu_node(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)): + responses=responses) +async def duplicate_qemu_node(destination_node_id: UUID = Body(..., embed=True), node: QemuVM = Depends(dep_node)): """ Duplicate a Qemu node. """ - new_node = await Qemu.instance().duplicate_node(str(node_id), str(destination_node_id)) + new_node = await Qemu.instance().duplicate_node(node.id, str(destination_node_id)) return new_node.__json__() @router.post("/{node_id}/resize_disk", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def resize_qemu_node_disk(project_id: UUID, node_id: UUID, node_data: schemas.QemuDiskResize): + responses=responses) +async def resize_qemu_node_disk(node_data: schemas.QemuDiskResize, node: QemuVM = Depends(dep_node)): - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.resize_disk(node_data.drive_name, node_data.extend) + await node.resize_disk(node_data.drive_name, node_data.extend) @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_qemu_node(project_id: UUID, node_id: UUID): + responses=responses) +async def start_qemu_node(node: QemuVM = Depends(dep_node)): """ Start a Qemu node. """ qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) hardware_accel = qemu_manager.config.get_section_config("Qemu").getboolean("enable_hardware_acceleration", True) if sys.platform.startswith("linux"): # the enable_kvm option was used before version 2.0 and has priority enable_kvm = qemu_manager.config.get_section_config("Qemu").getboolean("enable_kvm") if enable_kvm is not None: hardware_accel = enable_kvm - if hardware_accel and "-no-kvm" not in vm.options and "-no-hax" not in vm.options: + if hardware_accel and "-no-kvm" not in node.options and "-no-hax" not in node.options: pm = ProjectManager.instance() - if pm.check_hardware_virtualization(vm) is False: + if pm.check_hardware_virtualization(node) is False: 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") - await vm.start() + await node.start() @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_qemu_node(project_id: UUID, node_id: UUID): + responses=responses) +async def stop_qemu_node(node: QemuVM = Depends(dep_node)): """ Stop a Qemu node. """ - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.stop() + await node.stop() @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reload_qemu_node(project_id: UUID, node_id: UUID): + responses=responses) +async def reload_qemu_node(node: QemuVM = Depends(dep_node)): """ Reload a Qemu node. """ - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.reload() + await node.reload() @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def suspend_qemu_node(project_id: UUID, node_id: UUID): + responses=responses) +async def suspend_qemu_node(node: QemuVM = Depends(dep_node)): """ Suspend a Qemu node. """ - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.suspend() + await node.suspend() @router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def resume_qemu_node(project_id: UUID, node_id: UUID): + responses=responses) +async def resume_qemu_node(node: QemuVM = Depends(dep_node)): """ Resume a Qemu node. """ - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.resume() + await node.resume() @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: QemuVM = Depends(dep_node)): """ Add a NIO (Network Input/Output) to the node. The port number on the Qemu node is always 0. """ - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) - nio = qemu_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True)) - await vm.adapter_add_nio_binding(adapter_number, nio) + nio = Qemu.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) + await node.adapter_add_nio_binding(adapter_number, nio) return nio.__json__() @router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: QemuVM = Depends(dep_node)): """ Update a NIO (Network Input/Output) on the node. The port number on the Qemu node is always 0. """ - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vm.get_nio(adapter_number) + nio = node.get_nio(adapter_number) if nio_data.filters: nio.filters = nio_data.filters if nio_data.suspend: nio.suspend = nio_data.suspend - await vm.adapter_update_nio_binding(adapter_number, nio) + await node.adapter_update_nio_binding(adapter_number, nio) return nio.__json__() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def delete_nio(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)): """ Delete a NIO (Network Input/Output) from the node. The port number on the Qemu node is always 0. """ - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.adapter_remove_nio_binding(adapter_number) + await node.adapter_remove_nio_binding(adapter_number) @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture): + responses=responses) +async def start_capture(adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: QemuVM = Depends(dep_node)): """ Start a packet capture on the node. The port number on the Qemu node is always 0. """ - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) - pcap_file_path = os.path.join(vm.project.capture_working_directory(), node_capture_data.capture_file_name) - await vm.start_capture(adapter_number, pcap_file_path) + 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) return {"pcap_file_path": str(pcap_file_path)} @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stop_capture(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)): """ Stop a packet capture on the node. The port number on the Qemu node is always 0. """ - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.stop_capture(adapter_number) + await node.stop_capture(adapter_number) @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reset_console(project_id: UUID, node_id: UUID): + responses=responses) +async def reset_console(node: QemuVM = Depends(dep_node)): - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.reset_console() + await node.reset_console() @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stream_pcap_file(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)): """ Stream the pcap capture file. The port number on the Qemu node is always 0. """ - qemu_manager = Qemu.instance() - vm = qemu_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vm.get_nio(adapter_number) - stream = qemu_manager.stream_pcap_file(nio, vm.project.id) + nio = node.get_nio(adapter_number) + stream = Qemu.instance().stream_pcap_file(nio, node.project.id) return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") diff --git a/gns3server/endpoints/compute/virtualbox_nodes.py b/gns3server/endpoints/compute/virtualbox_nodes.py index e658aa3db..018908664 100644 --- a/gns3server/endpoints/compute/virtualbox_nodes.py +++ b/gns3server/endpoints/compute/virtualbox_nodes.py @@ -21,7 +21,7 @@ API endpoints for VirtualBox nodes. import os -from fastapi import APIRouter, status +from fastapi import APIRouter, Depends, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -30,9 +30,24 @@ from gns3server.endpoints import schemas from gns3server.compute.virtualbox import VirtualBox from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError from gns3server.compute.project_manager import ProjectManager +from gns3server.compute.virtualbox.virtualbox_vm import VirtualBoxVM router = APIRouter() +responses = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or VirtualBox node"} +} + + +def dep_node(project_id: UUID, node_id: UUID): + """ + Dependency to retrieve a node. + """ + + vbox_manager = VirtualBox.instance() + node = vbox_manager.get_node(str(node_id), project_id=str(project_id)) + return node + @router.post("/", response_model=schemas.VirtualBox, @@ -69,250 +84,228 @@ async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBox @router.get("/{node_id}", response_model=schemas.VirtualBox, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def get_virtualbox_node(project_id: UUID, node_id: UUID): + responses=responses) +def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): """ Return a VirtualBox node. """ - vbox_manager = VirtualBox.instance() - vm = vbox_manager.get_node(str(node_id), project_id=str(project_id)) - return vm.__json__() + return node.__json__() @router.put("/{node_id}", response_model=schemas.VirtualBox, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_virtualbox_node(project_id: UUID, node_id: UUID, node_data: schemas.VirtualBoxUpdate): + responses=responses) +async def update_virtualbox_node(node_data: schemas.VirtualBoxUpdate, node: VirtualBoxVM = Depends(dep_node)): """ Update a VirtualBox node. """ - vbox_manager = VirtualBox.instance() - vm = vbox_manager.get_node(str(node_id), project_id=str(project_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) - if "name" in node_data: name = node_data.pop("name") vmname = node_data.pop("vmname", None) - if name != vm.name: - oldname = vm.name - vm.name = name - if vm.linked_clone: + if name != node.name: + oldname = node.name + node.name = name + if node.linked_clone: try: - await vm.set_vmname(vm.name) + await node.set_vmname(node.name) except VirtualBoxError as e: # In case of error we rollback (we can't change the name when running) - vm.name = oldname - vm.updated() + node.name = oldname + node.updated() raise e if "adapters" in node_data: adapters = node_data.pop("adapters") - if adapters != vm.adapters: - await vm.set_adapters(adapters) + if adapters != node.adapters: + await node.set_adapters(adapters) if "ram" in node_data: ram = node_data.pop("ram") - if ram != vm.ram: - await vm.set_ram(ram) + if ram != node.ram: + await node.set_ram(ram) # update the console first to avoid issue if updating console type - vm.console = node_data.pop("console", vm.console) + node.console = node_data.pop("console", node.console) for name, value in node_data.items(): - if hasattr(vm, name) and getattr(vm, name) != value: - setattr(vm, name, value) + if hasattr(node, name) and getattr(node, name) != value: + setattr(node, name, value) - vm.updated() - return vm.__json__() + node.updated() + return node.__json__() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_virtualbox_node(project_id: UUID, node_id: UUID): + responses=responses) +async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): """ Delete a VirtualBox node. """ - # check the project_id exists - ProjectManager.instance().get_project(str(project_id)) - await VirtualBox.instance().delete_node(str(node_id)) + await VirtualBox.instance().delete_node(node.id) @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_virtualbox_node(project_id: UUID, node_id: UUID): + responses=responses) +async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): """ Start a VirtualBox node. """ - vbox_manager = VirtualBox.instance() - vm = vbox_manager.get_node(str(node_id), project_id=str(project_id)) - if await vm.check_hw_virtualization(): + if await node.check_hw_virtualization(): pm = ProjectManager.instance() - if pm.check_hardware_virtualization(vm) is False: - pass # FIXME: check this + if pm.check_hardware_virtualization(node) is False: + 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") - await vm.start() + await node.start() @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_virtualbox_node(project_id: UUID, node_id: UUID): + responses=responses) +async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): """ Stop a VirtualBox node. """ - vbox_manager = VirtualBox.instance() - vm = vbox_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.stop() + await node.stop() @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def suspend_virtualbox_node(project_id: UUID, node_id: UUID): + responses=responses) +async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): """ Suspend a VirtualBox node. """ - vbox_manager = VirtualBox.instance() - vm = vbox_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.suspend() + await node.suspend() @router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def resume_virtualbox_node(project_id: UUID, node_id: UUID): + responses=responses) +async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): """ Resume a VirtualBox node. """ - vbox_manager = VirtualBox.instance() - vm = vbox_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.resume() + await node.resume() @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reload_virtualbox_node(project_id: UUID, node_id: UUID): + responses=responses) +async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)): """ Reload a VirtualBox node. """ - vbox_manager = VirtualBox.instance() - vm = vbox_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.reload() + await node.reload() @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def create_nio(adapter_number: int, + port_number: int, + nio_data: schemas.UDPNIO, + node: VirtualBoxVM = Depends(dep_node)): """ Add a NIO (Network Input/Output) to the node. The port number on the VirtualBox node is always 0. """ - vbox_manager = VirtualBox.instance() - vm = vbox_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vbox_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True)) - await vm.adapter_add_nio_binding(adapter_number, nio) + nio = VirtualBox.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) + await node.adapter_add_nio_binding(adapter_number, nio) return nio.__json__() @router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def update_nio(adapter_number: int, + port_number: int, + nio_data: schemas.UDPNIO, + node: VirtualBoxVM = Depends(dep_node)): """ Update a NIO (Network Input/Output) on the node. The port number on the VirtualBox node is always 0. """ - virtualbox_manager = VirtualBox.instance() - vm = virtualbox_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vm.get_nio(adapter_number) + nio = node.get_nio(adapter_number) if nio_data.filters: nio.filters = nio_data.filters if nio_data.suspend: nio.suspend = nio_data.suspend - await vm.adapter_update_nio_binding(adapter_number, nio) + await node.adapter_update_nio_binding(adapter_number, nio) return nio.__json__() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def delete_nio(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)): """ Delete a NIO (Network Input/Output) from the node. The port number on the VirtualBox node is always 0. """ - vbox_manager = VirtualBox.instance() - vm = vbox_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.adapter_remove_nio_binding(adapter_number) + await node.adapter_remove_nio_binding(adapter_number) @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture): + responses=responses) +async def start_capture(adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: VirtualBoxVM = Depends(dep_node)): """ Start a packet capture on the node. The port number on the VirtualBox node is always 0. """ - vbox_manager = VirtualBox.instance() - vm = vbox_manager.get_node(str(node_id), project_id=str(project_id)) - pcap_file_path = os.path.join(vm.project.capture_working_directory(), node_capture_data.capture_file_name) - await vm.start_capture(adapter_number, pcap_file_path) + 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) return {"pcap_file_path": str(pcap_file_path)} @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stop_capture(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)): """ Stop a packet capture on the node. The port number on the VirtualBox node is always 0. """ - vbox_manager = VirtualBox.instance() - vm = vbox_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.stop_capture(adapter_number) + await node.stop_capture(adapter_number) @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reset_console(project_id: UUID, node_id: UUID): + responses=responses) +async def reset_console(node: VirtualBoxVM = Depends(dep_node)): - virtualbox_manager = VirtualBox.instance() - vm = virtualbox_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.reset_console() + await node.reset_console() @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stream_pcap_file(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)): """ Stream the pcap capture file. The port number on the VirtualBox node is always 0. """ - virtualbox_manager = VirtualBox.instance() - vm = virtualbox_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vm.get_nio(adapter_number) - stream = virtualbox_manager.stream_pcap_file(nio, vm.project.id) + nio = node.get_nio(adapter_number) + stream = VirtualBox.instance().stream_pcap_file(nio, node.project.id) return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") diff --git a/gns3server/endpoints/compute/vmware_nodes.py b/gns3server/endpoints/compute/vmware_nodes.py index c38f5a326..989ba70f4 100644 --- a/gns3server/endpoints/compute/vmware_nodes.py +++ b/gns3server/endpoints/compute/vmware_nodes.py @@ -21,7 +21,7 @@ API endpoints for VMware nodes. import os -from fastapi import APIRouter, status +from fastapi import APIRouter, Depends, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -29,9 +29,24 @@ from uuid import UUID from gns3server.endpoints import schemas from gns3server.compute.vmware import VMware from gns3server.compute.project_manager import ProjectManager +from gns3server.compute.vmware.vmware_vm import VMwareVM router = APIRouter() +responses = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"} +} + + +def dep_node(project_id: UUID, node_id: UUID): + """ + Dependency to retrieve a node. + """ + + vmware_manager = VMware.instance() + node = vmware_manager.get_node(str(node_id), project_id=str(project_id)) + return node + @router.post("/", response_model=schemas.VMware, @@ -62,238 +77,215 @@ async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate): @router.get("/{node_id}", response_model=schemas.VMware, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def get_vmware_node(project_id: UUID, node_id: UUID): + responses=responses) +def get_vmware_node(node: VMwareVM = Depends(dep_node)): """ Return a VMware node. """ - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) - return vm.__json__() + return node.__json__() @router.put("/{node_id}", response_model=schemas.VMware, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def update_vmware_node(project_id: UUID, node_id: UUID, node_data: schemas.VMwareUpdate): + responses=responses) +def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends(dep_node)): """ Update a VMware node. """ - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) # update the console first to avoid issue if updating console type - vm.console = node_data.pop("console", vm.console) + node.console = node_data.pop("console", node.console) for name, value in node_data.items(): - if hasattr(vm, name) and getattr(vm, name) != value: - setattr(vm, name, value) + if hasattr(node, name) and getattr(node, name) != value: + setattr(node, name, value) - vm.updated() - return vm.__json__() + node.updated() + return node.__json__() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_vmware_node(project_id: UUID, node_id: UUID): + responses=responses) +async def delete_vmware_node(node: VMwareVM = Depends(dep_node)): """ Delete a VMware node. """ - # check the project_id exists - ProjectManager.instance().get_project(str(project_id)) - await VMware.instance().delete_node(str(node_id)) + await VMware.instance().delete_node(node.id) @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_vmware_node(project_id: UUID, node_id: UUID): + responses=responses) +async def start_vmware_node(node: VMwareVM = Depends(dep_node)): """ Start a VMware node. """ - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) - if vm.check_hw_virtualization(): + if node.check_hw_virtualization(): pm = ProjectManager.instance() - if pm.check_hardware_virtualization(vm) is False: + if pm.check_hardware_virtualization(node) is False: 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") - await vm.start() + await node.start() @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_vmware_node(project_id: UUID, node_id: UUID): + responses=responses) +async def stop_vmware_node(node: VMwareVM = Depends(dep_node)): """ Stop a VMware node. """ - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.stop() + await node.stop() @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def suspend_vmware_node(project_id: UUID, node_id: UUID): + responses=responses) +async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)): """ Suspend a VMware node. """ - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.suspend() + await node.suspend() @router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def resume_vmware_node(project_id: UUID, node_id: UUID): + responses=responses) +async def resume_vmware_node(node: VMwareVM = Depends(dep_node)): """ Resume a VMware node. """ - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.resume() + await node.resume() @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reload_vmware_node(project_id: UUID, node_id: UUID): + responses=responses) +async def reload_vmware_node(node: VMwareVM = Depends(dep_node)): """ Reload a VMware node. """ - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.reload() + await node.reload() @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def create_nio(adapter_number: int, + port_number: int, + nio_data: schemas.UDPNIO, + node: VMwareVM = Depends(dep_node)): """ Add a NIO (Network Input/Output) to the node. The port number on the VMware node is always 0. """ - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vmware_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True)) - await vm.adapter_add_nio_binding(adapter_number, nio) + nio = VMware.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) + await node.adapter_add_nio_binding(adapter_number, nio) return nio.__json__() @router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def update_nio(adapter_number: int, + port_number: int, + nio_data: schemas.UDPNIO, node: VMwareVM = Depends(dep_node)): """ Update a NIO (Network Input/Output) on the node. The port number on the VMware node is always 0. """ - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vm.get_nio(adapter_number) + nio = node.get_nio(adapter_number) if nio_data.filters: nio.filters = nio_data.filters - await vm.adapter_update_nio_binding(adapter_number, nio) + await node.adapter_update_nio_binding(adapter_number, nio) return nio.__json__() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def delete_nio(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)): """ Delete a NIO (Network Input/Output) from the node. The port number on the VMware node is always 0. """ - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.adapter_remove_nio_binding(adapter_number) + await node.adapter_remove_nio_binding(adapter_number) @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture): + responses=responses) +async def start_capture(adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: VMwareVM = Depends(dep_node)): """ Start a packet capture on the node. The port number on the VMware node is always 0. """ - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) - pcap_file_path = os.path.join(vm.project.capture_working_directory(), node_capture_data.capture_file_name) - await vm.start_capture(adapter_number, pcap_file_path) + 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) return {"pcap_file_path": pcap_file_path} @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stop_capture(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)): """ Stop a packet capture on the node. The port number on the VMware node is always 0. """ - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.stop_capture(adapter_number) + await node.stop_capture(adapter_number) @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reset_console(project_id: UUID, node_id: UUID): + responses=responses) +async def reset_console(node: VMwareVM = Depends(dep_node)): - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.reset_console() + await node.reset_console() @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stream_pcap_file(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)): """ Stream the pcap capture file. The port number on the VMware node is always 0. """ - vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vm.get_nio(adapter_number) - stream = vmware_manager.stream_pcap_file(nio, vm.project.id) + nio = node.get_nio(adapter_number) + stream = VMware.instance().stream_pcap_file(nio, node.project.id) return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") @router.post("/{node_id}/interfaces/vmnet", status_code=status.HTTP_201_CREATED, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def allocate_vmnet(project_id: UUID, node_id: UUID) -> dict: + responses=responses) +def allocate_vmnet(node: VMwareVM = Depends(dep_node)) -> dict: """ Allocate a VMware VMnet interface on the server. """ vmware_manager = VMware.instance() - vm = vmware_manager.get_node(str(node_id), project_id=str(project_id)) vmware_manager.refresh_vmnet_list(ubridge=False) vmnet = vmware_manager.allocate_vmnet() - vm.vmnets.append(vmnet) + node.vmnets.append(vmnet) return {"vmnet": vmnet} diff --git a/gns3server/endpoints/compute/vpcs_nodes.py b/gns3server/endpoints/compute/vpcs_nodes.py index c6f04de16..064a3d212 100644 --- a/gns3server/endpoints/compute/vpcs_nodes.py +++ b/gns3server/endpoints/compute/vpcs_nodes.py @@ -21,7 +21,7 @@ API endpoints for VPCS nodes. import os -from fastapi import APIRouter, Body, status +from fastapi import APIRouter, Depends, Body, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from uuid import UUID @@ -29,10 +29,24 @@ from uuid import UUID from gns3server.endpoints import schemas from gns3server.compute.vpcs import VPCS from gns3server.compute.project_manager import ProjectManager - +from gns3server.compute.vpcs.vpcs_vm import VPCSVM router = APIRouter() +responses = { + 404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"} +} + + +def dep_node(project_id: UUID, node_id: UUID): + """ + Dependency to retrieve a node. + """ + + vpcs_manager = VPCS.instance() + node = vpcs_manager.get_node(str(node_id), project_id=str(project_id)) + return node + @router.post("/", response_model=schemas.VPCS, @@ -57,215 +71,190 @@ async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate): @router.get("/{node_id}", response_model=schemas.VPCS, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def get_vpcs_node(project_id: UUID, node_id: UUID): + responses=responses) +def get_vpcs_node(node: VPCSVM = Depends(dep_node)): """ Return a VPCS node. """ - vpcs_manager = VPCS.instance() - vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id)) - return vm.__json__() + return node.__json__() @router.put("/{node_id}", response_model=schemas.VPCS, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -def update_vpcs_node(project_id: UUID, node_id: UUID, node_data: schemas.VPCSUpdate): + responses=responses) +def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_node)): """ Update a VPCS node. """ - vpcs_manager = VPCS.instance() - vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) - vm.name = node_data.get("name", vm.name) - vm.console = node_data.get("console", vm.console) - vm.console_type = node_data.get("console_type", vm.console_type) - vm.updated() - return vm.__json__() + node.name = node_data.get("name", node.name) + node.console = node_data.get("console", node.console) + node.console_type = node_data.get("console_type", node.console_type) + node.updated() + return node.__json__() @router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_vpcs_node(project_id: UUID, node_id: UUID): + responses=responses) +async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)): """ Delete a VPCS node. """ - # check the project_id exists - ProjectManager.instance().get_project(str(project_id)) - await VPCS.instance().delete_node(str(node_id)) + await VPCS.instance().delete_node(node.id) @router.post("/{node_id}/duplicate", response_model=schemas.VPCS, status_code=status.HTTP_201_CREATED, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def duplicate_vpcs_node(project_id: UUID, node_id: UUID, destination_node_id: UUID = Body(..., embed=True)): + responses=responses) +async def duplicate_vpcs_node(destination_node_id: UUID = Body(..., embed=True), node: VPCSVM = Depends(dep_node)): """ Duplicate a VPCS node. """ - new_node = await VPCS.instance().duplicate_node(str(node_id), str(destination_node_id)) + new_node = await VPCS.instance().duplicate_node(node.id, str(destination_node_id)) return new_node.__json__() @router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_vpcs_node(project_id: UUID, node_id: UUID): + responses=responses) +async def start_vpcs_node(node: VPCSVM = Depends(dep_node)): """ Start a VPCS node. """ - vpcs_manager = VPCS.instance() - vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.start() + await node.start() @router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_vpcs_node(project_id: UUID, node_id: UUID): + responses=responses) +async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)): """ Stop a VPCS node. """ - vpcs_manager = VPCS.instance() - vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.stop() + await node.stop() @router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def suspend_vpcs_node(project_id: UUID, node_id: UUID): + responses=responses) +async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)): """ Suspend a VPCS node. Does nothing, suspend is not supported by VPCS. """ - vpcs_manager = VPCS.instance() - vpcs_manager.get_node(str(node_id), project_id=str(project_id)) + pass @router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reload_vpcs_node(project_id: UUID, node_id: UUID): + responses=responses) +async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)): """ Reload a VPCS node. """ - vpcs_manager = VPCS.instance() - vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.reload() + await node.reload() @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def create_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VPCSVM = Depends(dep_node)): """ Add a NIO (Network Input/Output) to the node. The adapter number on the VPCS node is always 0. """ - vpcs_manager = VPCS.instance() - vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vpcs_manager.create_nio(jsonable_encoder(nio_data, exclude_unset=True)) - await vm.port_add_nio_binding(port_number, nio) + nio = VPCS.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True)) + await node.port_add_nio_binding(port_number, nio) return nio.__json__() @router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_201_CREATED, response_model=schemas.UDPNIO, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def update_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, nio_data: schemas.UDPNIO): + responses=responses) +async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VPCSVM = Depends(dep_node)): """ Update a NIO (Network Input/Output) on the node. The adapter number on the VPCS node is always 0. """ - - vpcs_manager = VPCS.instance() - vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vm.get_nio(port_number) + nio = node.get_nio(port_number) if nio_data.filters: nio.filters = nio_data.filters - await vm.port_update_nio_binding(port_number, nio) + await node.port_update_nio_binding(port_number, nio) return nio.__json__() @router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def delete_nio(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def delete_nio(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)): """ Delete a NIO (Network Input/Output) from the node. The adapter number on the VPCS node is always 0. """ - vpcs_manager = VPCS.instance() - vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.port_remove_nio_binding(port_number) + await node.port_remove_nio_binding(port_number) @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/start_capture", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def start_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture): + responses=responses) +async def start_capture(adapter_number: int, + port_number: int, + node_capture_data: schemas.NodeCapture, + node: VPCSVM = Depends(dep_node)): """ Start a packet capture on the node. The adapter number on the VPCS node is always 0. """ - vpcs_manager = VPCS.instance() - vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id)) - pcap_file_path = os.path.join(vm.project.capture_working_directory(), node_capture_data.capture_file_name) - await vm.start_capture(adapter_number, pcap_file_path) + 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) return {"pcap_file_path": pcap_file_path} @router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/stop_capture", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stop_capture(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stop_capture(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)): """ Stop a packet capture on the node. The adapter number on the VPCS node is always 0. """ - vpcs_manager = VPCS.instance() - vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.stop_capture(port_number) + await node.stop_capture(port_number) @router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def reset_console(project_id: UUID, node_id: UUID): + responses=responses) +async def reset_console(node: VPCSVM = Depends(dep_node)): - vpcs_manager = VPCS.instance() - vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id)) - await vm.reset_console() + await node.reset_console() @router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap", - responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}) -async def stream_pcap_file(project_id: UUID, node_id: UUID, adapter_number: int, port_number: int): + responses=responses) +async def stream_pcap_file(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)): """ Stream the pcap capture file. The adapter number on the VPCS node is always 0. """ - vpcs_manager = VPCS.instance() - vm = vpcs_manager.get_node(str(node_id), project_id=str(project_id)) - nio = vm.get_nio(port_number) - stream = vpcs_manager.stream_pcap_file(nio, vm.project.id) + nio = node.get_nio(port_number) + stream = VPCS.instance().stream_pcap_file(nio, node.project.id) return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap") diff --git a/gns3server/endpoints/controller/__init__.py b/gns3server/endpoints/controller/__init__.py index b96a23e7b..d6e2aeabd 100644 --- a/gns3server/endpoints/controller/__init__.py +++ b/gns3server/endpoints/controller/__init__.py @@ -33,12 +33,12 @@ router = APIRouter() router.include_router(controller.router, tags=["controller"]) router.include_router(appliances.router, prefix="/appliances", tags=["appliances"]) router.include_router(computes.router, prefix="/computes", tags=["computes"]) -router.include_router(drawings.router, tags=["drawings"]) +router.include_router(drawings.router, prefix="/projects/{project_id}/drawings", tags=["drawings"]) router.include_router(gns3vm.router, prefix="/gns3vm", tags=["GNS3 VM"]) -router.include_router(links.router, tags=["links"]) -router.include_router(nodes.router, tags=["nodes"]) +router.include_router(links.router, prefix="/projects/{project_id}/links", tags=["links"]) +router.include_router(nodes.router, prefix="/projects/{project_id}/nodes", tags=["nodes"]) router.include_router(notifications.router, prefix="/notifications", tags=["notifications"]) router.include_router(projects.router, prefix="/projects", tags=["projects"]) -router.include_router(snapshots.router, tags=["snapshots"]) +router.include_router(snapshots.router, prefix="/projects/{project_id}/snapshots", tags=["snapshots"]) router.include_router(symbols.router, prefix="/symbols", tags=["symbols"]) router.include_router(templates.router, tags=["templates"]) diff --git a/gns3server/endpoints/controller/appliances.py b/gns3server/endpoints/controller/appliances.py index b24d845fd..9a1abc44d 100644 --- a/gns3server/endpoints/controller/appliances.py +++ b/gns3server/endpoints/controller/appliances.py @@ -25,8 +25,11 @@ from typing import Optional router = APIRouter() -@router.get("/", summary="List of appliances") -async def list_appliances(update: Optional[bool] = None, symbol_theme: Optional[str] = "Classic"): +@router.get("/") +async def get_appliances(update: Optional[bool] = None, symbol_theme: Optional[str] = "Classic"): + """ + Return all appliances known by the controller. + """ from gns3server.controller import Controller controller = Controller.instance() diff --git a/gns3server/endpoints/controller/computes.py b/gns3server/endpoints/controller/computes.py index b87b25b08..4cd6285a8 100644 --- a/gns3server/endpoints/controller/computes.py +++ b/gns3server/endpoints/controller/computes.py @@ -30,9 +30,12 @@ from gns3server.endpoints import schemas router = APIRouter() +responses = { + 404: {"model": ErrorMessage, "description": "Compute not found"} +} + @router.post("/", - summary="Create a new compute", status_code=status.HTTP_201_CREATED, response_model=schemas.Compute, responses={404: {"model": ErrorMessage, "description": "Could not connect to compute"}, @@ -49,14 +52,12 @@ async def create_compute(compute_data: schemas.ComputeCreate): @router.get("/{compute_id}", - summary="Get a compute", response_model=schemas.Compute, - response_description="Compute data", response_model_exclude_unset=True, - responses={404: {"model": ErrorMessage, "description": "Compute not found"}}) + responses=responses) def get_compute(compute_id: Union[str, UUID]): """ - Get compute data from the controller. + Return a compute from the controller. """ compute = Controller.instance().get_compute(str(compute_id)) @@ -64,13 +65,11 @@ def get_compute(compute_id: Union[str, UUID]): @router.get("/", - summary="List of all computes", response_model=List[schemas.Compute], - response_description="List of computes", response_model_exclude_unset=True) -async def list_computes(): +async def get_computes(): """ - Return the list of all computes known by the controller. + Return all computes known by the controller. """ controller = Controller.instance() @@ -78,11 +77,9 @@ async def list_computes(): @router.put("/{compute_id}", - summary="Update a compute", response_model=schemas.Compute, - response_description="Updated compute", response_model_exclude_unset=True, - responses={404: {"model": ErrorMessage, "description": "Compute not found"}}) + responses=responses) async def update_compute(compute_id: Union[str, UUID], compute_data: schemas.ComputeUpdate): """ Update a compute on the controller. @@ -95,9 +92,8 @@ async def update_compute(compute_id: Union[str, UUID], compute_data: schemas.Com @router.delete("/{compute_id}", - summary="Delete a compute", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Compute was not found"}}) + responses=responses) async def delete_compute(compute_id: Union[str, UUID]): """ Delete a compute from the controller. @@ -107,10 +103,8 @@ async def delete_compute(compute_id: Union[str, UUID]): @router.get("/{compute_id}/{emulator}/images", - summary="List images", - response_description="List of images", - responses={404: {"model": ErrorMessage, "description": "Compute was not found"}}) -async def list_images(compute_id: Union[str, UUID], emulator: str): + responses=responses) +async def get_images(compute_id: Union[str, UUID], emulator: str): """ Return the list of images available on a compute for a given emulator type. """ @@ -121,23 +115,24 @@ async def list_images(compute_id: Union[str, UUID], emulator: str): @router.get("/{compute_id}/{emulator}/{endpoint_path:path}", - summary="Forward GET request to a compute", - responses={404: {"model": ErrorMessage, "description": "Compute was not found"}}) + responses=responses) async def forward_get(compute_id: Union[str, UUID], emulator: str, endpoint_path: str): """ - Forward GET request to a compute. Read the full compute API documentation for available endpoints. + Forward a GET request to a compute. + Read the full compute API documentation for available endpoints. """ compute = Controller.instance().get_compute(str(compute_id)) result = await compute.forward("GET", emulator, endpoint_path) return result + @router.post("/{compute_id}/{emulator}/{endpoint_path:path}", - summary="Forward POST request to a compute", - responses={404: {"model": ErrorMessage, "description": "Compute was not found"}}) + responses=responses) async def forward_post(compute_id: Union[str, UUID], emulator: str, endpoint_path: str, compute_data: dict): """ - Forward POST request to a compute. Read the full compute API documentation for available endpoints. + Forward a POST request to a compute. + Read the full compute API documentation for available endpoints. """ compute = Controller.instance().get_compute(str(compute_id)) @@ -145,11 +140,11 @@ async def forward_post(compute_id: Union[str, UUID], emulator: str, endpoint_pat @router.put("/{compute_id}/{emulator}/{endpoint_path:path}", - summary="Forward PUT request to a compute", - responses={404: {"model": ErrorMessage, "description": "Compute was not found"}}) + responses=responses) async def forward_put(compute_id: Union[str, UUID], emulator: str, endpoint_path: str, compute_data: dict): """ - Forward PUT request to a compute. Read the full compute API documentation for available endpoints. + Forward a PUT request to a compute. + Read the full compute API documentation for available endpoints. """ compute = Controller.instance().get_compute(str(compute_id)) @@ -157,11 +152,10 @@ async def forward_put(compute_id: Union[str, UUID], emulator: str, endpoint_path @router.post("/{compute_id}/auto_idlepc", - summary="Find a new IDLE-PC value", - responses={404: {"model": ErrorMessage, "description": "Compute was not found"}}) + responses=responses) async def autoidlepc(compute_id: Union[str, UUID], auto_idle_pc: schemas.AutoIdlePC): """ - Find a suitable Idle-PC value for a given IOS image. This may take some time. + Find a suitable Idle-PC value for a given IOS image. This may take a few minutes. """ controller = Controller.instance() @@ -172,12 +166,11 @@ async def autoidlepc(compute_id: Union[str, UUID], auto_idle_pc: schemas.AutoIdl @router.get("/{compute_id}/ports", - summary="Get ports used by a compute", deprecated=True, - responses={404: {"model": ErrorMessage, "description": "Compute was not found"}}) + responses=responses) async def ports(compute_id: Union[str, UUID]): """ - Get ports information for a given compute. + Return ports information for a given compute. """ return await Controller.instance().compute_ports(str(compute_id)) diff --git a/gns3server/endpoints/controller/controller.py b/gns3server/endpoints/controller/controller.py index 7776e463b..a1e3d0c9b 100644 --- a/gns3server/endpoints/controller/controller.py +++ b/gns3server/endpoints/controller/controller.py @@ -37,7 +37,6 @@ router = APIRouter() @router.post("/shutdown", - summary="Shutdown the local server", status_code=status.HTTP_204_NO_CONTENT, responses={403: {"model": ErrorMessage, "description": "Server shutdown not allowed"}}) async def shutdown(): @@ -75,7 +74,7 @@ async def shutdown(): response_model=schemas.Version) def version(): """ - Retrieve the server version number. + Return the server version number. """ config = Config.instance() @@ -106,7 +105,7 @@ def check_version(version: schemas.Version): response_model=schemas.IOULicense) def get_iou_license(): """ - Get the IOU license settings + Return the IOU license settings """ return Controller.instance().iou_license @@ -130,7 +129,7 @@ async def update_iou_license(iou_license: schemas.IOULicense): @router.get("/statistics") async def statistics(): """ - Retrieve server statistics. + Return server statistics. """ compute_statistics = [] diff --git a/gns3server/endpoints/controller/drawings.py b/gns3server/endpoints/controller/drawings.py index b35ece545..75e5a246e 100644 --- a/gns3server/endpoints/controller/drawings.py +++ b/gns3server/endpoints/controller/drawings.py @@ -30,13 +30,15 @@ from gns3server.endpoints.schemas.drawings import Drawing router = APIRouter() +responses = { + 404: {"model": ErrorMessage, "description": "Project or drawing not found"} +} -@router.get("/projects/{project_id}/drawings", - summary="List of all drawings", + +@router.get("/", response_model=List[Drawing], - response_description="List of drawings", response_model_exclude_unset=True) -async def list_drawings(project_id: UUID): +async def get_drawings(project_id: UUID): """ Return the list of all drawings for a given project. """ @@ -45,27 +47,27 @@ async def list_drawings(project_id: UUID): return [v.__json__() for v in project.drawings.values()] -@router.post("/projects/{project_id}/drawings", - summary="Create a new drawing", +@router.post("/", status_code=status.HTTP_201_CREATED, response_model=Drawing, - responses={404: {"model": ErrorMessage, "description": "Could not find project"}}) + responses=responses) async def create_drawing(project_id: UUID, drawing_data: Drawing): + """ + Create a new drawing. + """ project = await Controller.instance().get_loaded_project(str(project_id)) drawing = await project.add_drawing(**jsonable_encoder(drawing_data, exclude_unset=True)) return drawing.__json__() -@router.get("/projects/{project_id}/drawings/{drawing_id}", - summary="Get a drawing", +@router.get("/{drawing_id}", response_model=Drawing, - response_description="Drawing data", response_model_exclude_unset=True, - responses={404: {"model": ErrorMessage, "description": "Project or drawing not found"}}) + responses=responses) async def get_drawing(project_id: UUID, drawing_id: UUID): """ - Get drawing data for a given project from the controller. + Return a drawing. """ project = await Controller.instance().get_loaded_project(str(project_id)) @@ -73,15 +75,13 @@ async def get_drawing(project_id: UUID, drawing_id: UUID): return drawing.__json__() -@router.put("/projects/{project_id}/drawings/{drawing_id}", - summary="Update a drawing", +@router.put("/{drawing_id}", response_model=Drawing, - response_description="Updated drawing", response_model_exclude_unset=True, - responses={404: {"model": ErrorMessage, "description": "Project or drawing not found"}}) + responses=responses) async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: Drawing): """ - Update a drawing for a given project on the controller. + Update a drawing. """ project = await Controller.instance().get_loaded_project(str(project_id)) @@ -90,13 +90,12 @@ async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: Drawi return drawing.__json__() -@router.delete("/projects/{project_id}/drawings/{drawing_id}", - summary="Delete a drawing", +@router.delete("/{drawing_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Project or drawing not found"}}) + responses=responses) async def delete_drawing(project_id: UUID, drawing_id: UUID): """ - Update a drawing for a given project from the controller. + Delete a drawing. """ project = await Controller.instance().get_loaded_project(str(project_id)) diff --git a/gns3server/endpoints/controller/gns3vm.py b/gns3server/endpoints/controller/gns3vm.py index eeb00ab68..4351cceb5 100644 --- a/gns3server/endpoints/controller/gns3vm.py +++ b/gns3server/endpoints/controller/gns3vm.py @@ -28,9 +28,8 @@ from gns3server.endpoints.schemas.gns3vm import GNS3VM router = APIRouter() -@router.get("/engines", - summary="List of engines") -async def list_engines(): +@router.get("/engines") +async def get_engines(): """ Return the list of supported engines for the GNS3VM. """ @@ -39,11 +38,10 @@ async def list_engines(): return gns3_vm.engine_list() -@router.get("/engines/{engine}/vms", - summary="List of VMs") +@router.get("/engines/{engine}/vms") async def get_vms(engine: str): """ - Get all the available VMs for a specific virtualization engine. + Return all the available VMs for a specific virtualization engine. """ vms = await Controller.instance().gns3vm.list(engine) @@ -51,19 +49,22 @@ async def get_vms(engine: str): @router.get("/", - summary="Get GNS3 VM settings", response_model=GNS3VM) async def get_gns3vm_settings(): + """ + Return the GNS3 VM settings. + """ return Controller.instance().gns3vm.__json__() @router.put("/", - summary="Update GNS3 VM settings", response_model=GNS3VM, - response_description="Updated GNS3 VM settings", response_model_exclude_unset=True) async def update_gns3vm_settings(gns3vm_data: GNS3VM): + """ + Update the GNS3 VM settings. + """ controller = Controller().instance() gns3_vm = controller.gns3vm diff --git a/gns3server/endpoints/controller/links.py b/gns3server/endpoints/controller/links.py index 60fd643ae..46b8424f5 100644 --- a/gns3server/endpoints/controller/links.py +++ b/gns3server/endpoints/controller/links.py @@ -19,7 +19,7 @@ API endpoints for links. """ -from fastapi import APIRouter, Request, status +from fastapi import APIRouter, Depends, Request, status from fastapi.encoders import jsonable_encoder from fastapi.responses import StreamingResponse from typing import List @@ -27,36 +27,47 @@ from uuid import UUID from gns3server.controller import Controller from gns3server.controller.controller_error import ControllerError +from gns3server.controller.link import Link from gns3server.endpoints.schemas.common import ErrorMessage -from gns3server.endpoints.schemas.links import Link - -import aiohttp -import multidict - +from gns3server.endpoints import schemas router = APIRouter() +responses = { + 404: {"model": ErrorMessage, "description": "Could not find project or link"} +} -@router.get("/projects/{project_id}/links", - summary="List of all links", - response_model=List[Link], - response_description="List of links", + +async def dep_link(project_id: UUID, link_id: UUID): + """ + Dependency to retrieve a link. + """ + + project = await Controller.instance().get_loaded_project(str(project_id)) + link = project.get_link(str(link_id)) + return link + + +@router.get("/", + response_model=List[schemas.Link], response_model_exclude_unset=True) -async def list_links(project_id: UUID): +async def get_links(project_id: UUID): + """ + Return all links for a given project. + """ project = await Controller.instance().get_loaded_project(str(project_id)) return [v.__json__() for v in project.links.values()] -@router.post("/projects/{project_id}/links", - summary="Create a new link", +@router.post("/", status_code=status.HTTP_201_CREATED, - response_model=Link, + response_model=schemas.Link, responses={404: {"model": ErrorMessage, "description": "Could not find project"}, 409: {"model": ErrorMessage, "description": "Could not create link"}}) -async def create_link(project_id: UUID, link_data: Link): +async def create_link(project_id: UUID, link_data: schemas.Link): """ - Create a new link on the controller. + Create a new link. """ project = await Controller.instance().get_loaded_project(str(project_id)) @@ -78,42 +89,37 @@ async def create_link(project_id: UUID, link_data: Link): return link.__json__() -@router.get("/projects/{project_id}/links/{link_id}/available_filters", - summary="List of filters", - responses={404: {"model": ErrorMessage, "description": "Could not find project or link"}}) -async def list_filters(project_id: UUID, link_id: UUID): +@router.get("/{link_id}/available_filters", + responses=responses) +async def get_filters(link: Link = Depends(dep_link)): """ - Return the list of filters available for this link. + Return all filters available for a given link. """ - project = await Controller.instance().get_loaded_project(str(project_id)) - link = project.get_link(str(link_id)) return link.available_filters() -@router.get("/projects/{project_id}/links/{link_id}", - summary="Get a link", - response_model=Link, - response_description="Link data", +@router.get("/{link_id}", + response_model=schemas.Link, response_model_exclude_unset=True, - responses={404: {"model": ErrorMessage, "description": "Could not find project or link"}}) -async def get_link(project_id: UUID, link_id: UUID): + responses=responses) +async def get_link(link: Link = Depends(dep_link)): + """ + Return a link. + """ - project = await Controller.instance().get_loaded_project(str(project_id)) - link = project.get_link(str(link_id)) return link.__json__() -@router.put("/projects/{project_id}/links/{link_id}", - summary="Update a link", - response_model=Link, - response_description="Updated link", +@router.put("/{link_id}", + response_model=schemas.Link, response_model_exclude_unset=True, - responses={404: {"model": ErrorMessage, "description": "Project or link not found"}}) -async def update_link(project_id: UUID, link_id: UUID, link_data: Link): + responses=responses) +async def update_link(link_data: schemas.Link, link: Link = Depends(dep_link)): + """ + Update a link. + """ - project = await Controller.instance().get_loaded_project(str(project_id)) - link = project.get_link(str(link_id)) link_data = jsonable_encoder(link_data, exclude_unset=True) if "filters" in link_data: await link.update_filters(link_data["filters"]) @@ -124,60 +130,53 @@ async def update_link(project_id: UUID, link_id: UUID, link_data: Link): return link.__json__() -@router.post("/projects/{project_id}/links/{link_id}/start_capture", - summary="Start a packet capture", +@router.post("/{link_id}/start_capture", status_code=status.HTTP_201_CREATED, - response_model=Link, - responses={404: {"model": ErrorMessage, "description": "Project or link not found"}}) -async def start_capture(project_id: UUID, link_id: UUID, capture_data: dict): + response_model=schemas.Link, + responses=responses) +async def start_capture(capture_data: dict, link: Link = Depends(dep_link)): """ Start packet capture on the link. """ - project = await Controller.instance().get_loaded_project(str(project_id)) - link = project.get_link(str(link_id)) await link.start_capture(data_link_type=capture_data.get("data_link_type", "DLT_EN10MB"), capture_file_name=capture_data.get("capture_file_name")) return link.__json__() -@router.post("/projects/{project_id}/links/{link_id}/stop_capture", - summary="Stop a packet capture", +@router.post("/{link_id}/stop_capture", status_code=status.HTTP_201_CREATED, - response_model=Link, - responses={404: {"model": ErrorMessage, "description": "Project or link not found"}}) -async def stop_capture(project_id: UUID, link_id: UUID): + response_model=schemas.Link, + responses=responses) +async def stop_capture(link: Link = Depends(dep_link)): """ Stop packet capture on the link. """ - project = await Controller.instance().get_loaded_project(str(project_id)) - link = project.get_link(str(link_id)) await link.stop_capture() return link.__json__() -@router.delete("/projects/{project_id}/links/{link_id}", - summary="Delete a link", - status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Project or link not found"}}) -async def delete(project_id: UUID, link_id: UUID): +@router.delete("/{link_id}", + status_code=status.HTTP_204_NO_CONTENT, + responses=responses) +async def delete_link(project_id: UUID, link: Link = Depends(dep_link)): """ - Delete link from the project. + Delete a link. """ project = await Controller.instance().get_loaded_project(str(project_id)) - await project.delete_link(str(link_id)) + await project.delete_link(link.id) -@router.post("/projects/{project_id}/links/{link_id}/reset", - summary="Reset a link", - response_model=Link, - responses={404: {"model": ErrorMessage, "description": "Project or link not found"}}) -async def reset(project_id: UUID, link_id: UUID): +@router.post("/{link_id}/reset", + response_model=schemas.Link, + responses=responses) +async def reset_link(link: Link = Depends(dep_link)): + """ + Reset a link. + """ - project = await Controller.instance().get_loaded_project(str(project_id)) - link = project.get_link(str(link_id)) await link.reset() return link.__json__() diff --git a/gns3server/endpoints/controller/nodes.py b/gns3server/endpoints/controller/nodes.py index 09c4ff2db..90efa1c50 100644 --- a/gns3server/endpoints/controller/nodes.py +++ b/gns3server/endpoints/controller/nodes.py @@ -21,28 +21,28 @@ API endpoints for nodes. import asyncio -from fastapi import APIRouter, Request, Response, status +from fastapi import APIRouter, Depends, Request, Response, status from fastapi.encoders import jsonable_encoder from fastapi.routing import APIRoute from typing import List, Callable from uuid import UUID from gns3server.controller import Controller +from gns3server.controller.node import Node +from gns3server.controller.project import Project from gns3server.utils import force_unix_path from gns3server.controller.controller_error import ControllerForbiddenError from gns3server.endpoints.schemas.common import ErrorMessage from gns3server.endpoints import schemas -import aiohttp - node_locks = {} class NodeConcurrency(APIRoute): """ - To avoid strange effect we prevent concurrency + To avoid strange effects, we prevent concurrency between the same instance of the node - (excepting when streaming a PCAP file and WebSocket consoles). + (excepting when streaming a PCAP file and for WebSocket consoles). """ def get_route_handler(self) -> Callable: @@ -74,25 +74,41 @@ class NodeConcurrency(APIRoute): router = APIRouter(route_class=NodeConcurrency) -# # dependency to retrieve a node -# async def get_node(project_id: UUID, node_id: UUID): -# -# project = await Controller.instance().get_loaded_project(str(project_id)) -# node = project.get_node(str(node_id)) -# return node +responses = { + 404: {"model": ErrorMessage, "description": "Could not find project or node"} +} -@router.post("/projects/{project_id}/nodes", - summary="Create a new node", +async def dep_project(project_id: UUID): + """ + Dependency to retrieve a project. + """ + + project = await Controller.instance().get_loaded_project(str(project_id)) + return project + + +async def dep_node(node_id: UUID, project: Project = Depends(dep_project)): + """ + Dependency to retrieve a node. + """ + + node = project.get_node(str(node_id)) + return node + + +@router.post("/", status_code=status.HTTP_201_CREATED, response_model=schemas.Node, responses={404: {"model": ErrorMessage, "description": "Could not find project"}, 409: {"model": ErrorMessage, "description": "Could not create node"}}) -async def create_node(project_id: UUID, node_data: schemas.Node): +async def create_node(node_data: schemas.Node, project: Project = Depends(dep_project)): + """ + Create a new node. + """ controller = Controller.instance() compute = controller.get_compute(str(node_data.compute_id)) - project = await controller.get_loaded_project(str(project_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) node = await project.add_node(compute, node_data.pop("name"), @@ -101,91 +117,82 @@ async def create_node(project_id: UUID, node_data: schemas.Node): return node.__json__() -@router.get("/projects/{project_id}/nodes", - summary="List of all nodes", +@router.get("/", response_model=List[schemas.Node], - response_description="List of nodes", response_model_exclude_unset=True) -async def list_nodes(project_id: UUID): +async def get_nodes(project: Project = Depends(dep_project)): + """ + Return all nodes belonging to a given project. + """ - project = await Controller.instance().get_loaded_project(str(project_id)) return [v.__json__() for v in project.nodes.values()] -@router.post("/projects/{project_id}/nodes/start", - summary="Start all nodes", +@router.post("/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find project"}}) -async def start_all_nodes(project_id: UUID): + responses=responses) +async def start_all_nodes(project: Project = Depends(dep_project)): """ - Start all nodes belonging to the project + Start all nodes belonging to a given project. """ - project = await Controller.instance().get_loaded_project(str(project_id)) await project.start_all() -@router.post("/projects/{project_id}/nodes/stop", - summary="Stop all nodes", +@router.post("/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find project"}}) -async def stop_all_nodes(project_id: UUID): + responses=responses) +async def stop_all_nodes(project: Project = Depends(dep_project)): """ - Stop all nodes belonging to the project + Stop all nodes belonging to a given project. """ - project = await Controller.instance().get_loaded_project(str(project_id)) await project.stop_all() -@router.post("/projects/{project_id}/nodes/suspend", - summary="Stop all nodes", +@router.post("/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find project"}}) -async def suspend_all_nodes(project_id: UUID): + responses=responses) +async def suspend_all_nodes(project: Project = Depends(dep_project)): """ - Suspend all nodes belonging to the project + Suspend all nodes belonging to a given project. """ - project = await Controller.instance().get_loaded_project(str(project_id)) await project.suspend_all() -@router.post("/projects/{project_id}/nodes/reload", - summary="Reload all nodes", +@router.post("/reload", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find project"}}) -async def reload_all_nodes(project_id: UUID): + responses=responses) +async def reload_all_nodes(project: Project = Depends(dep_project)): """ - Reload all nodes belonging to the project + Reload all nodes belonging to a given project. """ - project = await Controller.instance().get_loaded_project(str(project_id)) await project.stop_all() await project.start_all() -@router.get("/projects/{project_id}/nodes/{node_id}", - summary="Get a node", +@router.get("/{node_id}", response_model=schemas.Node, - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}}) -def get_node(project_id: UUID, node_id: UUID): + responses=responses) +def get_node(node: Node = Depends(dep_node)): + """ + Return a node from a given project. + """ - project = Controller.instance().get_project(str(project_id)) - node = project.get_node(str(node_id)) return node.__json__() -@router.put("/projects/{project_id}/nodes/{node_id}", - summary="Update a node", +@router.put("/{node_id}", response_model=schemas.Node, - response_description="Updated node", response_model_exclude_unset=True, - responses={404: {"model": ErrorMessage, "description": "Project or node not found"}}) -async def update_node(project_id: UUID, node_id: UUID, node_data: schemas.NodeUpdate): + responses=responses) +async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_node)): + """ + Update a node. + """ - project = await Controller.instance().get_loaded_project(str(project_id)) - node = project.get_node(str(node_id)) node_data = jsonable_encoder(node_data, exclude_unset=True) # Ignore these because we only use them when creating a node @@ -197,139 +204,129 @@ async def update_node(project_id: UUID, node_id: UUID, node_data: schemas.NodeUp return node.__json__() -@router.delete("/projects/{project_id}/nodes/{node_id}", - summary="Delete a node", +@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}, + responses={**responses, 409: {"model": ErrorMessage, "description": "Cannot delete node"}}) -async def delete_node(project_id: UUID, node_id: UUID): +async def delete_node(node_id: UUID, project: Project = Depends(dep_project)): + """ + Delete a node from a project. + """ - project = await Controller.instance().get_loaded_project(str(project_id)) await project.delete_node(str(node_id)) -@router.post("/projects/{project_id}/nodes/{node_id}/duplicate", - summary="Duplicate a node", +@router.post("/{node_id}/duplicate", response_model=schemas.Node, status_code=status.HTTP_201_CREATED, - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}}) -async def duplicate_node(project_id: UUID, node_id: UUID, duplicate_data: schemas.NodeDuplicate): + responses=responses) +async def duplicate_node(duplicate_data: schemas.NodeDuplicate, node: Node = Depends(dep_node)): + """ + Duplicate a node. + """ - project = await Controller.instance().get_loaded_project(str(project_id)) - node = project.get_node(str(node_id)) - new_node = await project.duplicate_node(node, - duplicate_data.x, - duplicate_data.y, - duplicate_data.z) + new_node = await node.project.duplicate_node(node, + duplicate_data.x, + duplicate_data.y, + duplicate_data.z) return new_node.__json__() -@router.post("/projects/{project_id}/nodes/{node_id}/start", - summary="Start a node", +@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}}) -async def start_node(project_id: UUID, node_id: UUID, start_data: dict): + responses=responses) +async def start_node(start_data: dict, node: Node = Depends(dep_node)): + """ + Start a node. + """ - project = await Controller.instance().get_loaded_project(str(project_id)) - node = project.get_node(str(node_id)) await node.start(data=start_data) -@router.post("/projects/{project_id}/nodes/{node_id}/stop", - summary="Stop a node", +@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}}) -async def stop_node(project_id: UUID, node_id: UUID): + responses=responses) +async def stop_node(node: Node = Depends(dep_node)): + """ + Stop a node. + """ - project = await Controller.instance().get_loaded_project(str(project_id)) - node = project.get_node(str(node_id)) await node.stop() -@router.post("/projects/{project_id}/nodes/{node_id}/suspend", - summary="Suspend a node", +@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}}) -async def suspend_node(project_id: UUID, node_id: UUID): + responses=responses) +async def suspend_node(node: Node = Depends(dep_node)): + """ + Suspend a node. + """ - project = await Controller.instance().get_loaded_project(str(project_id)) - node = project.get_node(str(node_id)) await node.suspend() -@router.post("/projects/{project_id}/nodes/{node_id}/reload", - summary="Reload a node", +@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}}) -async def reload_node(project_id: UUID, node_id: UUID): + responses=responses) +async def reload_node(node: Node = Depends(dep_node)): + """ + Reload a node. + """ - project = await Controller.instance().get_loaded_project(str(project_id)) - node = project.get_node(str(node_id)) await node.reload() -@router.get("/projects/{project_id}/nodes/{node_id}/links", - summary="List of all node links", +@router.get("/{node_id}/links", response_model=List[schemas.Link], - response_description="List of links", response_model_exclude_unset=True) -async def node_links(project_id: UUID, node_id: UUID): +async def get_node_links(node: Node = Depends(dep_node)): """ - Return all the links connected to the node. + Return all the links connected to a node. """ - project = await Controller.instance().get_loaded_project(str(project_id)) - node = project.get_node(str(node_id)) links = [] for link in node.links: links.append(link.__json__()) return links -@router.get("/projects/{project_id}/nodes/{node_id}/dynamips/auto_idlepc", - summary="Compute an Idle-PC", - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}}) -async def auto_idlepc(project_id: UUID, node_id: UUID): +@router.get("/{node_id}/dynamips/auto_idlepc", + responses=responses) +async def auto_idlepc(node: Node = Depends(dep_node)): """ Compute an Idle-PC value for a Dynamips node """ - project = await Controller.instance().get_loaded_project(str(project_id)) - node = project.get_node(str(node_id)) return await node.dynamips_auto_idlepc() -@router.get("/projects/{project_id}/nodes/{node_id}/dynamips/idlepc_proposals", - summary="Compute list of Idle-PC values", - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}}) -async def idlepc_proposals(project_id: UUID, node_id: UUID): +@router.get("/{node_id}/dynamips/idlepc_proposals", + responses=responses) +async def idlepc_proposals(node: Node = Depends(dep_node)): """ Compute a list of potential idle-pc values for a Dynamips node """ - project = await Controller.instance().get_loaded_project(str(project_id)) - node = project.get_node(str(node_id)) return await node.dynamips_idlepc_proposals() -@router.post("/projects/{project_id}/nodes/{node_id}/resize_disk", - summary="Resize a disk", +@router.post("/{node_id}/resize_disk", status_code=status.HTTP_201_CREATED, - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}}) -async def resize_disk(project_id: UUID, node_id: UUID, resize_data: dict): - - project = await Controller.instance().get_loaded_project(str(project_id)) - node = project.get_node(str(node_id)) + responses=responses) +async def resize_disk(resize_data: dict, node: Node = Depends(dep_node)): + """ + Resize a disk image. + """ await node.post("/resize_disk", **resize_data) -@router.get("/projects/{project_id}/nodes/{node_id}/files/{file_path:path}", - summary="Get a file in the node directory", - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}}) -async def get_file(project_id: UUID, node_id: UUID, file_path: str): +@router.get("/{node_id}/files/{file_path:path}", + responses=responses) +async def get_file(file_path: str, node: Node = Depends(dep_node)): + """ + Return a file in the node directory + """ - project = await Controller.instance().get_loaded_project(str(project_id)) - node = project.get_node(str(node_id)) path = force_unix_path(file_path) # Raise error if user try to escape @@ -339,18 +336,20 @@ async def get_file(project_id: UUID, node_id: UUID, file_path: str): node_type = node.node_type path = "/project-files/{}/{}/{}".format(node_type, node.id, path) - res = await node.compute.http_query("GET", "/projects/{project_id}/files{path}".format(project_id=project.id, path=path), timeout=None, raw=True) + res = await node.compute.http_query("GET", "/projects/{project_id}/files{path}".format(project_id=node.project.id, path=path), + timeout=None, + raw=True) return Response(res.body, media_type="application/octet-stream") -@router.post("/projects/{project_id}/nodes/{node_id}/files/{file_path:path}", - summary="Write a file in the node directory", +@router.post("/{node_id}/files/{file_path:path}", status_code=status.HTTP_201_CREATED, - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}}) -async def post_file(project_id: UUID, node_id: UUID, file_path: str, request: Request): + responses=responses) +async def post_file(file_path: str, request: Request, node: Node = Depends(dep_node)): + """ + Write a file in the node directory. + """ - project = await Controller.instance().get_loaded_project(str(project_id)) - node = project.get_node(str(node_id)) path = force_unix_path(file_path) # Raise error if user try to escape @@ -362,7 +361,10 @@ async def post_file(project_id: UUID, node_id: UUID, file_path: str, request: Re data = await request.body() #FIXME: are we handling timeout or large files correctly? - await node.compute.http_query("POST", "/projects/{project_id}/files{path}".format(project_id=project.id, path=path), data=data, timeout=None, raw=True) + await node.compute.http_query("POST", "/projects/{project_id}/files{path}".format(project_id=node.project.id, path=path), + data=data, + timeout=None, + raw=True) # @Route.get( @@ -420,23 +422,20 @@ async def post_file(project_id: UUID, node_id: UUID, file_path: str, request: Re # return ws -@router.post("/projects/{project_id}/nodes/console/reset", +@router.post("/console/reset", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}}) -async def reset_console_all(project_id: UUID): + responses=responses) +async def reset_console_all(project: Project = Depends(dep_project)): """ Reset console for all nodes belonging to the project. """ - project = await Controller.instance().get_loaded_project(str(project_id)) await project.reset_console_all() -@router.post("/projects/{project_id}/nodes/{node_id}/console/reset", +@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find project or node"}}) -async def console_reset(project_id: UUID, node_id: UUID): + responses=responses) +async def console_reset(node: Node = Depends(dep_node)): - project = await Controller.instance().get_loaded_project(str(project_id)) - node = project.get_node(str(node_id)) await node.post("/console/reset")#, request.json) diff --git a/gns3server/endpoints/controller/projects.py b/gns3server/endpoints/controller/projects.py index f854b1ad9..a09af4ee1 100644 --- a/gns3server/endpoints/controller/projects.py +++ b/gns3server/endpoints/controller/projects.py @@ -19,28 +19,7 @@ API endpoints for projects. """ -from fastapi import APIRouter, Request, Body, HTTPException, status, WebSocket, WebSocketDisconnect -from fastapi.encoders import jsonable_encoder -from fastapi.responses import StreamingResponse, FileResponse -from websockets.exceptions import WebSocketException -from typing import List -from pathlib import Path -from uuid import UUID - -from gns3server.endpoints.schemas.common import ErrorMessage -from gns3server.endpoints import schemas -from gns3server.controller import Controller -from gns3server.controller.controller_error import ControllerError, ControllerForbiddenError -from gns3server.controller.import_project import import_project as import_controller_project -from gns3server.controller.export_project import export_project as export_controller_project -from gns3server.utils.asyncio import aiozipstream -from gns3server.config import Config - -router = APIRouter() - - import os -import aiohttp import asyncio import tempfile import zipfile @@ -50,17 +29,52 @@ import time import logging log = logging.getLogger() +from fastapi import APIRouter, Depends, Request, Body, HTTPException, status, WebSocket, WebSocketDisconnect +from fastapi.encoders import jsonable_encoder +from fastapi.responses import StreamingResponse, FileResponse +from websockets.exceptions import WebSocketException +from typing import List +from uuid import UUID + +from gns3server.endpoints.schemas.common import ErrorMessage +from gns3server.endpoints import schemas +from gns3server.controller import Controller +from gns3server.controller.project import Project +from gns3server.controller.controller_error import ControllerError, ControllerForbiddenError +from gns3server.controller.import_project import import_project as import_controller_project +from gns3server.controller.export_project import export_project as export_controller_project +from gns3server.utils.asyncio import aiozipstream +from gns3server.config import Config + + +router = APIRouter() + +responses = { + 404: {"model": ErrorMessage, "description": "Could not find project"} +} + + +def dep_project(project_id: UUID): + """ + Dependency to retrieve a project. + """ + + project = Controller.instance().get_project(str(project_id)) + return project + CHUNK_SIZE = 1024 * 8 # 8KB @router.post("/", - summary="Create project", status_code=status.HTTP_201_CREATED, response_model=schemas.Project, response_model_exclude_unset=True, responses={409: {"model": ErrorMessage, "description": "Could not create project"}}) async def create_project(project_data: schemas.ProjectCreate): + """ + Create a new project. + """ controller = Controller.instance() project = await controller.add_project(**jsonable_encoder(project_data, exclude_unset=True)) @@ -69,92 +83,105 @@ async def create_project(project_data: schemas.ProjectCreate): @router.get("/", - summary="List of all projects", response_model=List[schemas.Project], - response_description="List of projects", response_model_exclude_unset=True) -def list_projects(): +def get_projects(): + """ + Return all projects. + """ controller = Controller.instance() return [p.__json__() for p in controller.projects.values()] @router.get("/{project_id}", - summary="Get a project", response_model=schemas.Project, - responses={404: {"model": ErrorMessage, "description": "Could not find project"}}) -def get_project(project_id: UUID): + responses=responses) +def get_project(project: Project = Depends(dep_project)): + """ + Return a project. + """ - controller = Controller.instance() - project = controller.get_project(str(project_id)) return project.__json__() @router.put("/{project_id}", - summary="Update a project", response_model=schemas.Project, response_model_exclude_unset=True, - responses={404: {"model": ErrorMessage, "description": "Could not find project"}}) -async def update_project(project_id: UUID, project_data: schemas.ProjectUpdate): + responses=responses) +async def update_project(project_data: schemas.ProjectUpdate, project: Project = Depends(dep_project)): + """ + Update a project. + """ - controller = Controller.instance() - project = controller.get_project(str(project_id)) await project.update(**jsonable_encoder(project_data, exclude_unset=True)) return project.__json__() @router.delete("/{project_id}", - summary="Delete a project", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find project"}}) -async def delete_project(project_id: UUID): + responses=responses) +async def delete_project(project: Project = Depends(dep_project)): + """ + Delete a project. + """ controller = Controller.instance() - project = controller.get_project(str(project_id)) await project.delete() controller.remove_project(project) @router.get("/{project_id}/stats", - summary="Get a project statistics", - responses={404: {"model": ErrorMessage, "description": "Could not find project"}}) -def get_project_stats(project_id: UUID): + responses=responses) +def get_project_stats(project: Project = Depends(dep_project)): + """ + Return a project statistics. + """ - controller = Controller.instance() - project = controller.get_project(str(project_id)) return project.stats() @router.post("/{project_id}/close", - summary="Close a project", status_code=status.HTTP_204_NO_CONTENT, - responses={409: {"model": ErrorMessage, "description": "Could not create project"}}) -async def close_project(project_id: UUID): + responses={ + **responses, + 409: {"model": ErrorMessage, "description": "Could not close project"} + }) +async def close_project(project: Project = Depends(dep_project)): + """ + Close a project. + """ - controller = Controller.instance() - project = controller.get_project(str(project_id)) await project.close() @router.post("/{project_id}/open", - summary="Open a project", status_code=status.HTTP_201_CREATED, response_model=schemas.Project, - responses={409: {"model": ErrorMessage, "description": "Could not create project"}}) -async def open_project(project_id: UUID): + responses={ + **responses, + 409: {"model": ErrorMessage, "description": "Could not open project"} + }) +async def open_project(project: Project = Depends(dep_project)): + """ + Open a project. + """ - controller = Controller.instance() - project = controller.get_project(str(project_id)) await project.open() return project.__json__() @router.post("/load", - summary="Open a project (local server only)", status_code=status.HTTP_201_CREATED, response_model=schemas.Project, - responses={409: {"model": ErrorMessage, "description": "Could not load project"}}) + responses={ + **responses, + 409: {"model": ErrorMessage, "description": "Could not load project"} + }) async def load_project(path: str = Body(..., embed=True)): + """ + Load a project (local server only). + """ controller = Controller.instance() config = Config.instance() @@ -224,9 +251,8 @@ async def notification_ws(project_id: UUID, websocket: WebSocket): @router.get("/{project_id}/export", - summary="Export project", - responses={404: {"model": ErrorMessage, "description": "Could not find project"}}) -async def export_project(project_id: UUID, + responses=responses) +async def export_project(project: Project = Depends(dep_project), include_snapshots: bool = False, include_images: bool = False, reset_mac_addresses: bool = False, @@ -235,9 +261,6 @@ async def export_project(project_id: UUID, Export a project as a portable archive. """ - controller = Controller.instance() - project = controller.get_project(str(project_id)) - compression_query = compression.lower() if compression_query == "zip": compression = zipfile.ZIP_DEFLATED @@ -277,10 +300,9 @@ async def export_project(project_id: UUID, @router.post("/{project_id}/import", - summary="Import a project", status_code=status.HTTP_201_CREATED, response_model=schemas.Project, - responses={404: {"model": ErrorMessage, "description": "Could not find project"}}) + responses=responses) async def import_project(project_id: UUID, request: Request): """ Import a project from a portable archive. @@ -319,15 +341,16 @@ async def import_project(project_id: UUID, request: Request): @router.post("/{project_id}/duplicate", - summary="Duplicate a project", status_code=status.HTTP_201_CREATED, response_model=schemas.Project, - responses={404: {"model": ErrorMessage, "description": "Could not find project"}, - 409: {"model": ErrorMessage, "description": "Could not duplicate project"}}) -async def duplicate(project_id: UUID, project_data: schemas.ProjectDuplicate): - - controller = Controller.instance() - project = await controller.get_loaded_project(str(project_id)) + responses={ + **responses, + 409: {"model": ErrorMessage, "description": "Could not duplicate project"} + }) +async def duplicate(project_data: schemas.ProjectDuplicate, project: Project = Depends(dep_project)): + """ + Duplicate a project. + """ if project_data.path: config = Config.instance() @@ -343,13 +366,11 @@ async def duplicate(project_id: UUID, project_data: schemas.ProjectDuplicate): @router.get("/{project_id}/files/{file_path:path}") -async def get_file(project_id: UUID, file_path: str): +async def get_file(file_path: str, project: Project = Depends(dep_project)): """ - Get a file from a project. + Return a file from a project. """ - controller = Controller.instance() - project = await controller.get_loaded_project(str(project_id)) path = os.path.normpath(file_path).strip('/') # Raise error if user try to escape @@ -365,10 +386,11 @@ async def get_file(project_id: UUID, file_path: str): @router.post("/{project_id}/files/{file_path:path}", status_code=status.HTTP_204_NO_CONTENT) -async def write_file(project_id: UUID, file_path: str, request: Request): +async def write_file(file_path: str, request: Request, project: Project = Depends(dep_project)): + """ + Write a file from a project. + """ - controller = Controller.instance() - project = await controller.get_loaded_project(str(project_id)) path = os.path.normpath(file_path).strip("/") # Raise error if user try to escape diff --git a/gns3server/endpoints/controller/snapshots.py b/gns3server/endpoints/controller/snapshots.py index 764e7edba..f31040665 100644 --- a/gns3server/endpoints/controller/snapshots.py +++ b/gns3server/endpoints/controller/snapshots.py @@ -19,75 +19,81 @@ API endpoints for snapshots. """ -from fastapi import APIRouter, status + +import logging +log = logging.getLogger() + +from fastapi import APIRouter, Depends, status from typing import List from uuid import UUID +from gns3server.controller.project import Project from gns3server.endpoints.schemas.common import ErrorMessage from gns3server.endpoints import schemas from gns3server.controller import Controller router = APIRouter() -import logging -log = logging.getLogger() +responses = { + 404: {"model": ErrorMessage, "description": "Could not find project or snapshot"} +} -@router.post("/projects/{project_id}/snapshots", +def dep_project(project_id: UUID): + """ + Dependency to retrieve a project. + """ + + project = Controller.instance().get_project(str(project_id)) + return project + + +@router.post("/", status_code=status.HTTP_201_CREATED, response_model=schemas.Snapshot, - responses={404: {"model": ErrorMessage, "description": "Could not find project"}}) -async def create_snapshot(project_id: UUID, snapshot_data: schemas.SnapshotCreate): + responses=responses) +async def create_snapshot(snapshot_data: schemas.SnapshotCreate, project: Project = Depends(dep_project)): """ - Create a new snapshot of the project. + Create a new snapshot of a project. """ - controller = Controller.instance() - project = controller.get_project(str(project_id)) snapshot = await project.snapshot(snapshot_data.name) return snapshot.__json__() -@router.get("/projects/{project_id}/snapshots", +@router.get("/", response_model=List[schemas.Snapshot], - response_description="List of snapshots", response_model_exclude_unset=True, - responses={404: {"model": ErrorMessage, "description": "Could not find project"}}) -def list_snapshots(project_id: UUID): + responses=responses) +def get_snapshots(project: Project = Depends(dep_project)): """ - Return a list of snapshots belonging to the project. + Return all snapshots belonging to a given project. """ - controller = Controller.instance() - project = controller.get_project(str(project_id)) snapshots = [s for s in project.snapshots.values()] return [s.__json__() for s in sorted(snapshots, key=lambda s: (s.created_at, s.name))] -@router.delete("/projects/{project_id}/snapshots/{snapshot_id}", +@router.delete("/{snapshot_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find project or snapshot"}}) -async def delete_snapshot(project_id: UUID, snapshot_id: UUID): + responses=responses) +async def delete_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)): """ - Delete a snapshot belonging to the project. + Delete a snapshot. """ - controller = Controller.instance() - project = controller.get_project(str(project_id)) await project.delete_snapshot(str(snapshot_id)) -@router.post("/projects/{project_id}/snapshots/{snapshot_id}/restore", +@router.post("/{snapshot_id}/restore", status_code=status.HTTP_201_CREATED, response_model=schemas.Project, - responses={404: {"model": ErrorMessage, "description": "Could not find project or snapshot"}}) -async def restore_snapshot(project_id: UUID, snapshot_id: UUID): + responses=responses) +async def restore_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)): """ - Restore a snapshot from the project. + Restore a snapshot. """ - controller = Controller.instance() - project = controller.get_project(str(project_id)) snapshot = project.get_snapshot(str(snapshot_id)) project = await snapshot.restore() return project.__json__() diff --git a/gns3server/endpoints/controller/symbols.py b/gns3server/endpoints/controller/symbols.py index fb41d1bda..aa662f844 100644 --- a/gns3server/endpoints/controller/symbols.py +++ b/gns3server/endpoints/controller/symbols.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -# Copyright (C) 2016 GNS3 Technologies Inc. +# 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 @@ -20,9 +20,8 @@ API endpoints for symbols. """ import os -import shutil -from fastapi import APIRouter, Request, status, File, UploadFile +from fastapi import APIRouter, Request, status from fastapi.responses import FileResponse from gns3server.controller import Controller @@ -37,7 +36,7 @@ router = APIRouter() @router.get("/") -def list_symbols(): +def get_symbols(): controller = Controller.instance() return controller.symbols.list() @@ -46,6 +45,9 @@ def list_symbols(): @router.get("/{symbol_id:path}/raw", responses={404: {"model": ErrorMessage, "description": "Could not find symbol"}}) async def get_symbol(symbol_id: str): + """ + Download a symbol file. + """ controller = Controller.instance() try: @@ -76,9 +78,9 @@ async def upload_symbol(symbol_id: str, request: Request): @router.get("/default_symbols") -def list_default_symbols(): +def get_default_symbols(): """ - Return list of default symbols. + Return all default symbols. """ controller = Controller.instance() diff --git a/gns3server/endpoints/controller/templates.py b/gns3server/endpoints/controller/templates.py index 79768f789..b4edd434f 100644 --- a/gns3server/endpoints/controller/templates.py +++ b/gns3server/endpoints/controller/templates.py @@ -22,6 +22,9 @@ API endpoints for templates. import hashlib import json +import logging +log = logging.getLogger(__name__) + from fastapi import APIRouter, Request, Response, HTTPException, status from fastapi.encoders import jsonable_encoder from typing import Union, List @@ -33,8 +36,10 @@ from gns3server.controller import Controller router = APIRouter() -import logging -log = logging.getLogger(__name__) +responses = { + 404: {"model": ErrorMessage, "description": "Could not find template"} +} + #template_create_models = Union[schemas.VPCSTemplateCreate, schemas.CloudTemplateCreate, schemas.IOUTemplateCreate] #template_update_models = Union[schemas.VPCSTemplateUpdate, schemas.CloudTemplateUpdate, schemas.IOUTemplateUpdate] @@ -45,6 +50,9 @@ log = logging.getLogger(__name__) status_code=status.HTTP_201_CREATED, response_model=schemas.Template) def create_template(template_data: schemas.TemplateCreate): + """ + Create a new template. + """ controller = Controller.instance() template = controller.template_manager.add_template(jsonable_encoder(template_data, exclude_unset=True)) @@ -54,11 +62,13 @@ def create_template(template_data: schemas.TemplateCreate): @router.get("/templates/{template_id}", - summary="List of all nodes", response_model=schemas.Template, response_model_exclude_unset=True, - responses={404: {"model": ErrorMessage, "description": "Could not find template"}}) + responses=responses) def get_template(template_id: UUID, request: Request, response: Response): + """ + Return a template. + """ request_etag = request.headers.get("If-None-Match", "") controller = Controller.instance() @@ -75,8 +85,11 @@ def get_template(template_id: UUID, request: Request, response: Response): @router.put("/templates/{template_id}", response_model=schemas.Template, response_model_exclude_unset=True, - responses={404: {"model": ErrorMessage, "description": "Template not found"}}) + responses=responses) def update_template(template_id: UUID, template_data: schemas.TemplateUpdate): + """ + Update a template. + """ controller = Controller.instance() template = controller.template_manager.get_template(str(template_id)) @@ -86,8 +99,11 @@ def update_template(template_id: UUID, template_data: schemas.TemplateUpdate): @router.delete("/templates/{template_id}", status_code=status.HTTP_204_NO_CONTENT, - responses={404: {"model": ErrorMessage, "description": "Could not find template"}}) + responses=responses) def delete_template(template_id: UUID): + """ + Delete a template. + """ controller = Controller.instance() controller.template_manager.delete_template(str(template_id)) @@ -95,9 +111,11 @@ def delete_template(template_id: UUID): @router.get("/templates", response_model=List[schemas.Template], - response_description="List of templates", response_model_exclude_unset=True) -def list_templates(): +def get_templates(): + """ + Return all templates. + """ controller = Controller.instance() return [c.__json__() for c in controller.template_manager.templates.values()] @@ -106,8 +124,11 @@ def list_templates(): @router.post("/templates/{template_id}/duplicate", response_model=schemas.Template, status_code=status.HTTP_201_CREATED, - responses={404: {"model": ErrorMessage, "description": "Could not find template"}}) + responses=responses) async def duplicate_template(template_id: UUID): + """ + Duplicate a template. + """ controller = Controller.instance() template = controller.template_manager.duplicate_template(str(template_id)) @@ -119,6 +140,9 @@ async def duplicate_template(template_id: UUID): status_code=status.HTTP_201_CREATED, responses={404: {"model": ErrorMessage, "description": "Could not find project or template"}}) async def create_node_from_template(project_id: UUID, template_id: UUID, template_usage: schemas.TemplateUsage): + """ + Create a new node from a template. + """ controller = Controller.instance() project = controller.get_project(str(project_id))