mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-04 22:41:12 +02:00
Merge branch '3.1' into fork/markparonyan/api-issue-10-0eztih
# Conflicts: # gns3server/schemas/__init__.py
This commit is contained in:
110 files changed
+21567
-701
No files matched your search
@@ -25,19 +25,51 @@ from fastapi import APIRouter, Request
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.compute import MODULES
|
||||
from gns3server.compute.ubridge.tc_probe import (
|
||||
probe_bridge_tap_support,
|
||||
probe_iol_tap_support,
|
||||
probe_tap_support,
|
||||
probe_tc_capabilities,
|
||||
)
|
||||
from gns3server.utils.path import get_default_project_directory
|
||||
from gns3server.utils.tc_capabilities import usable_ebpf_modes
|
||||
from gns3server.version import __version__
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/capabilities", response_model=schemas.Capabilities)
|
||||
def get_capabilities(request: Request) -> dict:
|
||||
async def get_capabilities(request: Request) -> dict:
|
||||
|
||||
node_types = []
|
||||
for module in MODULES:
|
||||
node_types.extend(module.node_types())
|
||||
|
||||
# Kernel-datapath packet filters: what this host's uBridge can run,
|
||||
# probed as the server's own user (None = unknown, old/missing uBridge).
|
||||
ubridge_tc = None
|
||||
caps = await probe_tc_capabilities()
|
||||
if caps:
|
||||
ubridge_tc = {
|
||||
"netem": [keyword for keyword in caps.get("netem", "").split(",") if keyword],
|
||||
"ebpf": caps.get("ebpf") == "1",
|
||||
"ebpf_modes": list(usable_ebpf_modes(caps)),
|
||||
"cbpf": caps.get("cbpf") == "1",
|
||||
}
|
||||
|
||||
# Persistent TAP support: the anchor QEMU adapters need for the kernel
|
||||
# datapath. None = unknown (probe failed or uBridge too old).
|
||||
ubridge_tap = await probe_tap_support()
|
||||
|
||||
# IOL-port TAP termination (iol_bridge add_nio_tap): the anchor IOU's
|
||||
# Ethernet ports need for the kernel datapath. None = unknown.
|
||||
ubridge_iol_tap = await probe_iol_tap_support()
|
||||
|
||||
# Swappable TAP leg on the generic bridge module (bridge delete_nio_tap):
|
||||
# what lets an IOL runner container's per-port bridge carry a kernel-link
|
||||
# anchor. None = unknown (probe failed or uBridge too old).
|
||||
ubridge_bridge_tap = await probe_bridge_tap_support()
|
||||
|
||||
# record the controller hostname or IP address
|
||||
if request.client:
|
||||
request.app.state.controller_host = request.client.host
|
||||
@@ -49,4 +81,8 @@ def get_capabilities(request: Request) -> dict:
|
||||
"memory": psutil.virtual_memory().total,
|
||||
"disk_size": psutil.disk_usage(get_default_project_directory()).total,
|
||||
"node_types": node_types,
|
||||
"ubridge_tc": ubridge_tc,
|
||||
"ubridge_tap": ubridge_tap,
|
||||
"ubridge_iol_tap": ubridge_iol_tap,
|
||||
"ubridge_bridge_tap": ubridge_bridge_tap,
|
||||
}
|
||||
@@ -260,12 +260,15 @@ async def duplicate_docker_node(
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
response_model=Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_docker_node_nio(
|
||||
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node)
|
||||
) -> schemas.UDPNIO:
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
node: DockerVM = Depends(dep_node),
|
||||
) -> Union[schemas.UDPNIO, schemas.BridgeNIO]:
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
The port number on the Docker node is always 0.
|
||||
@@ -279,12 +282,15 @@ async def create_docker_node_nio(
|
||||
@router.put(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
response_model=Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def update_docker_node_nio(
|
||||
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: DockerVM = Depends(dep_node)
|
||||
) -> schemas.UDPNIO:
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
node: DockerVM = Depends(dep_node),
|
||||
) -> Union[schemas.UDPNIO, schemas.BridgeNIO]:
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
The port number on the Docker node is always 0.
|
||||
|
||||
@@ -29,6 +29,7 @@ from fastapi.responses import StreamingResponse
|
||||
from gns3server import schemas
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.compute.dynamips.nodes.router import Router
|
||||
from gns3server.compute.nios.nio_bridge import NIOBridge
|
||||
|
||||
from .dependencies.authentication import compute_authentication, ws_compute_authentication
|
||||
|
||||
@@ -173,12 +174,15 @@ async def reload_router(node: Router = Depends(dep_node)) -> None:
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
response_model=Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_nio(
|
||||
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: Router = Depends(dep_node)
|
||||
) -> schemas.UDPNIO:
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
node: Router = Depends(dep_node),
|
||||
) -> Union[schemas.UDPNIO, schemas.BridgeNIO]:
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
"""
|
||||
@@ -191,12 +195,15 @@ async def create_nio(
|
||||
@router.put(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
response_model=Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def update_nio(
|
||||
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: Router = Depends(dep_node)
|
||||
) -> schemas.UDPNIO:
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
node: Router = Depends(dep_node),
|
||||
) -> Union[schemas.UDPNIO, schemas.BridgeNIO]:
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
"""
|
||||
@@ -206,6 +213,10 @@ async def update_nio(
|
||||
if nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
nio.markers = nio_data.markers or {}
|
||||
# Suspend is what the compute turns into an admin-down anchor on a
|
||||
# kernel link (native carrier), so it must reach the NIO like it does
|
||||
# on the Docker/QEMU/IOU routes.
|
||||
nio.suspend = getattr(nio_data, "suspend", None) or False
|
||||
await node.slot_update_nio_binding(adapter_number, port_number, nio)
|
||||
return nio.asdict()
|
||||
|
||||
@@ -221,7 +232,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: Router = Depen
|
||||
"""
|
||||
|
||||
nio = await node.slot_remove_nio_binding(adapter_number, port_number)
|
||||
await nio.delete()
|
||||
if not isinstance(nio, NIOBridge):
|
||||
# A kernel NIO has no hypervisor side left: its TAP NIO was deleted
|
||||
# by the slot binding removal itself.
|
||||
await nio.delete()
|
||||
|
||||
|
||||
@router.post(
|
||||
|
||||
@@ -23,7 +23,7 @@ through uBridge's ``brctl`` module (see
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import Any
|
||||
from typing import Any, Union
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Body, Depends, HTTPException, Path, status
|
||||
@@ -109,8 +109,21 @@ async def update_ethernet_switch(
|
||||
if "usage" in data:
|
||||
node.usage = data["usage"]
|
||||
if "ports_mapping" in data:
|
||||
# capture the mapping before the setter replaces it: the VLAN
|
||||
# reconcile diffs old against new to touch only what changed, in
|
||||
# place (the setter itself keeps the port-count guard)
|
||||
previous_mapping = [dict(port) for port in node.ports_mapping]
|
||||
node.ports_mapping = data["ports_mapping"]
|
||||
await node.update_port_settings()
|
||||
try:
|
||||
await node.update_port_settings(previous_mapping)
|
||||
except Exception:
|
||||
# Nothing, or only part, of the new mapping was applied — keep
|
||||
# the old one so the failed change stays visible in GET and a
|
||||
# retry still diffs against it. A kept-new mapping would turn
|
||||
# the retry into a no-op that reports success with the VLANs
|
||||
# never applied.
|
||||
node.ports_mapping = previous_mapping
|
||||
raise
|
||||
if "console_type" in data:
|
||||
node.console_type = data["console_type"]
|
||||
node.updated()
|
||||
@@ -174,15 +187,20 @@ def reload_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
response_model=Union[schemas.UDPNIO, schemas.AnchorNIO],
|
||||
)
|
||||
async def create_ethernet_switch_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
nio_data: Union[schemas.UDPNIO, schemas.AnchorNIO],
|
||||
node: EthernetSwitch = Depends(dep_node),
|
||||
) -> schemas.UDPNIO:
|
||||
) -> Union[schemas.UDPNIO, schemas.AnchorNIO]:
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node: a UDP NIO wires the relay
|
||||
datapath, an anchor NIO absorbs the peer's kernel anchor into this
|
||||
switch's kernel bridge. The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
nio = Builtin.instance().create_nio(jsonable_encoder(nio_data, exclude_unset=True))
|
||||
await node.add_nio(nio, port_number)
|
||||
@@ -192,19 +210,21 @@ async def create_ethernet_switch_nio(
|
||||
@router.put(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
response_model=Union[schemas.UDPNIO, schemas.AnchorNIO],
|
||||
)
|
||||
async def update_ethernet_switch_nio(
|
||||
*,
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
nio_data: Union[schemas.UDPNIO, schemas.AnchorNIO],
|
||||
node: EthernetSwitch = Depends(dep_node),
|
||||
) -> schemas.UDPNIO:
|
||||
) -> Union[schemas.UDPNIO, schemas.AnchorNIO]:
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node: re-apply the packet
|
||||
filters and traffic-insight markers carried by the NIO onto the port's
|
||||
uBridge relay. The adapter number on the switch is always 0.
|
||||
filters, traffic-insight markers and suspend state carried by the NIO —
|
||||
on the port's uBridge relay for a relay port, on the absorbed anchor
|
||||
(tc + kernel markers + carrier) for a kernel port. The adapter number
|
||||
on the switch is always 0.
|
||||
"""
|
||||
|
||||
nio = node.get_nio(port_number)
|
||||
@@ -212,6 +232,10 @@ async def update_ethernet_switch_nio(
|
||||
if nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
nio.markers = nio_data.markers or {}
|
||||
# Suspend is what the compute turns into an admin-down anchor on a
|
||||
# kernel link (native carrier), so it must reach the NIO like it does
|
||||
# on the other node routes.
|
||||
nio.suspend = getattr(nio_data, "suspend", None) or False
|
||||
await node.update_nio(port_number, nio)
|
||||
return nio.asdict()
|
||||
|
||||
|
||||
@@ -187,15 +187,15 @@ async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
@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],
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO, schemas.BridgeNIO],
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_iou_node_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, schemas.BridgeNIO],
|
||||
node: IOUVM = Depends(dep_node),
|
||||
) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]:
|
||||
) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO, schemas.BridgeNIO]:
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
"""
|
||||
@@ -208,25 +208,31 @@ async def create_iou_node_nio(
|
||||
@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],
|
||||
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO, schemas.BridgeNIO],
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def update_iou_node_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, schemas.BridgeNIO],
|
||||
node: IOUVM = Depends(dep_node),
|
||||
) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO]:
|
||||
) -> Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO, schemas.BridgeNIO]:
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
"""
|
||||
|
||||
nio = node.get_nio(adapter_number, port_number)
|
||||
nio.filters.clear()
|
||||
if isinstance(nio_data, schemas.UDPNIO) and nio_data.filters:
|
||||
# EthernetNIO/TAPNIO carry no filters field; UDPNIO (relay) and
|
||||
# BridgeNIO (kernel datapath) both do.
|
||||
if isinstance(nio_data, (schemas.UDPNIO, schemas.BridgeNIO)) and nio_data.filters:
|
||||
nio.filters = nio_data.filters
|
||||
# NIO type is a Union (Ethernet/TAP/UDP); only UDPNIO carries markers.
|
||||
# NIO type is a Union (Ethernet/TAP/UDP/bridge); only UDPNIO and
|
||||
# BridgeNIO carry markers and suspend. Suspend is what the compute turns
|
||||
# into an admin-down anchor on a kernel link (native carrier), so it must
|
||||
# reach the NIO like it does on the Docker/QEMU routes.
|
||||
nio.markers = getattr(nio_data, "markers", None) or {}
|
||||
nio.suspend = getattr(nio_data, "suspend", None) or False
|
||||
await node.adapter_update_nio_binding(adapter_number, port_number, nio)
|
||||
return nio.asdict()
|
||||
|
||||
|
||||
@@ -269,18 +269,19 @@ async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
response_model=Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def create_qemu_node_nio(
|
||||
*,
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
nio_data: schemas.UDPNIO,
|
||||
nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
node: QemuVM = Depends(dep_node),
|
||||
) -> schemas.UDPNIO:
|
||||
) -> Union[schemas.UDPNIO, schemas.BridgeNIO]:
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
Add a NIO (Network Input/Output) to the node: a UDP relay NIO, or a
|
||||
kernel-datapath bridge NIO the node enslaves its adapter TAP into.
|
||||
The port number on the Qemu node is always 0.
|
||||
"""
|
||||
|
||||
@@ -292,16 +293,16 @@ async def create_qemu_node_nio(
|
||||
@router.put(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
response_model=Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
dependencies=[Depends(compute_authentication)],
|
||||
)
|
||||
async def update_qemu_node_nio(
|
||||
*,
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
nio_data: schemas.UDPNIO,
|
||||
nio_data: Union[schemas.UDPNIO, schemas.BridgeNIO],
|
||||
node: QemuVM = Depends(dep_node),
|
||||
) -> schemas.UDPNIO:
|
||||
) -> Union[schemas.UDPNIO, schemas.BridgeNIO]:
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
The port number on the Qemu node is always 0.
|
||||
|
||||
@@ -33,8 +33,13 @@ from gns3server import schemas
|
||||
from gns3server.agent.web_wireshark.manager import WebWiresharkManager
|
||||
from gns3server.api.openapi import PCAP_MEDIA_TYPE, binary_response
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.controller.controller_error import ControllerError, ControllerNotFoundError
|
||||
from gns3server.controller.controller_error import (
|
||||
ControllerError,
|
||||
ControllerNotFoundError,
|
||||
controller_error_status_code,
|
||||
)
|
||||
from gns3server.controller.link import _UNSET, Link
|
||||
from gns3server.controller.project import Project
|
||||
from gns3server.db.repositories.rbac import RbacRepository
|
||||
from gns3server.utils.http_client import HTTPClient
|
||||
from gns3server.utils.port_allocator import link_id_to_port
|
||||
@@ -123,7 +128,7 @@ async def get_links(project_id: UUID) -> List[schemas.Link]:
|
||||
},
|
||||
dependencies=[Depends(has_privilege("Link.Allocate"))],
|
||||
)
|
||||
async def create_link(project_id: UUID, link_create: schemas.LinkCreate) -> schemas.Link:
|
||||
async def create_link(project_id: UUID, link_create: schemas.LinkCreate) -> dict:
|
||||
"""
|
||||
Create a new link.
|
||||
|
||||
@@ -131,17 +136,22 @@ async def create_link(project_id: UUID, link_create: schemas.LinkCreate) -> sche
|
||||
"""
|
||||
|
||||
project = await Controller.instance().get_loaded_project(str(project_id))
|
||||
return await _create_link(project, link_create)
|
||||
|
||||
|
||||
async def _create_link(project: Project, link_create: schemas.LinkCreate) -> dict:
|
||||
|
||||
link = await project.add_link()
|
||||
link_data = jsonable_encoder(link_create, exclude_unset=True)
|
||||
if "filters" in link_data:
|
||||
await link.update_filters(link_data["filters"])
|
||||
if "link_style" in link_data:
|
||||
await link.update_link_style(link_data["link_style"])
|
||||
if "suspend" in link_data:
|
||||
await link.update_suspend(link_data["suspend"])
|
||||
if "show_filters_icon" in link_data:
|
||||
await link.update_show_filters_icon(link_data["show_filters_icon"])
|
||||
try:
|
||||
if "filters" in link_data:
|
||||
await link.update_filters(link_data["filters"])
|
||||
if "link_style" in link_data:
|
||||
await link.update_link_style(link_data["link_style"])
|
||||
if "suspend" in link_data:
|
||||
await link.update_suspend(link_data["suspend"])
|
||||
if "show_filters_icon" in link_data:
|
||||
await link.update_show_filters_icon(link_data["show_filters_icon"])
|
||||
endpoints = link_data["nodes"]
|
||||
attached_port = None
|
||||
for index, endpoint in enumerate(endpoints):
|
||||
@@ -159,6 +169,40 @@ async def create_link(project_id: UUID, link_create: schemas.LinkCreate) -> sche
|
||||
return link.asdict()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/batch",
|
||||
response_model=List[schemas.LinkBatchResult],
|
||||
responses={
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project"},
|
||||
},
|
||||
dependencies=[Depends(has_privilege("Link.Allocate"))],
|
||||
)
|
||||
async def create_links(project_id: UUID, links_create: List[schemas.LinkCreate]) -> List[schemas.LinkBatchResult]:
|
||||
"""
|
||||
Create several links in the order they are listed.
|
||||
|
||||
The response lists one result per link in the same order. A link that fails to be created
|
||||
does not stop the following ones.
|
||||
|
||||
Required privilege: Link.Allocate
|
||||
"""
|
||||
|
||||
project = await Controller.instance().get_loaded_project(str(project_id))
|
||||
results = []
|
||||
for link_create in links_create:
|
||||
try:
|
||||
link = await _create_link(project, link_create)
|
||||
results.append(schemas.LinkBatchResult(status_code=status.HTTP_201_CREATED, link=link))
|
||||
except ControllerError as e:
|
||||
log.error(f"Could not create link in batch: {e}")
|
||||
results.append(
|
||||
schemas.LinkBatchResult(
|
||||
status_code=controller_error_status_code(e), error=schemas.ErrorMessage(message=str(e))
|
||||
)
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{link_id}/available_filters",
|
||||
response_model=List[schemas.LinkFilterInfo],
|
||||
|
||||
@@ -37,8 +37,10 @@ from gns3server.config import Config
|
||||
from gns3server.controller import Controller
|
||||
from gns3server.controller.controller_error import (
|
||||
ControllerBadRequestError,
|
||||
ControllerError,
|
||||
ControllerForbiddenError,
|
||||
ControllerTimeoutError,
|
||||
controller_error_status_code,
|
||||
)
|
||||
from gns3server.controller.node import Node
|
||||
from gns3server.controller.project import Project
|
||||
@@ -141,13 +143,20 @@ def _check_node_type(node: Node, *required_types: str) -> None:
|
||||
},
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))],
|
||||
)
|
||||
async def create_node(node_create: schemas.NodeCreate, project: Project = Depends(dep_project)) -> schemas.Node:
|
||||
async def create_node(node_create: schemas.NodeCreate, project: Project = Depends(dep_project)) -> dict:
|
||||
"""
|
||||
Create a new node.
|
||||
|
||||
Set strict_names to get a 409 error instead of an automatic rename when the node name is already used.
|
||||
|
||||
Required privilege: Node.Allocate
|
||||
"""
|
||||
|
||||
return await _create_node(project, node_create)
|
||||
|
||||
|
||||
async def _create_node(project: Project, node_create: schemas.NodeCreate) -> dict:
|
||||
|
||||
controller = Controller.instance()
|
||||
compute = controller.get_compute(str(node_create.compute_id))
|
||||
node_data = jsonable_encoder(node_create, exclude_unset=True)
|
||||
@@ -155,6 +164,41 @@ async def create_node(node_create: schemas.NodeCreate, project: Project = Depend
|
||||
return node.asdict()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/batch",
|
||||
response_model=List[schemas.NodeBatchResult],
|
||||
responses={
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project"},
|
||||
},
|
||||
dependencies=[Depends(has_privilege("Node.Allocate"))],
|
||||
)
|
||||
async def create_nodes(
|
||||
nodes_create: List[schemas.NodeCreate], project: Project = Depends(dep_project)
|
||||
) -> List[schemas.NodeBatchResult]:
|
||||
"""
|
||||
Create several nodes in the order they are listed.
|
||||
|
||||
The response lists one result per node in the same order. A node that fails to be created
|
||||
does not stop the following ones.
|
||||
|
||||
Required privilege: Node.Allocate
|
||||
"""
|
||||
|
||||
results = []
|
||||
for node_create in nodes_create:
|
||||
try:
|
||||
node = await _create_node(project, node_create)
|
||||
results.append(schemas.NodeBatchResult(status_code=status.HTTP_201_CREATED, node=node))
|
||||
except ControllerError as e:
|
||||
log.error(f"Could not create node {node_create.name} in batch: {e}")
|
||||
results.append(
|
||||
schemas.NodeBatchResult(
|
||||
status_code=controller_error_status_code(e), error=schemas.ErrorMessage(message=str(e))
|
||||
)
|
||||
)
|
||||
return results
|
||||
|
||||
|
||||
@router.get(
|
||||
"",
|
||||
response_model=List[schemas.Node],
|
||||
|
||||
Reference in new issue
Block a user