mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-24 17:28:08 +00:00
Use black with -l 120 param.
This commit is contained in:
parent
f928738bd5
commit
c021e21309
@ -1,6 +1,9 @@
|
||||
GNS3-server
|
||||
===========
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
|
||||
.. image:: https://github.com/GNS3/gns3-server/workflows/testing/badge.svg
|
||||
:target: https://github.com/GNS3/gns3-server/actions?query=workflow%3Atesting
|
||||
|
||||
|
@ -27,7 +27,7 @@ from gns3server.compute.compute_error import (
|
||||
ComputeNotFoundError,
|
||||
ComputeTimeoutError,
|
||||
ComputeForbiddenError,
|
||||
ComputeUnauthorizedError
|
||||
ComputeUnauthorizedError,
|
||||
)
|
||||
|
||||
from . import capabilities
|
||||
@ -50,9 +50,11 @@ from . import vmware_nodes
|
||||
from . import vpcs_nodes
|
||||
|
||||
|
||||
compute_api = FastAPI(title="GNS3 compute API",
|
||||
description="This page describes the private compute API for GNS3. PLEASE DO NOT USE DIRECTLY!",
|
||||
version="v3")
|
||||
compute_api = FastAPI(
|
||||
title="GNS3 compute API",
|
||||
description="This page describes the private compute API for GNS3. PLEASE DO NOT USE DIRECTLY!",
|
||||
version="v3",
|
||||
)
|
||||
|
||||
|
||||
@compute_api.exception_handler(ComputeError)
|
||||
@ -141,16 +143,30 @@ compute_api.include_router(compute.router, tags=["Compute"])
|
||||
compute_api.include_router(notifications.router, tags=["Notifications"])
|
||||
compute_api.include_router(projects.router, tags=["Projects"])
|
||||
compute_api.include_router(images.router, tags=["Images"])
|
||||
compute_api.include_router(atm_switch_nodes.router, prefix="/projects/{project_id}/atm_switch/nodes", tags=["ATM switch"])
|
||||
compute_api.include_router(
|
||||
atm_switch_nodes.router, prefix="/projects/{project_id}/atm_switch/nodes", tags=["ATM switch"]
|
||||
)
|
||||
compute_api.include_router(cloud_nodes.router, prefix="/projects/{project_id}/cloud/nodes", tags=["Cloud nodes"])
|
||||
compute_api.include_router(docker_nodes.router, prefix="/projects/{project_id}/docker/nodes", tags=["Docker nodes"])
|
||||
compute_api.include_router(dynamips_nodes.router, prefix="/projects/{project_id}/dynamips/nodes", tags=["Dynamips nodes"])
|
||||
compute_api.include_router(ethernet_hub_nodes.router, prefix="/projects/{project_id}/ethernet_hub/nodes", tags=["Ethernet hub nodes"])
|
||||
compute_api.include_router(ethernet_switch_nodes.router, prefix="/projects/{project_id}/ethernet_switch/nodes", tags=["Ethernet switch nodes"])
|
||||
compute_api.include_router(frame_relay_switch_nodes.router, prefix="/projects/{project_id}/frame_relay_switch/nodes", tags=["Frame Relay switch nodes"])
|
||||
compute_api.include_router(
|
||||
dynamips_nodes.router, prefix="/projects/{project_id}/dynamips/nodes", tags=["Dynamips nodes"]
|
||||
)
|
||||
compute_api.include_router(
|
||||
ethernet_hub_nodes.router, prefix="/projects/{project_id}/ethernet_hub/nodes", tags=["Ethernet hub nodes"]
|
||||
)
|
||||
compute_api.include_router(
|
||||
ethernet_switch_nodes.router, prefix="/projects/{project_id}/ethernet_switch/nodes", tags=["Ethernet switch nodes"]
|
||||
)
|
||||
compute_api.include_router(
|
||||
frame_relay_switch_nodes.router,
|
||||
prefix="/projects/{project_id}/frame_relay_switch/nodes",
|
||||
tags=["Frame Relay switch nodes"],
|
||||
)
|
||||
compute_api.include_router(iou_nodes.router, prefix="/projects/{project_id}/iou/nodes", tags=["IOU nodes"])
|
||||
compute_api.include_router(nat_nodes.router, prefix="/projects/{project_id}/nat/nodes", tags=["NAT nodes"])
|
||||
compute_api.include_router(qemu_nodes.router, prefix="/projects/{project_id}/qemu/nodes", tags=["Qemu nodes"])
|
||||
compute_api.include_router(virtualbox_nodes.router, prefix="/projects/{project_id}/virtualbox/nodes", tags=["VirtualBox nodes"])
|
||||
compute_api.include_router(
|
||||
virtualbox_nodes.router, prefix="/projects/{project_id}/virtualbox/nodes", tags=["VirtualBox nodes"]
|
||||
)
|
||||
compute_api.include_router(vmware_nodes.router, prefix="/projects/{project_id}/vmware/nodes", tags=["VMware nodes"])
|
||||
compute_api.include_router(vpcs_nodes.router, prefix="/projects/{project_id}/vpcs/nodes", tags=["VPCS nodes"])
|
||||
|
@ -29,9 +29,7 @@ from gns3server import schemas
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.compute.dynamips.nodes.atm_switch import ATMSwitch
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or ATM switch node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or ATM switch node"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -46,10 +44,12 @@ async def dep_node(project_id: UUID, node_id: UUID):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
response_model=schemas.ATMSwitch,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create ATM switch node"}})
|
||||
@router.post(
|
||||
"",
|
||||
response_model=schemas.ATMSwitch,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create ATM switch node"}},
|
||||
)
|
||||
async def create_atm_switch(project_id: UUID, node_data: schemas.ATMSwitchCreate):
|
||||
"""
|
||||
Create a new ATM switch node.
|
||||
@ -58,16 +58,17 @@ async def create_atm_switch(project_id: UUID, node_data: schemas.ATMSwitchCreate
|
||||
# Use the Dynamips ATM switch to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await dynamips_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="atm_switch",
|
||||
mappings=node_data.get("mappings"))
|
||||
node = await dynamips_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="atm_switch",
|
||||
mappings=node_data.get("mappings"),
|
||||
)
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.ATMSwitch)
|
||||
@router.get("/{node_id}", response_model=schemas.ATMSwitch)
|
||||
def get_atm_switch(node: ATMSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Return an ATM switch node.
|
||||
@ -76,9 +77,7 @@ def get_atm_switch(node: ATMSwitch = Depends(dep_node)):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.ATMSwitch,
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.ATMSwitch, status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_atm_switch(destination_node_id: UUID = Body(..., embed=True), node: ATMSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Duplicate an ATM switch node.
|
||||
@ -88,8 +87,7 @@ async def duplicate_atm_switch(destination_node_id: UUID = Body(..., embed=True)
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.ATMSwitch)
|
||||
@router.put("/{node_id}", response_model=schemas.ATMSwitch)
|
||||
async def update_atm_switch(node_data: schemas.ATMSwitchUpdate, node: ATMSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Update an ATM switch node.
|
||||
@ -104,8 +102,7 @@ async def update_atm_switch(node_data: schemas.ATMSwitchUpdate, node: ATMSwitch
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_atm_switch_node(node: ATMSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Delete an ATM switch node.
|
||||
@ -114,8 +111,7 @@ async def delete_atm_switch_node(node: ATMSwitch = Depends(dep_node)):
|
||||
await Dynamips.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def start_atm_switch(node: ATMSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Start an ATM switch node.
|
||||
@ -125,8 +121,7 @@ def start_atm_switch(node: ATMSwitch = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def stop_atm_switch(node: ATMSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Stop an ATM switch node.
|
||||
@ -136,8 +131,7 @@ def stop_atm_switch(node: ATMSwitch = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Suspend an ATM switch node.
|
||||
@ -147,13 +141,14 @@ def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
async def create_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: ATMSwitch = Depends(dep_node)):
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
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.
|
||||
@ -164,8 +159,7 @@ async def create_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
@ -177,10 +171,9 @@ async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = De
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: ATMSwitch = Depends(dep_node)):
|
||||
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.
|
||||
@ -191,8 +184,10 @@ async def start_capture(adapter_number: int,
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop_capture",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
|
@ -31,18 +31,18 @@ from gns3server import schemas
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/capabilities",
|
||||
response_model=schemas.Capabilities
|
||||
)
|
||||
@router.get("/capabilities", response_model=schemas.Capabilities)
|
||||
def get_capabilities():
|
||||
|
||||
node_types = []
|
||||
for module in MODULES:
|
||||
node_types.extend(module.node_types())
|
||||
|
||||
return {"version": __version__,
|
||||
"platform": sys.platform,
|
||||
"cpus": psutil.cpu_count(logical=True),
|
||||
"memory": psutil.virtual_memory().total,
|
||||
"disk_size": psutil.disk_usage(get_default_project_directory()).total,
|
||||
"node_types": node_types}
|
||||
return {
|
||||
"version": __version__,
|
||||
"platform": sys.platform,
|
||||
"cpus": psutil.cpu_count(logical=True),
|
||||
"memory": psutil.virtual_memory().total,
|
||||
"disk_size": psutil.disk_usage(get_default_project_directory()).total,
|
||||
"node_types": node_types,
|
||||
}
|
||||
|
@ -30,9 +30,7 @@ from gns3server import schemas
|
||||
from gns3server.compute.builtin import Builtin
|
||||
from gns3server.compute.builtin.nodes.cloud import Cloud
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or cloud node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or cloud node"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -47,10 +45,12 @@ def dep_node(project_id: UUID, node_id: UUID):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
response_model=schemas.Cloud,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create cloud node"}})
|
||||
@router.post(
|
||||
"",
|
||||
response_model=schemas.Cloud,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create cloud node"}},
|
||||
)
|
||||
async def create_cloud(project_id: UUID, node_data: schemas.CloudCreate):
|
||||
"""
|
||||
Create a new cloud node.
|
||||
@ -58,11 +58,13 @@ async def create_cloud(project_id: UUID, node_data: schemas.CloudCreate):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await builtin_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="cloud",
|
||||
ports=node_data.get("ports_mapping"))
|
||||
node = await builtin_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="cloud",
|
||||
ports=node_data.get("ports_mapping"),
|
||||
)
|
||||
|
||||
# add the remote console settings
|
||||
node.remote_console_host = node_data.get("remote_console_host", node.remote_console_host)
|
||||
@ -73,8 +75,7 @@ async def create_cloud(project_id: UUID, node_data: schemas.CloudCreate):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.Cloud)
|
||||
@router.get("/{node_id}", response_model=schemas.Cloud)
|
||||
def get_cloud(node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Return a cloud node.
|
||||
@ -83,8 +84,7 @@ def get_cloud(node: Cloud = Depends(dep_node)):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.Cloud)
|
||||
@router.put("/{node_id}", response_model=schemas.Cloud)
|
||||
def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Update a cloud node.
|
||||
@ -98,8 +98,7 @@ def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_cloud(node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Delete a cloud node.
|
||||
@ -108,8 +107,7 @@ async def delete_cloud(node: Cloud = Depends(dep_node)):
|
||||
await Builtin.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_cloud(node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Start a cloud node.
|
||||
@ -118,8 +116,7 @@ async def start_cloud(node: Cloud = Depends(dep_node)):
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_cloud(node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Stop a cloud node.
|
||||
@ -129,8 +126,7 @@ async def stop_cloud(node: Cloud = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def suspend_cloud(node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Suspend a cloud node.
|
||||
@ -140,13 +136,17 @@ async def suspend_cloud(node: Cloud = Depends(dep_node)):
|
||||
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])
|
||||
async def create_cloud_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: Cloud = Depends(dep_node)):
|
||||
@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],
|
||||
)
|
||||
async def create_cloud_nio(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
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.
|
||||
@ -157,13 +157,17 @@ async def create_cloud_nio(adapter_number: int,
|
||||
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])
|
||||
async def update_cloud_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: Cloud = Depends(dep_node)):
|
||||
@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],
|
||||
)
|
||||
async def update_cloud_nio(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
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.
|
||||
@ -176,8 +180,7 @@ async def update_cloud_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_cloud_nio(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
@ -188,10 +191,9 @@ async def delete_cloud_nio(adapter_number: int, port_number: int, node: Cloud =
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_cloud_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: Cloud = Depends(dep_node)):
|
||||
async def start_cloud_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.
|
||||
@ -202,8 +204,9 @@ async def start_cloud_capture(adapter_number: int,
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_cloud_capture(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
|
@ -42,8 +42,7 @@ from typing import Optional, List
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/projects/{project_id}/ports/udp",
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/projects/{project_id}/ports/udp", status_code=status.HTTP_201_CREATED)
|
||||
def allocate_udp_port(project_id: UUID) -> dict:
|
||||
"""
|
||||
Allocate an UDP port on the compute.
|
||||
@ -106,19 +105,21 @@ def compute_statistics() -> dict:
|
||||
disk_usage_percent = int(psutil.disk_usage(get_default_project_directory()).percent)
|
||||
except psutil.Error as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
#raise HTTPConflict(text="Psutil error detected: {}".format(e))
|
||||
# raise HTTPConflict(text="Psutil error detected: {}".format(e))
|
||||
|
||||
return {"memory_total": memory_total,
|
||||
"memory_free": memory_free,
|
||||
"memory_used": memory_used,
|
||||
"swap_total": swap_total,
|
||||
"swap_free": swap_free,
|
||||
"swap_used": swap_used,
|
||||
"cpu_usage_percent": cpu_percent,
|
||||
"memory_usage_percent": memory_percent,
|
||||
"swap_usage_percent": swap_percent,
|
||||
"disk_usage_percent": disk_usage_percent,
|
||||
"load_average_percent": load_average_percent}
|
||||
return {
|
||||
"memory_total": memory_total,
|
||||
"memory_free": memory_free,
|
||||
"memory_used": memory_used,
|
||||
"swap_total": swap_total,
|
||||
"swap_free": swap_free,
|
||||
"swap_used": swap_used,
|
||||
"cpu_usage_percent": cpu_percent,
|
||||
"memory_usage_percent": memory_percent,
|
||||
"swap_usage_percent": swap_percent,
|
||||
"disk_usage_percent": disk_usage_percent,
|
||||
"load_average_percent": load_average_percent,
|
||||
}
|
||||
|
||||
|
||||
@router.get("/qemu/binaries")
|
||||
@ -142,9 +143,11 @@ async def get_qemu_capabilities() -> dict:
|
||||
return capabilities
|
||||
|
||||
|
||||
@router.post("/qemu/img",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to create Qemu image"}})
|
||||
@router.post(
|
||||
"/qemu/img",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to create Qemu image"}},
|
||||
)
|
||||
async def create_qemu_image(image_data: schemas.QemuImageCreate):
|
||||
"""
|
||||
Create a Qemu image.
|
||||
@ -154,12 +157,16 @@ async def create_qemu_image(image_data: schemas.QemuImageCreate):
|
||||
if Config.instance().settings.Server.local is False:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
await Qemu.instance().create_disk(image_data.qemu_img, image_data.path, jsonable_encoder(image_data, exclude_unset=True))
|
||||
await Qemu.instance().create_disk(
|
||||
image_data.qemu_img, image_data.path, jsonable_encoder(image_data, exclude_unset=True)
|
||||
)
|
||||
|
||||
|
||||
@router.put("/qemu/img",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to update Qemu image"}})
|
||||
@router.put(
|
||||
"/qemu/img",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to update Qemu image"}},
|
||||
)
|
||||
async def update_qemu_image(image_data: schemas.QemuImageUpdate):
|
||||
"""
|
||||
Update a Qemu image.
|
||||
@ -173,17 +180,14 @@ async def update_qemu_image(image_data: schemas.QemuImageUpdate):
|
||||
await Qemu.instance().resize_disk(image_data.qemu_img, image_data.path, image_data.extend)
|
||||
|
||||
|
||||
@router.get("/virtualbox/vms",
|
||||
response_model=List[dict])
|
||||
@router.get("/virtualbox/vms", response_model=List[dict])
|
||||
async def get_virtualbox_vms():
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
return await vbox_manager.list_vms()
|
||||
|
||||
|
||||
@router.get("/vmware/vms",
|
||||
response_model=List[dict])
|
||||
@router.get("/vmware/vms", response_model=List[dict])
|
||||
async def get_vms():
|
||||
vmware_manager = VMware.instance()
|
||||
return await vmware_manager.list_vms()
|
||||
|
||||
|
@ -29,9 +29,7 @@ from gns3server import schemas
|
||||
from gns3server.compute.docker import Docker
|
||||
from gns3server.compute.docker.docker_vm import DockerVM
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or Docker node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Docker node"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -46,10 +44,12 @@ def dep_node(project_id: UUID, node_id: UUID):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
response_model=schemas.Docker,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}})
|
||||
@router.post(
|
||||
"",
|
||||
response_model=schemas.Docker,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Docker node"}},
|
||||
)
|
||||
async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate):
|
||||
"""
|
||||
Create a new Docker node.
|
||||
@ -57,24 +57,26 @@ async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate):
|
||||
|
||||
docker_manager = Docker.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
container = await docker_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
image=node_data.pop("image"),
|
||||
start_command=node_data.get("start_command"),
|
||||
environment=node_data.get("environment"),
|
||||
adapters=node_data.get("adapters"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type"),
|
||||
console_resolution=node_data.get("console_resolution", "1024x768"),
|
||||
console_http_port=node_data.get("console_http_port", 80),
|
||||
console_http_path=node_data.get("console_http_path", "/"),
|
||||
aux=node_data.get("aux"),
|
||||
aux_type=node_data.pop("aux_type", "none"),
|
||||
extra_hosts=node_data.get("extra_hosts"),
|
||||
extra_volumes=node_data.get("extra_volumes"),
|
||||
memory=node_data.get("memory", 0),
|
||||
cpus=node_data.get("cpus", 0))
|
||||
container = await docker_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
image=node_data.pop("image"),
|
||||
start_command=node_data.get("start_command"),
|
||||
environment=node_data.get("environment"),
|
||||
adapters=node_data.get("adapters"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type"),
|
||||
console_resolution=node_data.get("console_resolution", "1024x768"),
|
||||
console_http_port=node_data.get("console_http_port", 80),
|
||||
console_http_path=node_data.get("console_http_path", "/"),
|
||||
aux=node_data.get("aux"),
|
||||
aux_type=node_data.pop("aux_type", "none"),
|
||||
extra_hosts=node_data.get("extra_hosts"),
|
||||
extra_volumes=node_data.get("extra_volumes"),
|
||||
memory=node_data.get("memory", 0),
|
||||
cpus=node_data.get("cpus", 0),
|
||||
)
|
||||
for name, value in node_data.items():
|
||||
if name != "node_id":
|
||||
if hasattr(container, name) and getattr(container, name) != value:
|
||||
@ -83,8 +85,7 @@ async def create_docker_node(project_id: UUID, node_data: schemas.DockerCreate):
|
||||
return container.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.Docker)
|
||||
@router.get("/{node_id}", response_model=schemas.Docker)
|
||||
def get_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Return a Docker node.
|
||||
@ -93,18 +94,28 @@ def get_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.Docker)
|
||||
@router.put("/{node_id}", response_model=schemas.Docker)
|
||||
async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Update a Docker node.
|
||||
"""
|
||||
|
||||
props = [
|
||||
"name", "console", "console_type", "aux", "aux_type", "console_resolution",
|
||||
"console_http_port", "console_http_path", "start_command",
|
||||
"environment", "adapters", "extra_hosts", "extra_volumes",
|
||||
"memory", "cpus"
|
||||
"name",
|
||||
"console",
|
||||
"console_type",
|
||||
"aux",
|
||||
"aux_type",
|
||||
"console_resolution",
|
||||
"console_http_port",
|
||||
"console_http_path",
|
||||
"start_command",
|
||||
"environment",
|
||||
"adapters",
|
||||
"extra_hosts",
|
||||
"extra_volumes",
|
||||
"memory",
|
||||
"cpus",
|
||||
]
|
||||
|
||||
changed = False
|
||||
@ -120,8 +131,7 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Start a Docker node.
|
||||
@ -130,8 +140,7 @@ async def start_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a Docker node.
|
||||
@ -140,8 +149,7 @@ async def stop_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
await node.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def suspend_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Suspend a Docker node.
|
||||
@ -150,8 +158,7 @@ async def suspend_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
await node.pause()
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reload_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Reload a Docker node.
|
||||
@ -160,8 +167,7 @@ async def reload_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
await node.restart()
|
||||
|
||||
|
||||
@router.post("/{node_id}/pause",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/pause", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def pause_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Pause a Docker node.
|
||||
@ -170,8 +176,7 @@ async def pause_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
await node.pause()
|
||||
|
||||
|
||||
@router.post("/{node_id}/unpause",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/unpause", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def unpause_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Unpause a Docker node.
|
||||
@ -180,8 +185,7 @@ async def unpause_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
await node.unpause()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a Docker node.
|
||||
@ -190,9 +194,7 @@ async def delete_docker_node(node: DockerVM = Depends(dep_node)):
|
||||
await node.delete()
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.Docker,
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.Docker, status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_docker_node(destination_node_id: UUID = Body(..., embed=True), node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Duplicate a Docker node.
|
||||
@ -202,13 +204,14 @@ async def duplicate_docker_node(destination_node_id: UUID = Body(..., embed=True
|
||||
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)
|
||||
async def create_docker_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: DockerVM = Depends(dep_node)):
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def create_docker_node_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.
|
||||
@ -219,12 +222,14 @@ async def create_docker_node_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
async def update_docker_node_nio(adapter_number: int,
|
||||
port_number: int, nio_data: schemas.UDPNIO,
|
||||
node: DockerVM = Depends(dep_node)):
|
||||
@router.put(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def update_docker_node_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.
|
||||
@ -237,8 +242,7 @@ async def update_docker_node_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_docker_node_nio(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
@ -249,10 +253,9 @@ async def delete_docker_node_nio(adapter_number: int, port_number: int, node: Do
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_docker_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: DockerVM = Depends(dep_node)):
|
||||
async def start_docker_node_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.
|
||||
@ -263,8 +266,9 @@ async def start_docker_node_capture(adapter_number: int,
|
||||
return {"pcap_file_path": str(pcap_file_path)}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_docker_node_capture(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@ -295,8 +299,7 @@ async def console_ws(websocket: WebSocket, node: DockerVM = Depends(dep_node)):
|
||||
await node.start_websocket_console(websocket)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: DockerVM = Depends(dep_node)):
|
||||
|
||||
await node.reset_console()
|
||||
|
@ -32,19 +32,12 @@ from gns3server.compute.dynamips.nodes.router import Router
|
||||
from gns3server.compute.dynamips.dynamips_error import DynamipsError
|
||||
from gns3server import schemas
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
|
||||
|
||||
DEFAULT_CHASSIS = {
|
||||
"c1700": "1720",
|
||||
"c2600": "2610",
|
||||
"c3600": "3640"
|
||||
}
|
||||
DEFAULT_CHASSIS = {"c1700": "1720", "c2600": "2610", "c3600": "3640"}
|
||||
|
||||
|
||||
def dep_node(project_id: UUID, node_id: UUID):
|
||||
@ -57,10 +50,12 @@ def dep_node(project_id: UUID, node_id: UUID):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
response_model=schemas.Dynamips,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Dynamips node"}})
|
||||
@router.post(
|
||||
"",
|
||||
response_model=schemas.Dynamips,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Dynamips node"}},
|
||||
)
|
||||
async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate):
|
||||
"""
|
||||
Create a new Dynamips router.
|
||||
@ -72,23 +67,24 @@ async def create_router(project_id: UUID, node_data: schemas.DynamipsCreate):
|
||||
if not node_data.chassis and platform in DEFAULT_CHASSIS:
|
||||
chassis = DEFAULT_CHASSIS[platform]
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await dynamips_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
dynamips_id=node_data.get("dynamips_id"),
|
||||
platform=platform,
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
aux=node_data.get("aux"),
|
||||
aux_type=node_data.pop("aux_type", "none"),
|
||||
chassis=chassis,
|
||||
node_type="dynamips")
|
||||
vm = await dynamips_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
dynamips_id=node_data.get("dynamips_id"),
|
||||
platform=platform,
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
aux=node_data.get("aux"),
|
||||
aux_type=node_data.pop("aux_type", "none"),
|
||||
chassis=chassis,
|
||||
node_type="dynamips",
|
||||
)
|
||||
await dynamips_manager.update_vm_settings(vm, node_data)
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.Dynamips)
|
||||
@router.get("/{node_id}", response_model=schemas.Dynamips)
|
||||
def get_router(node: Router = Depends(dep_node)):
|
||||
"""
|
||||
Return Dynamips router.
|
||||
@ -97,8 +93,7 @@ def get_router(node: Router = Depends(dep_node)):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.Dynamips)
|
||||
@router.put("/{node_id}", response_model=schemas.Dynamips)
|
||||
async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depends(dep_node)):
|
||||
"""
|
||||
Update a Dynamips router.
|
||||
@ -109,8 +104,7 @@ async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depend
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_router(node: Router = Depends(dep_node)):
|
||||
"""
|
||||
Delete a Dynamips router.
|
||||
@ -119,8 +113,7 @@ async def delete_router(node: Router = Depends(dep_node)):
|
||||
await Dynamips.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_router(node: Router = Depends(dep_node)):
|
||||
"""
|
||||
Start a Dynamips router.
|
||||
@ -133,8 +126,7 @@ async def start_router(node: Router = Depends(dep_node)):
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_router(node: Router = Depends(dep_node)):
|
||||
"""
|
||||
Stop a Dynamips router.
|
||||
@ -143,15 +135,13 @@ async def stop_router(node: Router = Depends(dep_node)):
|
||||
await node.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def suspend_router(node: Router = Depends(dep_node)):
|
||||
|
||||
await node.suspend()
|
||||
|
||||
|
||||
@router.post("/{node_id}/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def resume_router(node: Router = Depends(dep_node)):
|
||||
"""
|
||||
Resume a suspended Dynamips router.
|
||||
@ -160,8 +150,7 @@ async def resume_router(node: Router = Depends(dep_node)):
|
||||
await node.resume()
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reload_router(node: Router = Depends(dep_node)):
|
||||
"""
|
||||
Reload a suspended Dynamips router.
|
||||
@ -170,9 +159,11 @@ async def reload_router(node: Router = Depends(dep_node)):
|
||||
await node.reload()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
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.
|
||||
@ -183,9 +174,11 @@ async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UD
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
@router.put(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
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.
|
||||
@ -198,8 +191,7 @@ async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UD
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: Router = Depends(dep_node)):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
@ -210,29 +202,31 @@ async def delete_nio(adapter_number: int, port_number: int, node: Router = Depen
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: Router = Depends(dep_node)):
|
||||
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.
|
||||
"""
|
||||
|
||||
pcap_file_path = os.path.join(node.project.capture_working_directory(), node_capture_data.capture_file_name)
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
if sys.platform.startswith("win"):
|
||||
# FIXME: Dynamips (Cygwin actually) doesn't like non ascii paths on Windows
|
||||
try:
|
||||
pcap_file_path.encode('ascii')
|
||||
pcap_file_path.encode("ascii")
|
||||
except UnicodeEncodeError:
|
||||
raise DynamipsError(f"The capture file path '{pcap_file_path}' must only contain ASCII (English) characters")
|
||||
raise DynamipsError(
|
||||
f"The capture file path '{pcap_file_path}' must only contain ASCII (English) characters"
|
||||
)
|
||||
|
||||
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}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: Router = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@ -272,8 +266,7 @@ async def get_auto_idlepc(node: Router = Depends(dep_node)) -> dict:
|
||||
return {"idlepc": idlepc}
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/{node_id}/duplicate", status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_router(destination_node_id: UUID, node: Router = Depends(dep_node)):
|
||||
"""
|
||||
Duplicate a router.
|
||||
@ -292,8 +285,7 @@ async def console_ws(websocket: WebSocket, node: Router = Depends(dep_node)):
|
||||
await node.start_websocket_console(websocket)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: Router = Depends(dep_node)):
|
||||
|
||||
await node.reset_console()
|
||||
|
@ -29,9 +29,7 @@ from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.compute.dynamips.nodes.ethernet_hub import EthernetHub
|
||||
from gns3server import schemas
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or Ethernet hub node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Ethernet hub node"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -46,10 +44,12 @@ def dep_node(project_id: UUID, node_id: UUID):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
response_model=schemas.EthernetHub,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Ethernet hub node"}})
|
||||
@router.post(
|
||||
"",
|
||||
response_model=schemas.EthernetHub,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Ethernet hub node"}},
|
||||
)
|
||||
async def create_ethernet_hub(project_id: UUID, node_data: schemas.EthernetHubCreate):
|
||||
"""
|
||||
Create a new Ethernet hub.
|
||||
@ -58,16 +58,17 @@ async def create_ethernet_hub(project_id: UUID, node_data: schemas.EthernetHubCr
|
||||
# Use the Dynamips Ethernet hub to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await dynamips_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="ethernet_hub",
|
||||
ports=node_data.get("ports_mapping"))
|
||||
node = await dynamips_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="ethernet_hub",
|
||||
ports=node_data.get("ports_mapping"),
|
||||
)
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.EthernetHub)
|
||||
@router.get("/{node_id}", response_model=schemas.EthernetHub)
|
||||
def get_ethernet_hub(node: EthernetHub = Depends(dep_node)):
|
||||
"""
|
||||
Return an Ethernet hub.
|
||||
@ -76,11 +77,10 @@ def get_ethernet_hub(node: EthernetHub = Depends(dep_node)):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.EthernetHub,
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_ethernet_hub(destination_node_id: UUID = Body(..., embed=True),
|
||||
node: EthernetHub = Depends(dep_node)):
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.EthernetHub, status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_ethernet_hub(
|
||||
destination_node_id: UUID = Body(..., embed=True), node: EthernetHub = Depends(dep_node)
|
||||
):
|
||||
"""
|
||||
Duplicate an Ethernet hub.
|
||||
"""
|
||||
@ -89,8 +89,7 @@ async def duplicate_ethernet_hub(destination_node_id: UUID = Body(..., embed=Tru
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.EthernetHub)
|
||||
@router.put("/{node_id}", response_model=schemas.EthernetHub)
|
||||
async def update_ethernet_hub(node_data: schemas.EthernetHubUpdate, node: EthernetHub = Depends(dep_node)):
|
||||
"""
|
||||
Update an Ethernet hub.
|
||||
@ -105,8 +104,7 @@ async def update_ethernet_hub(node_data: schemas.EthernetHubUpdate, node: Ethern
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_ethernet_hub(node: EthernetHub = Depends(dep_node)):
|
||||
"""
|
||||
Delete an Ethernet hub.
|
||||
@ -115,8 +113,7 @@ async def delete_ethernet_hub(node: EthernetHub = Depends(dep_node)):
|
||||
await Dynamips.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def start_ethernet_hub(node: EthernetHub = Depends(dep_node)):
|
||||
"""
|
||||
Start an Ethernet hub.
|
||||
@ -126,8 +123,7 @@ def start_ethernet_hub(node: EthernetHub = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)):
|
||||
"""
|
||||
Stop an Ethernet hub.
|
||||
@ -137,8 +133,7 @@ def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)):
|
||||
"""
|
||||
Suspend an Ethernet hub.
|
||||
@ -148,13 +143,14 @@ def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
async def create_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: EthernetHub = Depends(dep_node)):
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
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.
|
||||
@ -165,8 +161,7 @@ async def create_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: EthernetHub = Depends(dep_node)):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
@ -178,10 +173,9 @@ async def delete_nio(adapter_number: int, port_number: int, node: EthernetHub =
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: EthernetHub = Depends(dep_node)):
|
||||
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.
|
||||
@ -192,8 +186,9 @@ async def start_capture(adapter_number: int,
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: EthernetHub = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
|
@ -29,9 +29,7 @@ from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.compute.dynamips.nodes.ethernet_switch import EthernetSwitch
|
||||
from gns3server import schemas
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or Ethernet switch node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Ethernet switch node"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -46,10 +44,12 @@ def dep_node(project_id: UUID, node_id: UUID):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
response_model=schemas.EthernetSwitch,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Ethernet switch node"}})
|
||||
@router.post(
|
||||
"",
|
||||
response_model=schemas.EthernetSwitch,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
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.
|
||||
@ -58,29 +58,29 @@ async def create_ethernet_switch(project_id: UUID, node_data: schemas.EthernetSw
|
||||
# Use the Dynamips Ethernet switch to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await dynamips_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type"),
|
||||
node_type="ethernet_switch",
|
||||
ports=node_data.get("ports_mapping"))
|
||||
node = await dynamips_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type"),
|
||||
node_type="ethernet_switch",
|
||||
ports=node_data.get("ports_mapping"),
|
||||
)
|
||||
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.EthernetSwitch)
|
||||
@router.get("/{node_id}", response_model=schemas.EthernetSwitch)
|
||||
def get_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
|
||||
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.EthernetSwitch,
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_ethernet_switch(destination_node_id: UUID = Body(..., embed=True),
|
||||
node: EthernetSwitch = Depends(dep_node)):
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.EthernetSwitch, status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_ethernet_switch(
|
||||
destination_node_id: UUID = Body(..., embed=True), node: EthernetSwitch = Depends(dep_node)
|
||||
):
|
||||
"""
|
||||
Duplicate an Ethernet switch.
|
||||
"""
|
||||
@ -89,8 +89,7 @@ async def duplicate_ethernet_switch(destination_node_id: UUID = Body(..., embed=
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.EthernetSwitch)
|
||||
@router.put("/{node_id}", response_model=schemas.EthernetSwitch)
|
||||
async def update_ethernet_switch(node_data: schemas.EthernetSwitchUpdate, node: EthernetSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Update an Ethernet switch.
|
||||
@ -108,8 +107,7 @@ async def update_ethernet_switch(node_data: schemas.EthernetSwitchUpdate, node:
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Delete an Ethernet switch.
|
||||
@ -118,8 +116,7 @@ async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
|
||||
await Dynamips.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Start an Ethernet switch.
|
||||
@ -129,8 +126,7 @@ def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Stop an Ethernet switch.
|
||||
@ -140,8 +136,7 @@ def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Suspend an Ethernet switch.
|
||||
@ -151,21 +146,21 @@ def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
async def create_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: EthernetSwitch = Depends(dep_node)):
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def create_nio(
|
||||
adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: EthernetSwitch = Depends(dep_node)
|
||||
):
|
||||
|
||||
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)
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: EthernetSwitch = Depends(dep_node)):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
@ -177,10 +172,12 @@ async def delete_nio(adapter_number: int, port_number: int, node: EthernetSwitch
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: EthernetSwitch = Depends(dep_node)):
|
||||
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.
|
||||
@ -191,9 +188,10 @@ async def start_capture(adapter_number: int,
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_capture(adapter_number: int,port_number: int, node: EthernetSwitch = Depends(dep_node)):
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
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.
|
||||
|
@ -29,9 +29,7 @@ from gns3server import schemas
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.compute.dynamips.nodes.frame_relay_switch import FrameRelaySwitch
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or Frame Relay switch node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Frame Relay switch node"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -46,10 +44,12 @@ def dep_node(project_id: UUID, node_id: UUID):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
response_model=schemas.FrameRelaySwitch,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Frame Relay switch node"}})
|
||||
@router.post(
|
||||
"",
|
||||
response_model=schemas.FrameRelaySwitch,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Frame Relay switch node"}},
|
||||
)
|
||||
async def create_frame_relay_switch(project_id: UUID, node_data: schemas.FrameRelaySwitchCreate):
|
||||
"""
|
||||
Create a new Frame Relay switch node.
|
||||
@ -58,16 +58,17 @@ async def create_frame_relay_switch(project_id: UUID, node_data: schemas.FrameRe
|
||||
# Use the Dynamips Frame Relay switch to simulate this node
|
||||
dynamips_manager = Dynamips.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await dynamips_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="frame_relay_switch",
|
||||
mappings=node_data.get("mappings"))
|
||||
node = await dynamips_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="frame_relay_switch",
|
||||
mappings=node_data.get("mappings"),
|
||||
)
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.FrameRelaySwitch)
|
||||
@router.get("/{node_id}", response_model=schemas.FrameRelaySwitch)
|
||||
def get_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
|
||||
"""
|
||||
Return a Frame Relay switch node.
|
||||
@ -76,11 +77,10 @@ def get_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.FrameRelaySwitch,
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_frame_relay_switch(destination_node_id: UUID = Body(..., embed=True),
|
||||
node: FrameRelaySwitch = Depends(dep_node)):
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.FrameRelaySwitch, status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_frame_relay_switch(
|
||||
destination_node_id: UUID = Body(..., embed=True), node: FrameRelaySwitch = Depends(dep_node)
|
||||
):
|
||||
"""
|
||||
Duplicate a Frame Relay switch node.
|
||||
"""
|
||||
@ -89,10 +89,10 @@ async def duplicate_frame_relay_switch(destination_node_id: UUID = Body(..., emb
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.FrameRelaySwitch)
|
||||
async def update_frame_relay_switch(node_data: schemas.FrameRelaySwitchUpdate,
|
||||
node: FrameRelaySwitch = Depends(dep_node)):
|
||||
@router.put("/{node_id}", response_model=schemas.FrameRelaySwitch)
|
||||
async def update_frame_relay_switch(
|
||||
node_data: schemas.FrameRelaySwitchUpdate, node: FrameRelaySwitch = Depends(dep_node)
|
||||
):
|
||||
"""
|
||||
Update an Frame Relay switch node.
|
||||
"""
|
||||
@ -106,8 +106,7 @@ async def update_frame_relay_switch(node_data: schemas.FrameRelaySwitchUpdate,
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
|
||||
"""
|
||||
Delete a Frame Relay switch node.
|
||||
@ -116,8 +115,7 @@ async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
|
||||
await Dynamips.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
|
||||
"""
|
||||
Start a Frame Relay switch node.
|
||||
@ -127,8 +125,7 @@ def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
|
||||
"""
|
||||
Stop a Frame Relay switch node.
|
||||
@ -138,8 +135,7 @@ def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
|
||||
"""
|
||||
Suspend a Frame Relay switch node.
|
||||
@ -149,13 +145,14 @@ def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
async def create_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: FrameRelaySwitch = Depends(dep_node)):
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
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.
|
||||
@ -166,8 +163,7 @@ async def create_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: FrameRelaySwitch = Depends(dep_node)):
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
@ -179,10 +175,12 @@ async def delete_nio(adapter_number: int, port_number: int, node: FrameRelaySwit
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: FrameRelaySwitch = Depends(dep_node)):
|
||||
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.
|
||||
@ -193,8 +191,9 @@ async def start_capture(adapter_number: int,
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: FrameRelaySwitch = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
|
@ -53,8 +53,7 @@ async def get_dynamips_images() -> List[str]:
|
||||
return await dynamips_manager.list_images()
|
||||
|
||||
|
||||
@router.post("/dynamips/images/{filename:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/dynamips/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def upload_dynamips_image(filename: str, request: Request):
|
||||
"""
|
||||
Upload a Dynamips IOS image.
|
||||
@ -93,8 +92,7 @@ async def get_iou_images() -> List[str]:
|
||||
return await iou_manager.list_images()
|
||||
|
||||
|
||||
@router.post("/iou/images/{filename:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/iou/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def upload_iou_image(filename: str, request: Request):
|
||||
"""
|
||||
Upload an IOU image.
|
||||
@ -130,8 +128,7 @@ async def get_qemu_images() -> List[str]:
|
||||
return await qemu_manager.list_images()
|
||||
|
||||
|
||||
@router.post("/qemu/images/{filename:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/qemu/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def upload_qemu_image(filename: str, request: Request):
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
|
@ -30,9 +30,7 @@ from gns3server import schemas
|
||||
from gns3server.compute.iou import IOU
|
||||
from gns3server.compute.iou.iou_vm import IOUVM
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or IOU node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or IOU node"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -47,10 +45,12 @@ def dep_node(project_id: UUID, node_id: UUID):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
response_model=schemas.IOU,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create IOU node"}})
|
||||
@router.post(
|
||||
"",
|
||||
response_model=schemas.IOU,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create IOU node"}},
|
||||
)
|
||||
async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate):
|
||||
"""
|
||||
Create a new IOU node.
|
||||
@ -58,13 +58,15 @@ async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate):
|
||||
|
||||
iou = IOU.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await iou.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
application_id=node_data.get("application_id"),
|
||||
path=node_data.get("path"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type", "telnet"))
|
||||
vm = await iou.create_node(
|
||||
node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
application_id=node_data.get("application_id"),
|
||||
path=node_data.get("path"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
)
|
||||
|
||||
for name, value in node_data.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
@ -80,8 +82,7 @@ async def create_iou_node(project_id: UUID, node_data: schemas.IOUCreate):
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.IOU)
|
||||
@router.get("/{node_id}", response_model=schemas.IOU)
|
||||
def get_iou_node(node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Return an IOU node.
|
||||
@ -90,8 +91,7 @@ def get_iou_node(node: IOUVM = Depends(dep_node)):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.IOU)
|
||||
@router.put("/{node_id}", response_model=schemas.IOU)
|
||||
async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Update an IOU node.
|
||||
@ -112,8 +112,7 @@ async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(de
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_iou_node(node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete an IOU node.
|
||||
@ -122,9 +121,7 @@ async def delete_iou_node(node: IOUVM = Depends(dep_node)):
|
||||
await IOU.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.IOU,
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.IOU, status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_iou_node(destination_node_id: UUID = Body(..., embed=True), node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Duplicate an IOU node.
|
||||
@ -134,8 +131,7 @@ async def duplicate_iou_node(destination_node_id: UUID = Body(..., embed=True),
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Start an IOU node.
|
||||
@ -150,8 +146,7 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_iou_node(node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop an IOU node.
|
||||
@ -160,8 +155,7 @@ async def stop_iou_node(node: IOUVM = Depends(dep_node)):
|
||||
await node.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def suspend_iou_node(node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Suspend an IOU node.
|
||||
@ -171,8 +165,7 @@ def suspend_iou_node(node: IOUVM = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reload_iou_node(node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Reload an IOU node.
|
||||
@ -181,13 +174,17 @@ async def reload_iou_node(node: IOUVM = Depends(dep_node)):
|
||||
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])
|
||||
async def create_iou_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: IOUVM = Depends(dep_node)):
|
||||
@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],
|
||||
)
|
||||
async def create_iou_node_nio(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: IOUVM = Depends(dep_node),
|
||||
):
|
||||
"""
|
||||
Add a NIO (Network Input/Output) to the node.
|
||||
"""
|
||||
@ -197,13 +194,17 @@ async def create_iou_node_nio(adapter_number: int,
|
||||
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])
|
||||
async def update_iou_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: IOUVM = Depends(dep_node)):
|
||||
@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],
|
||||
)
|
||||
async def update_iou_node_nio(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: IOUVM = Depends(dep_node),
|
||||
):
|
||||
"""
|
||||
Update a NIO (Network Input/Output) on the node.
|
||||
"""
|
||||
@ -215,8 +216,7 @@ async def update_iou_node_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
@ -226,10 +226,9 @@ async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_iou_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: IOUVM = Depends(dep_node)):
|
||||
async def start_iou_node_capture(
|
||||
adapter_number: int, port_number: int, node_capture_data: schemas.NodeCapture, node: IOUVM = Depends(dep_node)
|
||||
):
|
||||
"""
|
||||
Start a packet capture on the node.
|
||||
"""
|
||||
@ -239,8 +238,9 @@ async def start_iou_node_capture(adapter_number: int,
|
||||
return {"pcap_file_path": str(pcap_file_path)}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@ -269,8 +269,7 @@ async def console_ws(websocket: WebSocket, node: IOUVM = Depends(dep_node)):
|
||||
await node.start_websocket_console(websocket)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: IOUVM = Depends(dep_node)):
|
||||
|
||||
await node.reset_console()
|
||||
|
@ -30,9 +30,7 @@ from gns3server import schemas
|
||||
from gns3server.compute.builtin import Builtin
|
||||
from gns3server.compute.builtin.nodes.nat import Nat
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or NAT node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or NAT node"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -47,10 +45,12 @@ def dep_node(project_id: UUID, node_id: UUID):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
response_model=schemas.NAT,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create NAT node"}})
|
||||
@router.post(
|
||||
"",
|
||||
response_model=schemas.NAT,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create NAT node"}},
|
||||
)
|
||||
async def create_nat_node(project_id: UUID, node_data: schemas.NATCreate):
|
||||
"""
|
||||
Create a new NAT node.
|
||||
@ -58,18 +58,19 @@ async def create_nat_node(project_id: UUID, node_data: schemas.NATCreate):
|
||||
|
||||
builtin_manager = Builtin.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await builtin_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="nat",
|
||||
ports=node_data.get("ports_mapping"))
|
||||
node = await builtin_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_type="nat",
|
||||
ports=node_data.get("ports_mapping"),
|
||||
)
|
||||
|
||||
node.usage = node_data.get("usage", "")
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.NAT)
|
||||
@router.get("/{node_id}", response_model=schemas.NAT)
|
||||
def get_nat_node(node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Return a NAT node.
|
||||
@ -78,8 +79,7 @@ def get_nat_node(node: Nat = Depends(dep_node)):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.NAT)
|
||||
@router.put("/{node_id}", response_model=schemas.NAT)
|
||||
def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Update a NAT node.
|
||||
@ -93,8 +93,7 @@ def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node))
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_nat_node(node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Delete a cloud node.
|
||||
@ -103,8 +102,7 @@ async def delete_nat_node(node: Nat = Depends(dep_node)):
|
||||
await Builtin.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_nat_node(node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Start a NAT node.
|
||||
@ -113,8 +111,7 @@ async def start_nat_node(node: Nat = Depends(dep_node)):
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_nat_node(node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Stop a NAT node.
|
||||
@ -124,8 +121,7 @@ async def stop_nat_node(node: Nat = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def suspend_nat_node(node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Suspend a NAT node.
|
||||
@ -135,13 +131,17 @@ async def suspend_nat_node(node: Nat = Depends(dep_node)):
|
||||
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])
|
||||
async def create_nat_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: Nat = Depends(dep_node)):
|
||||
@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],
|
||||
)
|
||||
async def create_nat_node_nio(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
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.
|
||||
@ -152,13 +152,17 @@ async def create_nat_node_nio(adapter_number: int,
|
||||
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])
|
||||
async def update_nat_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
|
||||
node: Nat = Depends(dep_node)):
|
||||
@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],
|
||||
)
|
||||
async def update_nat_node_nio(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
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.
|
||||
@ -171,8 +175,7 @@ async def update_nat_node_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_nat_node_nio(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
@ -183,10 +186,9 @@ async def delete_nat_node_nio(adapter_number: int, port_number: int, node: Nat =
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_nat_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: Nat = Depends(dep_node)):
|
||||
async def start_nat_node_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.
|
||||
@ -197,8 +199,9 @@ async def start_nat_node_capture(adapter_number: int,
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_nat_node_capture(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
|
@ -24,6 +24,7 @@ from websockets.exceptions import ConnectionClosed, WebSocketException
|
||||
from gns3server.compute.notification_manager import NotificationManager
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
@ -50,7 +51,7 @@ async def notification_ws(websocket: WebSocket):
|
||||
await websocket.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
|
@ -21,6 +21,7 @@ API routes for projects.
|
||||
import os
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request, status
|
||||
@ -60,9 +61,7 @@ def get_compute_projects():
|
||||
return [p.__json__() for p in pm.projects]
|
||||
|
||||
|
||||
@router.post("/projects",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project)
|
||||
@router.post("/projects", status_code=status.HTTP_201_CREATED, response_model=schemas.Project)
|
||||
def create_compute_project(project_data: schemas.ProjectCreate):
|
||||
"""
|
||||
Create a new project on the compute.
|
||||
@ -70,15 +69,16 @@ def create_compute_project(project_data: schemas.ProjectCreate):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project_data = jsonable_encoder(project_data, exclude_unset=True)
|
||||
project = pm.create_project(name=project_data.get("name"),
|
||||
path=project_data.get("path"),
|
||||
project_id=project_data.get("project_id"),
|
||||
variables=project_data.get("variables", None))
|
||||
project = pm.create_project(
|
||||
name=project_data.get("name"),
|
||||
path=project_data.get("path"),
|
||||
project_id=project_data.get("project_id"),
|
||||
variables=project_data.get("variables", None),
|
||||
)
|
||||
return project.__json__()
|
||||
|
||||
|
||||
@router.put("/projects/{project_id}",
|
||||
response_model=schemas.Project)
|
||||
@router.put("/projects/{project_id}", response_model=schemas.Project)
|
||||
async def update_compute_project(project_data: schemas.ProjectUpdate, project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Update project on the compute.
|
||||
@ -88,8 +88,7 @@ async def update_compute_project(project_data: schemas.ProjectUpdate, project: P
|
||||
return project.__json__()
|
||||
|
||||
|
||||
@router.get("/projects/{project_id}",
|
||||
response_model=schemas.Project)
|
||||
@router.get("/projects/{project_id}", response_model=schemas.Project)
|
||||
def get_compute_project(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Return a project from the compute.
|
||||
@ -98,8 +97,7 @@ def get_compute_project(project: Project = Depends(dep_project)):
|
||||
return project.__json__()
|
||||
|
||||
|
||||
@router.post("/projects/{project_id}/close",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/projects/{project_id}/close", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def close_compute_project(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Close a project on the compute.
|
||||
@ -117,8 +115,7 @@ async def close_compute_project(project: Project = Depends(dep_project)):
|
||||
log.warning("Skip project closing, another client is listening for project notifications")
|
||||
|
||||
|
||||
@router.delete("/projects/{project_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/projects/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_compute_project(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Delete project from the compute.
|
||||
@ -127,6 +124,7 @@ async def delete_compute_project(project: Project = Depends(dep_project)):
|
||||
await project.delete()
|
||||
ProjectManager.instance().remove_project(project.id)
|
||||
|
||||
|
||||
# @Route.get(
|
||||
# r"/projects/{project_id}/notifications",
|
||||
# description="Receive notifications about the project",
|
||||
@ -181,8 +179,7 @@ async def delete_compute_project(project: Project = Depends(dep_project)):
|
||||
# return {"action": "ping", "event": stats}
|
||||
|
||||
|
||||
@router.get("/projects/{project_id}/files",
|
||||
response_model=List[schemas.ProjectFile])
|
||||
@router.get("/projects/{project_id}/files", response_model=List[schemas.ProjectFile])
|
||||
async def get_compute_project_files(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Return files belonging to a project.
|
||||
@ -210,8 +207,7 @@ async def get_compute_project_file(file_path: str, project: Project = Depends(de
|
||||
return FileResponse(path, media_type="application/octet-stream")
|
||||
|
||||
|
||||
@router.post("/projects/{project_id}/files/{file_path:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/projects/{project_id}/files/{file_path:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def write_compute_project_file(file_path: str, request: Request, project: Project = Depends(dep_project)):
|
||||
|
||||
path = os.path.normpath(file_path)
|
||||
|
@ -31,9 +31,7 @@ from gns3server.compute.project_manager import ProjectManager
|
||||
from gns3server.compute.qemu import Qemu
|
||||
from gns3server.compute.qemu.qemu_vm import QemuVM
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or Qemu node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Qemu node"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -48,10 +46,12 @@ def dep_node(project_id: UUID, node_id: UUID):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
response_model=schemas.Qemu,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Qemu node"}})
|
||||
@router.post(
|
||||
"",
|
||||
response_model=schemas.Qemu,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create Qemu node"}},
|
||||
)
|
||||
async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate):
|
||||
"""
|
||||
Create a new Qemu node.
|
||||
@ -59,16 +59,18 @@ async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate):
|
||||
|
||||
qemu = Qemu.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await qemu.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.pop("node_id", None),
|
||||
linked_clone=node_data.get("linked_clone", True),
|
||||
qemu_path=node_data.pop("qemu_path", None),
|
||||
console=node_data.pop("console", None),
|
||||
console_type=node_data.pop("console_type", "telnet"),
|
||||
aux=node_data.get("aux"),
|
||||
aux_type=node_data.pop("aux_type", "none"),
|
||||
platform=node_data.pop("platform", None))
|
||||
vm = await qemu.create_node(
|
||||
node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.pop("node_id", None),
|
||||
linked_clone=node_data.get("linked_clone", True),
|
||||
qemu_path=node_data.pop("qemu_path", None),
|
||||
console=node_data.pop("console", None),
|
||||
console_type=node_data.pop("console_type", "telnet"),
|
||||
aux=node_data.get("aux"),
|
||||
aux_type=node_data.pop("aux_type", "none"),
|
||||
platform=node_data.pop("platform", None),
|
||||
)
|
||||
|
||||
for name, value in node_data.items():
|
||||
if hasattr(vm, name) and getattr(vm, name) != value:
|
||||
@ -77,8 +79,7 @@ async def create_qemu_node(project_id: UUID, node_data: schemas.QemuCreate):
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.Qemu)
|
||||
@router.get("/{node_id}", response_model=schemas.Qemu)
|
||||
def get_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Return a Qemu node.
|
||||
@ -87,8 +88,7 @@ def get_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.Qemu)
|
||||
@router.put("/{node_id}", response_model=schemas.Qemu)
|
||||
async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Update a Qemu node.
|
||||
@ -104,8 +104,7 @@ async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a Qemu node.
|
||||
@ -114,9 +113,7 @@ async def delete_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
await Qemu.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.Qemu,
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.Qemu, status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_qemu_node(destination_node_id: UUID = Body(..., embed=True), node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Duplicate a Qemu node.
|
||||
@ -126,15 +123,13 @@ async def duplicate_qemu_node(destination_node_id: UUID = Body(..., embed=True),
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/resize_disk",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/resize_disk", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def resize_qemu_node_disk(node_data: schemas.QemuDiskResize, node: QemuVM = Depends(dep_node)):
|
||||
|
||||
await node.resize_disk(node_data.drive_name, node_data.extend)
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Start a Qemu node.
|
||||
@ -145,13 +140,12 @@ async def start_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
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(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")
|
||||
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 node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a Qemu node.
|
||||
@ -160,8 +154,7 @@ async def stop_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
await node.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reload_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Reload a Qemu node.
|
||||
@ -170,8 +163,7 @@ async def reload_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
await node.reload()
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def suspend_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Suspend a Qemu node.
|
||||
@ -180,8 +172,7 @@ async def suspend_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
await node.suspend()
|
||||
|
||||
|
||||
@router.post("/{node_id}/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def resume_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Resume a Qemu node.
|
||||
@ -190,13 +181,14 @@ async def resume_qemu_node(node: QemuVM = Depends(dep_node)):
|
||||
await node.resume()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
async def create_qemu_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: QemuVM = Depends(dep_node)):
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def create_qemu_node_nio(
|
||||
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.
|
||||
@ -207,13 +199,14 @@ async def create_qemu_node_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
async def update_qemu_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: QemuVM = Depends(dep_node)):
|
||||
@router.put(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def update_qemu_node_nio(
|
||||
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.
|
||||
@ -228,11 +221,8 @@ async def update_qemu_node_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_qemu_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
node: QemuVM = Depends(dep_node)):
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_qemu_node_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.
|
||||
@ -242,10 +232,9 @@ async def delete_qemu_node_nio(adapter_number: int,
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_qemu_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: QemuVM = Depends(dep_node)):
|
||||
async def start_qemu_node_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.
|
||||
@ -256,8 +245,9 @@ async def start_qemu_node_capture(adapter_number: int,
|
||||
return {"pcap_file_path": str(pcap_file_path)}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_qemu_node_capture(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@ -288,8 +278,7 @@ async def console_ws(websocket: WebSocket, node: QemuVM = Depends(dep_node)):
|
||||
await node.start_websocket_console(websocket)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: QemuVM = Depends(dep_node)):
|
||||
|
||||
await node.reset_console()
|
||||
|
@ -31,9 +31,7 @@ from gns3server.compute.virtualbox.virtualbox_error import VirtualBoxError
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
from gns3server.compute.virtualbox.virtualbox_vm import VirtualBoxVM
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or VirtualBox node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VirtualBox node"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -48,10 +46,12 @@ def dep_node(project_id: UUID, node_id: UUID):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
response_model=schemas.VirtualBox,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VirtualBox node"}})
|
||||
@router.post(
|
||||
"",
|
||||
response_model=schemas.VirtualBox,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VirtualBox node"}},
|
||||
)
|
||||
async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBoxCreate):
|
||||
"""
|
||||
Create a new VirtualBox node.
|
||||
@ -59,14 +59,16 @@ async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBox
|
||||
|
||||
vbox_manager = VirtualBox.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await vbox_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_data.pop("vmname"),
|
||||
linked_clone=node_data.pop("linked_clone", False),
|
||||
console=node_data.get("console", None),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
adapters=node_data.get("adapters", 0))
|
||||
vm = await vbox_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_data.pop("vmname"),
|
||||
linked_clone=node_data.pop("linked_clone", False),
|
||||
console=node_data.get("console", None),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
adapters=node_data.get("adapters", 0),
|
||||
)
|
||||
|
||||
if "ram" in node_data:
|
||||
ram = node_data.pop("ram")
|
||||
@ -81,8 +83,7 @@ async def create_virtualbox_node(project_id: UUID, node_data: schemas.VirtualBox
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.VirtualBox)
|
||||
@router.get("/{node_id}", response_model=schemas.VirtualBox)
|
||||
def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Return a VirtualBox node.
|
||||
@ -91,8 +92,7 @@ def get_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.VirtualBox)
|
||||
@router.put("/{node_id}", response_model=schemas.VirtualBox)
|
||||
async def update_virtualbox_node(node_data: schemas.VirtualBoxUpdate, node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Update a VirtualBox node.
|
||||
@ -134,8 +134,7 @@ async def update_virtualbox_node(node_data: schemas.VirtualBoxUpdate, node: Virt
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a VirtualBox node.
|
||||
@ -144,8 +143,7 @@ async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
await VirtualBox.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Start a VirtualBox node.
|
||||
@ -155,12 +153,11 @@ async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
pm = ProjectManager.instance()
|
||||
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")
|
||||
# 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 node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a VirtualBox node.
|
||||
@ -169,8 +166,7 @@ async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
await node.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Suspend a VirtualBox node.
|
||||
@ -179,8 +175,7 @@ async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
await node.suspend()
|
||||
|
||||
|
||||
@router.post("/{node_id}/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Resume a VirtualBox node.
|
||||
@ -189,8 +184,7 @@ async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
await node.resume()
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Reload a VirtualBox node.
|
||||
@ -199,13 +193,14 @@ async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
|
||||
await node.reload()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
async def create_virtualbox_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: VirtualBoxVM = Depends(dep_node)):
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def create_virtualbox_node_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.
|
||||
@ -216,13 +211,14 @@ async def create_virtualbox_node_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
async def update_virtualbox_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: VirtualBoxVM = Depends(dep_node)):
|
||||
@router.put(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def update_virtualbox_node_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.
|
||||
@ -237,8 +233,7 @@ async def update_virtualbox_node_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_virtualbox_node_nio(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
@ -249,10 +244,12 @@ async def delete_virtualbox_node_nio(adapter_number: int, port_number: int, node
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_virtualbox_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: VirtualBoxVM = Depends(dep_node)):
|
||||
async def start_virtualbox_node_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.
|
||||
@ -263,8 +260,9 @@ async def start_virtualbox_node_capture(adapter_number: int,
|
||||
return {"pcap_file_path": str(pcap_file_path)}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_virtualbox_node_capture(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@ -295,8 +293,7 @@ async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node
|
||||
await node.start_websocket_console(websocket)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: VirtualBoxVM = Depends(dep_node)):
|
||||
|
||||
await node.reset_console()
|
||||
|
@ -30,9 +30,7 @@ from gns3server.compute.vmware import VMware
|
||||
from gns3server.compute.project_manager import ProjectManager
|
||||
from gns3server.compute.vmware.vmware_vm import VMwareVM
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -47,10 +45,12 @@ def dep_node(project_id: UUID, node_id: UUID):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
response_model=schemas.VMware,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}})
|
||||
@router.post(
|
||||
"",
|
||||
response_model=schemas.VMware,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
|
||||
)
|
||||
async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate):
|
||||
"""
|
||||
Create a new VMware node.
|
||||
@ -58,13 +58,15 @@ async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate):
|
||||
|
||||
vmware_manager = VMware.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await vmware_manager.create_node(node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_data.pop("vmx_path"),
|
||||
linked_clone=node_data.pop("linked_clone"),
|
||||
console=node_data.get("console", None),
|
||||
console_type=node_data.get("console_type", "telnet"))
|
||||
vm = await vmware_manager.create_node(
|
||||
node_data.pop("name"),
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
node_data.pop("vmx_path"),
|
||||
linked_clone=node_data.pop("linked_clone"),
|
||||
console=node_data.get("console", None),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
)
|
||||
|
||||
for name, value in node_data.items():
|
||||
if name != "node_id":
|
||||
@ -74,8 +76,7 @@ async def create_vmware_node(project_id: UUID, node_data: schemas.VMwareCreate):
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.VMware)
|
||||
@router.get("/{node_id}", response_model=schemas.VMware)
|
||||
def get_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Return a VMware node.
|
||||
@ -84,8 +85,7 @@ def get_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.VMware)
|
||||
@router.put("/{node_id}", response_model=schemas.VMware)
|
||||
def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Update a VMware node.
|
||||
@ -102,8 +102,7 @@ def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a VMware node.
|
||||
@ -112,8 +111,7 @@ async def delete_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
await VMware.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Start a VMware node.
|
||||
@ -122,13 +120,12 @@ async def start_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
if node.check_hw_virtualization():
|
||||
pm = ProjectManager.instance()
|
||||
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")
|
||||
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 node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a VMware node.
|
||||
@ -137,8 +134,7 @@ async def stop_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
await node.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Suspend a VMware node.
|
||||
@ -147,8 +143,7 @@ async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
await node.suspend()
|
||||
|
||||
|
||||
@router.post("/{node_id}/resume",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/resume", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def resume_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Resume a VMware node.
|
||||
@ -157,8 +152,7 @@ async def resume_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
await node.resume()
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reload_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Reload a VMware node.
|
||||
@ -167,13 +161,14 @@ async def reload_vmware_node(node: VMwareVM = Depends(dep_node)):
|
||||
await node.reload()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
async def create_vmware_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: VMwareVM = Depends(dep_node)):
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def create_vmware_node_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.
|
||||
@ -184,12 +179,14 @@ async def create_vmware_node_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
async def update_vmware_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO, node: VMwareVM = Depends(dep_node)):
|
||||
@router.put(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def update_vmware_node_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.
|
||||
@ -202,8 +199,7 @@ async def update_vmware_node_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_vmware_node_nio(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
@ -214,10 +210,9 @@ async def delete_vmware_node_nio(adapter_number: int, port_number: int, node: VM
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_vmware_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: VMwareVM = Depends(dep_node)):
|
||||
async def start_vmware_node_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.
|
||||
@ -228,8 +223,9 @@ async def start_vmware_node_capture(adapter_number: int,
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_vmware_node_capture(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@ -251,8 +247,7 @@ async def stream_pcap_file(adapter_number: int, port_number: int, node: VMwareVM
|
||||
return StreamingResponse(stream, media_type="application/vnd.tcpdump.pcap")
|
||||
|
||||
|
||||
@router.post("/{node_id}/interfaces/vmnet",
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/{node_id}/interfaces/vmnet", status_code=status.HTTP_201_CREATED)
|
||||
def allocate_vmnet(node: VMwareVM = Depends(dep_node)) -> dict:
|
||||
"""
|
||||
Allocate a VMware VMnet interface on the server.
|
||||
@ -274,8 +269,7 @@ async def console_ws(websocket: WebSocket, node: VMwareVM = Depends(dep_node)):
|
||||
await node.start_websocket_console(websocket)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: VMwareVM = Depends(dep_node)):
|
||||
|
||||
await node.reset_console()
|
||||
|
@ -29,9 +29,7 @@ from gns3server import schemas
|
||||
from gns3server.compute.vpcs import VPCS
|
||||
from gns3server.compute.vpcs.vpcs_vm import VPCSVM
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or VMware node"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -46,10 +44,12 @@ def dep_node(project_id: UUID, node_id: UUID):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
response_model=schemas.VPCS,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}})
|
||||
@router.post(
|
||||
"",
|
||||
response_model=schemas.VPCS,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create VMware node"}},
|
||||
)
|
||||
async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate):
|
||||
"""
|
||||
Create a new VPCS node.
|
||||
@ -57,18 +57,19 @@ async def create_vpcs_node(project_id: UUID, node_data: schemas.VPCSCreate):
|
||||
|
||||
vpcs = VPCS.instance()
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
vm = await vpcs.create_node(node_data["name"],
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
startup_script=node_data.get("startup_script"))
|
||||
vm = await vpcs.create_node(
|
||||
node_data["name"],
|
||||
str(project_id),
|
||||
node_data.get("node_id"),
|
||||
console=node_data.get("console"),
|
||||
console_type=node_data.get("console_type", "telnet"),
|
||||
startup_script=node_data.get("startup_script"),
|
||||
)
|
||||
|
||||
return vm.__json__()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.VPCS)
|
||||
@router.get("/{node_id}", response_model=schemas.VPCS)
|
||||
def get_vpcs_node(node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Return a VPCS node.
|
||||
@ -77,8 +78,7 @@ def get_vpcs_node(node: VPCSVM = Depends(dep_node)):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.VPCS)
|
||||
@router.put("/{node_id}", response_model=schemas.VPCS)
|
||||
def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Update a VPCS node.
|
||||
@ -92,8 +92,7 @@ def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_n
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Delete a VPCS node.
|
||||
@ -102,9 +101,7 @@ async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)):
|
||||
await VPCS.instance().delete_node(node.id)
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.VPCS,
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.VPCS, status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_vpcs_node(destination_node_id: UUID = Body(..., embed=True), node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Duplicate a VPCS node.
|
||||
@ -114,8 +111,7 @@ async def duplicate_vpcs_node(destination_node_id: UUID = Body(..., embed=True),
|
||||
return new_node.__json__()
|
||||
|
||||
|
||||
@router.post("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_vpcs_node(node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Start a VPCS node.
|
||||
@ -124,8 +120,7 @@ async def start_vpcs_node(node: VPCSVM = Depends(dep_node)):
|
||||
await node.start()
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a VPCS node.
|
||||
@ -134,8 +129,7 @@ async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)):
|
||||
await node.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Suspend a VPCS node.
|
||||
@ -145,8 +139,7 @@ async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)):
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Reload a VPCS node.
|
||||
@ -155,13 +148,14 @@ async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)):
|
||||
await node.reload()
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
async def create_vpcs_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: VPCSVM = Depends(dep_node)):
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def create_vpcs_node_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.
|
||||
@ -172,13 +166,14 @@ async def create_vpcs_node_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO)
|
||||
async def update_vpcs_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
nio_data: schemas.UDPNIO,
|
||||
node: VPCSVM = Depends(dep_node)):
|
||||
@router.put(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.UDPNIO,
|
||||
)
|
||||
async def update_vpcs_node_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.
|
||||
@ -191,11 +186,8 @@ async def update_vpcs_node_nio(adapter_number: int,
|
||||
return nio.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_vpcs_node_nio(adapter_number: int,
|
||||
port_number: int,
|
||||
node: VPCSVM = Depends(dep_node)):
|
||||
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_vpcs_node_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.
|
||||
@ -205,10 +197,9 @@ async def delete_vpcs_node_nio(adapter_number: int,
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
async def start_vpcs_node_capture(adapter_number: int,
|
||||
port_number: int,
|
||||
node_capture_data: schemas.NodeCapture,
|
||||
node: VPCSVM = Depends(dep_node)):
|
||||
async def start_vpcs_node_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.
|
||||
@ -219,8 +210,9 @@ async def start_vpcs_node_capture(adapter_number: int,
|
||||
return {"pcap_file_path": pcap_file_path}
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post(
|
||||
"/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop", status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def stop_vpcs_node_capture(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)):
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
@ -230,8 +222,7 @@ async def stop_vpcs_node_capture(adapter_number: int, port_number: int, node: VP
|
||||
await node.stop_capture(port_number)
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: VPCSVM = Depends(dep_node)):
|
||||
|
||||
await node.reset_console()
|
||||
|
@ -25,12 +25,13 @@ router = APIRouter()
|
||||
|
||||
|
||||
@router.get("")
|
||||
async def get_appliances(update: Optional[bool] = None, symbol_theme: Optional[str] = "Classic"):
|
||||
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()
|
||||
if update:
|
||||
await controller.appliance_manager.download_appliances()
|
||||
|
@ -29,22 +29,24 @@ from gns3server import schemas
|
||||
|
||||
from .dependencies.database import get_repository
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Compute not found"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Compute not found"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
|
||||
@router.post("",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Compute,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not connect to compute"},
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not create compute"},
|
||||
401: {"model": schemas.ErrorMessage, "description": "Invalid authentication for compute"}})
|
||||
@router.post(
|
||||
"",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Compute,
|
||||
responses={
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not connect to compute"},
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not create compute"},
|
||||
401: {"model": schemas.ErrorMessage, "description": "Invalid authentication for compute"},
|
||||
},
|
||||
)
|
||||
async def create_compute(
|
||||
compute_create: schemas.ComputeCreate,
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
|
||||
compute_create: schemas.ComputeCreate,
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository)),
|
||||
) -> schemas.Compute:
|
||||
"""
|
||||
Create a new compute on the controller.
|
||||
@ -53,12 +55,9 @@ async def create_compute(
|
||||
return await ComputesService(computes_repo).create_compute(compute_create)
|
||||
|
||||
|
||||
@router.get("/{compute_id}",
|
||||
response_model=schemas.Compute,
|
||||
response_model_exclude_unset=True)
|
||||
@router.get("/{compute_id}", response_model=schemas.Compute, response_model_exclude_unset=True)
|
||||
async def get_compute(
|
||||
compute_id: Union[str, UUID],
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
|
||||
compute_id: Union[str, UUID], computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
|
||||
) -> schemas.Compute:
|
||||
"""
|
||||
Return a compute from the controller.
|
||||
@ -67,11 +66,9 @@ async def get_compute(
|
||||
return await ComputesService(computes_repo).get_compute(compute_id)
|
||||
|
||||
|
||||
@router.get("",
|
||||
response_model=List[schemas.Compute],
|
||||
response_model_exclude_unset=True)
|
||||
@router.get("", response_model=List[schemas.Compute], response_model_exclude_unset=True)
|
||||
async def get_computes(
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository)),
|
||||
) -> List[schemas.Compute]:
|
||||
"""
|
||||
Return all computes known by the controller.
|
||||
@ -80,13 +77,11 @@ async def get_computes(
|
||||
return await ComputesService(computes_repo).get_computes()
|
||||
|
||||
|
||||
@router.put("/{compute_id}",
|
||||
response_model=schemas.Compute,
|
||||
response_model_exclude_unset=True)
|
||||
@router.put("/{compute_id}", response_model=schemas.Compute, response_model_exclude_unset=True)
|
||||
async def update_compute(
|
||||
compute_id: Union[str, UUID],
|
||||
compute_update: schemas.ComputeUpdate,
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
|
||||
compute_id: Union[str, UUID],
|
||||
compute_update: schemas.ComputeUpdate,
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository)),
|
||||
) -> schemas.Compute:
|
||||
"""
|
||||
Update a compute on the controller.
|
||||
@ -95,11 +90,9 @@ async def update_compute(
|
||||
return await ComputesService(computes_repo).update_compute(compute_id, compute_update)
|
||||
|
||||
|
||||
@router.delete("/{compute_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{compute_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_compute(
|
||||
compute_id: Union[str, UUID],
|
||||
computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
|
||||
compute_id: Union[str, UUID], computes_repo: ComputesRepository = Depends(get_repository(ComputesRepository))
|
||||
):
|
||||
"""
|
||||
Delete a compute from the controller.
|
||||
@ -160,7 +153,4 @@ async def autoidlepc(compute_id: Union[str, UUID], auto_idle_pc: schemas.AutoIdl
|
||||
"""
|
||||
|
||||
controller = Controller.instance()
|
||||
return await controller.autoidlepc(str(compute_id),
|
||||
auto_idle_pc.platform,
|
||||
auto_idle_pc.image,
|
||||
auto_idle_pc.ram)
|
||||
return await controller.autoidlepc(str(compute_id), auto_idle_pc.platform, auto_idle_pc.image, auto_idle_pc.ram)
|
||||
|
@ -29,14 +29,17 @@ from gns3server import schemas
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/shutdown",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={403: {"model": schemas.ErrorMessage, "description": "Server shutdown not allowed"}})
|
||||
@router.post(
|
||||
"/shutdown",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={403: {"model": schemas.ErrorMessage, "description": "Server shutdown not allowed"}},
|
||||
)
|
||||
async def shutdown():
|
||||
"""
|
||||
Shutdown the local server
|
||||
@ -67,8 +70,7 @@ async def shutdown():
|
||||
os.kill(os.getpid(), signal.SIGTERM)
|
||||
|
||||
|
||||
@router.get("/version",
|
||||
response_model=schemas.Version)
|
||||
@router.get("/version", response_model=schemas.Version)
|
||||
def get_version():
|
||||
"""
|
||||
Return the server version number.
|
||||
@ -78,10 +80,12 @@ def get_version():
|
||||
return {"version": __version__, "local": local_server}
|
||||
|
||||
|
||||
@router.post("/version",
|
||||
response_model=schemas.Version,
|
||||
response_model_exclude_defaults=True,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Invalid version"}})
|
||||
@router.post(
|
||||
"/version",
|
||||
response_model=schemas.Version,
|
||||
response_model_exclude_defaults=True,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Invalid version"}},
|
||||
)
|
||||
def check_version(version: schemas.Version):
|
||||
"""
|
||||
Check if version is the same as the server.
|
||||
@ -97,8 +101,7 @@ def check_version(version: schemas.Version):
|
||||
return {"version": __version__}
|
||||
|
||||
|
||||
@router.get("/iou_license",
|
||||
response_model=schemas.IOULicense)
|
||||
@router.get("/iou_license", response_model=schemas.IOULicense)
|
||||
def get_iou_license():
|
||||
"""
|
||||
Return the IOU license settings
|
||||
@ -107,9 +110,7 @@ def get_iou_license():
|
||||
return Controller.instance().iou_license
|
||||
|
||||
|
||||
@router.put("/iou_license",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.IOULicense)
|
||||
@router.put("/iou_license", status_code=status.HTTP_201_CREATED, response_model=schemas.IOULicense)
|
||||
async def update_iou_license(iou_license: schemas.IOULicense):
|
||||
"""
|
||||
Update the IOU license settings.
|
||||
@ -137,6 +138,7 @@ async def statistics():
|
||||
log.error(f"Could not retrieve statistics on compute {compute.name}: {e}")
|
||||
return compute_statistics
|
||||
|
||||
|
||||
# @Route.post(
|
||||
# r"/debug",
|
||||
# description="Dump debug information to disk (debug directory in config directory). Work only for local server",
|
||||
|
@ -28,8 +28,7 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/users/login") # FIXME: URL p
|
||||
|
||||
|
||||
async def get_user_from_token(
|
||||
token: str = Depends(oauth2_scheme),
|
||||
user_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
token: str = Depends(oauth2_scheme), user_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
) -> schemas.User:
|
||||
|
||||
username = auth_service.get_username_from_token(token)
|
||||
@ -38,7 +37,7 @@ async def get_user_from_token(
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Could not validate credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"}
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
return user
|
||||
|
||||
@ -49,6 +48,6 @@ async def get_current_active_user(current_user: schemas.User = Depends(get_user_
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Not an active user",
|
||||
headers={"WWW-Authenticate": "Bearer"}
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
return current_user
|
||||
|
@ -31,7 +31,7 @@ async def get_db_session(request: Request) -> AsyncSession:
|
||||
|
||||
|
||||
def get_repository(repo: Type[BaseRepository]) -> Callable:
|
||||
|
||||
def get_repo(db_session: AsyncSession = Depends(get_db_session)) -> Type[BaseRepository]:
|
||||
return repo(db_session)
|
||||
|
||||
return get_repo
|
||||
|
@ -26,16 +26,12 @@ from uuid import UUID
|
||||
from gns3server.controller import Controller
|
||||
from gns3server import schemas
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Project or drawing not found"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Project or drawing not found"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
|
||||
@router.get("",
|
||||
response_model=List[schemas.Drawing],
|
||||
response_model_exclude_unset=True)
|
||||
@router.get("", response_model=List[schemas.Drawing], response_model_exclude_unset=True)
|
||||
async def get_drawings(project_id: UUID):
|
||||
"""
|
||||
Return the list of all drawings for a given project.
|
||||
@ -45,9 +41,7 @@ async def get_drawings(project_id: UUID):
|
||||
return [v.__json__() for v in project.drawings.values()]
|
||||
|
||||
|
||||
@router.post("",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Drawing)
|
||||
@router.post("", status_code=status.HTTP_201_CREATED, response_model=schemas.Drawing)
|
||||
async def create_drawing(project_id: UUID, drawing_data: schemas.Drawing):
|
||||
"""
|
||||
Create a new drawing.
|
||||
@ -58,9 +52,7 @@ async def create_drawing(project_id: UUID, drawing_data: schemas.Drawing):
|
||||
return drawing.__json__()
|
||||
|
||||
|
||||
@router.get("/{drawing_id}",
|
||||
response_model=schemas.Drawing,
|
||||
response_model_exclude_unset=True)
|
||||
@router.get("/{drawing_id}", response_model=schemas.Drawing, response_model_exclude_unset=True)
|
||||
async def get_drawing(project_id: UUID, drawing_id: UUID):
|
||||
"""
|
||||
Return a drawing.
|
||||
@ -71,9 +63,7 @@ async def get_drawing(project_id: UUID, drawing_id: UUID):
|
||||
return drawing.__json__()
|
||||
|
||||
|
||||
@router.put("/{drawing_id}",
|
||||
response_model=schemas.Drawing,
|
||||
response_model_exclude_unset=True)
|
||||
@router.put("/{drawing_id}", response_model=schemas.Drawing, response_model_exclude_unset=True)
|
||||
async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: schemas.Drawing):
|
||||
"""
|
||||
Update a drawing.
|
||||
@ -85,8 +75,7 @@ async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: schem
|
||||
return drawing.__json__()
|
||||
|
||||
|
||||
@router.delete("/{drawing_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{drawing_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_drawing(project_id: UUID, drawing_id: UUID):
|
||||
"""
|
||||
Delete a drawing.
|
||||
|
@ -34,11 +34,10 @@ from gns3server.utils.http_client import HTTPClient
|
||||
from gns3server import schemas
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or link"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or link"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -53,9 +52,7 @@ async def dep_link(project_id: UUID, link_id: UUID):
|
||||
return link
|
||||
|
||||
|
||||
@router.get("",
|
||||
response_model=List[schemas.Link],
|
||||
response_model_exclude_unset=True)
|
||||
@router.get("", response_model=List[schemas.Link], response_model_exclude_unset=True)
|
||||
async def get_links(project_id: UUID):
|
||||
"""
|
||||
Return all links for a given project.
|
||||
@ -65,11 +62,15 @@ async def get_links(project_id: UUID):
|
||||
return [v.__json__() for v in project.links.values()]
|
||||
|
||||
|
||||
@router.post("",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Link,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project"},
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not create link"}})
|
||||
@router.post(
|
||||
"",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Link,
|
||||
responses={
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project"},
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not create link"},
|
||||
},
|
||||
)
|
||||
async def create_link(project_id: UUID, link_data: schemas.Link):
|
||||
"""
|
||||
Create a new link.
|
||||
@ -84,10 +85,12 @@ async def create_link(project_id: UUID, link_data: schemas.Link):
|
||||
await link.update_suspend(link_data["suspend"])
|
||||
try:
|
||||
for node in link_data["nodes"]:
|
||||
await link.add_node(project.get_node(node["node_id"]),
|
||||
node.get("adapter_number", 0),
|
||||
node.get("port_number", 0),
|
||||
label=node.get("label"))
|
||||
await link.add_node(
|
||||
project.get_node(node["node_id"]),
|
||||
node.get("adapter_number", 0),
|
||||
node.get("port_number", 0),
|
||||
label=node.get("label"),
|
||||
)
|
||||
except ControllerError as e:
|
||||
await project.delete_link(link.id)
|
||||
raise e
|
||||
@ -103,9 +106,7 @@ async def get_filters(link: Link = Depends(dep_link)):
|
||||
return link.available_filters()
|
||||
|
||||
|
||||
@router.get("/{link_id}",
|
||||
response_model=schemas.Link,
|
||||
response_model_exclude_unset=True)
|
||||
@router.get("/{link_id}", response_model=schemas.Link, response_model_exclude_unset=True)
|
||||
async def get_link(link: Link = Depends(dep_link)):
|
||||
"""
|
||||
Return a link.
|
||||
@ -114,9 +115,7 @@ async def get_link(link: Link = Depends(dep_link)):
|
||||
return link.__json__()
|
||||
|
||||
|
||||
@router.put("/{link_id}",
|
||||
response_model=schemas.Link,
|
||||
response_model_exclude_unset=True)
|
||||
@router.put("/{link_id}", response_model=schemas.Link, response_model_exclude_unset=True)
|
||||
async def update_link(link_data: schemas.Link, link: Link = Depends(dep_link)):
|
||||
"""
|
||||
Update a link.
|
||||
@ -132,8 +131,7 @@ async def update_link(link_data: schemas.Link, link: Link = Depends(dep_link)):
|
||||
return link.__json__()
|
||||
|
||||
|
||||
@router.delete("/{link_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{link_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_link(project_id: UUID, link: Link = Depends(dep_link)):
|
||||
"""
|
||||
Delete a link.
|
||||
@ -143,8 +141,7 @@ async def delete_link(project_id: UUID, link: Link = Depends(dep_link)):
|
||||
await project.delete_link(link.id)
|
||||
|
||||
|
||||
@router.post("/{link_id}/reset",
|
||||
response_model=schemas.Link)
|
||||
@router.post("/{link_id}/reset", response_model=schemas.Link)
|
||||
async def reset_link(link: Link = Depends(dep_link)):
|
||||
"""
|
||||
Reset a link.
|
||||
@ -154,21 +151,20 @@ async def reset_link(link: Link = Depends(dep_link)):
|
||||
return link.__json__()
|
||||
|
||||
|
||||
@router.post("/{link_id}/capture/start",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Link)
|
||||
@router.post("/{link_id}/capture/start", status_code=status.HTTP_201_CREATED, response_model=schemas.Link)
|
||||
async def start_capture(capture_data: dict, link: Link = Depends(dep_link)):
|
||||
"""
|
||||
Start packet capture on the link.
|
||||
"""
|
||||
|
||||
await link.start_capture(data_link_type=capture_data.get("data_link_type", "DLT_EN10MB"),
|
||||
capture_file_name=capture_data.get("capture_file_name"))
|
||||
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("/{link_id}/capture/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{link_id}/capture/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_capture(link: Link = Depends(dep_link)):
|
||||
"""
|
||||
Stop packet capture on the link.
|
||||
@ -189,8 +185,8 @@ async def stream_pcap(request: Request, link: Link = Depends(dep_link)):
|
||||
compute = link.compute
|
||||
pcap_streaming_url = link.pcap_streaming_url()
|
||||
headers = multidict.MultiDict(request.headers)
|
||||
headers['Host'] = compute.host
|
||||
headers['Router-Host'] = request.client.host
|
||||
headers["Host"] = compute.host
|
||||
headers["Router-Host"] = request.client.host
|
||||
body = await request.body()
|
||||
|
||||
async def compute_pcap_stream():
|
||||
|
@ -36,6 +36,7 @@ from gns3server.controller.controller_error import ControllerForbiddenError
|
||||
from gns3server import schemas
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
node_locks = {}
|
||||
@ -75,9 +76,7 @@ class NodeConcurrency(APIRoute):
|
||||
return custom_route_handler
|
||||
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or node"}}
|
||||
|
||||
router = APIRouter(route_class=NodeConcurrency, responses=responses)
|
||||
|
||||
@ -100,11 +99,15 @@ async def dep_node(node_id: UUID, project: Project = Depends(dep_project)):
|
||||
return node
|
||||
|
||||
|
||||
@router.post("",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Node,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project"},
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not create node"}})
|
||||
@router.post(
|
||||
"",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Node,
|
||||
responses={
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project"},
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not create node"},
|
||||
},
|
||||
)
|
||||
async def create_node(node_data: schemas.Node, project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Create a new node.
|
||||
@ -113,16 +116,11 @@ async def create_node(node_data: schemas.Node, project: Project = Depends(dep_pr
|
||||
controller = Controller.instance()
|
||||
compute = controller.get_compute(str(node_data.compute_id))
|
||||
node_data = jsonable_encoder(node_data, exclude_unset=True)
|
||||
node = await project.add_node(compute,
|
||||
node_data.pop("name"),
|
||||
node_data.pop("node_id", None),
|
||||
**node_data)
|
||||
node = await project.add_node(compute, node_data.pop("name"), node_data.pop("node_id", None), **node_data)
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.get("",
|
||||
response_model=List[schemas.Node],
|
||||
response_model_exclude_unset=True)
|
||||
@router.get("", response_model=List[schemas.Node], response_model_exclude_unset=True)
|
||||
async def get_nodes(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Return all nodes belonging to a given project.
|
||||
@ -131,8 +129,7 @@ async def get_nodes(project: Project = Depends(dep_project)):
|
||||
return [v.__json__() for v in project.nodes.values()]
|
||||
|
||||
|
||||
@router.post("/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_all_nodes(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Start all nodes belonging to a given project.
|
||||
@ -141,8 +138,7 @@ async def start_all_nodes(project: Project = Depends(dep_project)):
|
||||
await project.start_all()
|
||||
|
||||
|
||||
@router.post("/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_all_nodes(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Stop all nodes belonging to a given project.
|
||||
@ -151,8 +147,7 @@ async def stop_all_nodes(project: Project = Depends(dep_project)):
|
||||
await project.stop_all()
|
||||
|
||||
|
||||
@router.post("/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def suspend_all_nodes(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Suspend all nodes belonging to a given project.
|
||||
@ -161,8 +156,7 @@ async def suspend_all_nodes(project: Project = Depends(dep_project)):
|
||||
await project.suspend_all()
|
||||
|
||||
|
||||
@router.post("/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reload_all_nodes(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Reload all nodes belonging to a given project.
|
||||
@ -172,8 +166,7 @@ async def reload_all_nodes(project: Project = Depends(dep_project)):
|
||||
await project.start_all()
|
||||
|
||||
|
||||
@router.get("/{node_id}",
|
||||
response_model=schemas.Node)
|
||||
@router.get("/{node_id}", response_model=schemas.Node)
|
||||
def get_node(node: Node = Depends(dep_node)):
|
||||
"""
|
||||
Return a node from a given project.
|
||||
@ -182,9 +175,7 @@ def get_node(node: Node = Depends(dep_node)):
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.put("/{node_id}",
|
||||
response_model=schemas.Node,
|
||||
response_model_exclude_unset=True)
|
||||
@router.put("/{node_id}", response_model=schemas.Node, response_model_exclude_unset=True)
|
||||
async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_node)):
|
||||
"""
|
||||
Update a node.
|
||||
@ -201,10 +192,11 @@ async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_no
|
||||
return node.__json__()
|
||||
|
||||
|
||||
@router.delete("/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={**responses,
|
||||
409: {"model": schemas.ErrorMessage, "description": "Cannot delete node"}})
|
||||
@router.delete(
|
||||
"/{node_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Cannot delete node"}},
|
||||
)
|
||||
async def delete_node(node_id: UUID, project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Delete a node from a project.
|
||||
@ -213,23 +205,17 @@ async def delete_node(node_id: UUID, project: Project = Depends(dep_project)):
|
||||
await project.delete_node(str(node_id))
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate",
|
||||
response_model=schemas.Node,
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.Node, status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_node(duplicate_data: schemas.NodeDuplicate, node: Node = Depends(dep_node)):
|
||||
"""
|
||||
Duplicate a node.
|
||||
"""
|
||||
|
||||
new_node = await node.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("/{node_id}/start",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_node(start_data: dict, node: Node = Depends(dep_node)):
|
||||
"""
|
||||
Start a node.
|
||||
@ -238,8 +224,7 @@ async def start_node(start_data: dict, node: Node = Depends(dep_node)):
|
||||
await node.start(data=start_data)
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_node(node: Node = Depends(dep_node)):
|
||||
"""
|
||||
Stop a node.
|
||||
@ -248,8 +233,7 @@ async def stop_node(node: Node = Depends(dep_node)):
|
||||
await node.stop()
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def suspend_node(node: Node = Depends(dep_node)):
|
||||
"""
|
||||
Suspend a node.
|
||||
@ -258,8 +242,7 @@ async def suspend_node(node: Node = Depends(dep_node)):
|
||||
await node.suspend()
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reload_node(node: Node = Depends(dep_node)):
|
||||
"""
|
||||
Reload a node.
|
||||
@ -268,9 +251,7 @@ async def reload_node(node: Node = Depends(dep_node)):
|
||||
await node.reload()
|
||||
|
||||
|
||||
@router.get("/{node_id}/links",
|
||||
response_model=List[schemas.Link],
|
||||
response_model_exclude_unset=True)
|
||||
@router.get("/{node_id}/links", response_model=List[schemas.Link], response_model_exclude_unset=True)
|
||||
async def get_node_links(node: Node = Depends(dep_node)):
|
||||
"""
|
||||
Return all the links connected to a node.
|
||||
@ -300,8 +281,7 @@ async def idlepc_proposals(node: Node = Depends(dep_node)):
|
||||
return await node.dynamips_idlepc_proposals()
|
||||
|
||||
|
||||
@router.post("/{node_id}/resize_disk",
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/{node_id}/resize_disk", status_code=status.HTTP_201_CREATED)
|
||||
async def resize_disk(resize_data: dict, node: Node = Depends(dep_node)):
|
||||
"""
|
||||
Resize a disk image.
|
||||
@ -328,8 +308,7 @@ async def get_file(file_path: str, node: Node = Depends(dep_node)):
|
||||
return Response(res.body, media_type="application/octet-stream")
|
||||
|
||||
|
||||
@router.post("/{node_id}/files/{file_path:path}",
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/{node_id}/files/{file_path:path}", status_code=status.HTTP_201_CREATED)
|
||||
async def post_file(file_path: str, request: Request, node: Node = Depends(dep_node)):
|
||||
"""
|
||||
Write a file in the node directory.
|
||||
@ -344,7 +323,7 @@ async def post_file(file_path: str, request: Request, node: Node = Depends(dep_n
|
||||
node_type = node.node_type
|
||||
path = f"/project-files/{node_type}/{node.id}/{path}"
|
||||
|
||||
data = await request.body() #FIXME: are we handling timeout or large files correctly?
|
||||
data = await request.body() # FIXME: are we handling timeout or large files correctly?
|
||||
|
||||
await node.compute.http_query("POST", f"/projects/{node.project.id}/files{path}", data=data, timeout=None, raw=True)
|
||||
|
||||
@ -357,9 +336,13 @@ async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)):
|
||||
|
||||
compute = node.compute
|
||||
await websocket.accept()
|
||||
log.info(f"New client {websocket.client.host}:{websocket.client.port} has connected to controller console WebSocket")
|
||||
ws_console_compute_url = f"ws://{compute.host}:{compute.port}/v3/compute/projects/" \
|
||||
f"{node.project.id}/{node.node_type}/nodes/{node.id}/console/ws"
|
||||
log.info(
|
||||
f"New client {websocket.client.host}:{websocket.client.port} has connected to controller console WebSocket"
|
||||
)
|
||||
ws_console_compute_url = (
|
||||
f"ws://{compute.host}:{compute.port}/v3/compute/projects/"
|
||||
f"{node.project.id}/{node.node_type}/nodes/{node.id}/console/ws"
|
||||
)
|
||||
|
||||
async def ws_receive(ws_console_compute):
|
||||
"""
|
||||
@ -373,8 +356,10 @@ async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)):
|
||||
await ws_console_compute.send_str(data)
|
||||
except WebSocketDisconnect:
|
||||
await ws_console_compute.close()
|
||||
log.info(f"Client {websocket.client.host}:{websocket.client.port} has disconnected from controller"
|
||||
f" console WebSocket")
|
||||
log.info(
|
||||
f"Client {websocket.client.host}:{websocket.client.port} has disconnected from controller"
|
||||
f" console WebSocket"
|
||||
)
|
||||
|
||||
try:
|
||||
# receive WebSocket data from compute console WebSocket and forward to client.
|
||||
@ -391,8 +376,7 @@ async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)):
|
||||
log.error(f"Client error received when forwarding to compute console WebSocket: {e}")
|
||||
|
||||
|
||||
@router.post("/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console_all_nodes(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Reset console for all nodes belonging to the project.
|
||||
@ -401,8 +385,7 @@ async def reset_console_all_nodes(project: Project = Depends(dep_project)):
|
||||
await project.reset_console_all()
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def console_reset(node: Node = Depends(dep_node)):
|
||||
|
||||
await node.post("/console/reset")#, request.json)
|
||||
await node.post("/console/reset") # , request.json)
|
||||
|
@ -25,6 +25,7 @@ from websockets.exceptions import ConnectionClosed, WebSocketException
|
||||
from gns3server.controller import Controller
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
|
@ -26,6 +26,7 @@ import aiofiles
|
||||
import time
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
from fastapi import APIRouter, Depends, Request, Body, HTTPException, status, WebSocket, WebSocketDisconnect
|
||||
@ -45,9 +46,7 @@ from gns3server.controller.export_project import export_project as export_contro
|
||||
from gns3server.utils.asyncio import aiozipstream
|
||||
from gns3server.config import Config
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -64,9 +63,7 @@ def dep_project(project_id: UUID):
|
||||
CHUNK_SIZE = 1024 * 8 # 8KB
|
||||
|
||||
|
||||
@router.get("",
|
||||
response_model=List[schemas.Project],
|
||||
response_model_exclude_unset=True)
|
||||
@router.get("", response_model=List[schemas.Project], response_model_exclude_unset=True)
|
||||
def get_projects():
|
||||
"""
|
||||
Return all projects.
|
||||
@ -76,11 +73,13 @@ def get_projects():
|
||||
return [p.__json__() for p in controller.projects.values()]
|
||||
|
||||
|
||||
@router.post("",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
response_model_exclude_unset=True,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create project"}})
|
||||
@router.post(
|
||||
"",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
response_model_exclude_unset=True,
|
||||
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create project"}},
|
||||
)
|
||||
async def create_project(project_data: schemas.ProjectCreate):
|
||||
"""
|
||||
Create a new project.
|
||||
@ -91,8 +90,7 @@ async def create_project(project_data: schemas.ProjectCreate):
|
||||
return project.__json__()
|
||||
|
||||
|
||||
@router.get("/{project_id}",
|
||||
response_model=schemas.Project)
|
||||
@router.get("/{project_id}", response_model=schemas.Project)
|
||||
def get_project(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Return a project.
|
||||
@ -101,9 +99,7 @@ def get_project(project: Project = Depends(dep_project)):
|
||||
return project.__json__()
|
||||
|
||||
|
||||
@router.put("/{project_id}",
|
||||
response_model=schemas.Project,
|
||||
response_model_exclude_unset=True)
|
||||
@router.put("/{project_id}", response_model=schemas.Project, response_model_exclude_unset=True)
|
||||
async def update_project(project_data: schemas.ProjectUpdate, project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Update a project.
|
||||
@ -113,8 +109,7 @@ async def update_project(project_data: schemas.ProjectUpdate, project: Project =
|
||||
return project.__json__()
|
||||
|
||||
|
||||
@router.delete("/{project_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{project_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_project(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Delete a project.
|
||||
@ -134,12 +129,11 @@ def get_project_stats(project: Project = Depends(dep_project)):
|
||||
return project.stats()
|
||||
|
||||
|
||||
@router.post("/{project_id}/close",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={
|
||||
**responses,
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not close project"}
|
||||
})
|
||||
@router.post(
|
||||
"/{project_id}/close",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not close project"}},
|
||||
)
|
||||
async def close_project(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Close a project.
|
||||
@ -148,13 +142,12 @@ async def close_project(project: Project = Depends(dep_project)):
|
||||
await project.close()
|
||||
|
||||
|
||||
@router.post("/{project_id}/open",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
responses={
|
||||
**responses,
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not open project"}
|
||||
})
|
||||
@router.post(
|
||||
"/{project_id}/open",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not open project"}},
|
||||
)
|
||||
async def open_project(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Open a project.
|
||||
@ -164,13 +157,12 @@ async def open_project(project: Project = Depends(dep_project)):
|
||||
return project.__json__()
|
||||
|
||||
|
||||
@router.post("/load",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
responses={
|
||||
**responses,
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not load project"}
|
||||
})
|
||||
@router.post(
|
||||
"/load",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not load project"}},
|
||||
)
|
||||
async def load_project(path: str = Body(..., embed=True)):
|
||||
"""
|
||||
Load a project (local server only).
|
||||
@ -181,7 +173,9 @@ async def load_project(path: str = Body(..., embed=True)):
|
||||
if Config.instance().settings.Server.local is False:
|
||||
log.error(f"Cannot load '{dot_gns3_file}' because the server has not been started with the '--local' parameter")
|
||||
raise ControllerForbiddenError("Cannot load project when server is not local")
|
||||
project = await controller.load_project(dot_gns3_file,)
|
||||
project = await controller.load_project(
|
||||
dot_gns3_file,
|
||||
)
|
||||
return project.__json__()
|
||||
|
||||
|
||||
@ -248,11 +242,13 @@ async def notification_ws(project_id: UUID, websocket: WebSocket):
|
||||
|
||||
|
||||
@router.get("/{project_id}/export")
|
||||
async def export_project(project: Project = Depends(dep_project),
|
||||
include_snapshots: bool = False,
|
||||
include_images: bool = False,
|
||||
reset_mac_addresses: bool = False,
|
||||
compression: str = "zip"):
|
||||
async def export_project(
|
||||
project: Project = Depends(dep_project),
|
||||
include_snapshots: bool = False,
|
||||
include_images: bool = False,
|
||||
reset_mac_addresses: bool = False,
|
||||
compression: str = "zip",
|
||||
):
|
||||
"""
|
||||
Export a project as a portable archive.
|
||||
"""
|
||||
@ -275,12 +271,14 @@ async def export_project(project: Project = Depends(dep_project),
|
||||
async def streamer():
|
||||
with tempfile.TemporaryDirectory(dir=working_dir) as tmpdir:
|
||||
with aiozipstream.ZipFile(compression=compression) as zstream:
|
||||
await export_controller_project(zstream,
|
||||
project,
|
||||
tmpdir,
|
||||
include_snapshots=include_snapshots,
|
||||
include_images=include_images,
|
||||
reset_mac_addresses=reset_mac_addresses)
|
||||
await export_controller_project(
|
||||
zstream,
|
||||
project,
|
||||
tmpdir,
|
||||
include_snapshots=include_snapshots,
|
||||
include_images=include_images,
|
||||
reset_mac_addresses=reset_mac_addresses,
|
||||
)
|
||||
async for chunk in zstream:
|
||||
yield chunk
|
||||
|
||||
@ -295,9 +293,7 @@ async def export_project(project: Project = Depends(dep_project),
|
||||
return StreamingResponse(streamer(), media_type="application/gns3project", headers=headers)
|
||||
|
||||
|
||||
@router.post("/{project_id}/import",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project)
|
||||
@router.post("/{project_id}/import", status_code=status.HTTP_201_CREATED, response_model=schemas.Project)
|
||||
async def import_project(project_id: UUID, request: Request, path: Optional[Path] = None, name: Optional[str] = None):
|
||||
"""
|
||||
Import a project from a portable archive.
|
||||
@ -318,7 +314,7 @@ async def import_project(project_id: UUID, request: Request, path: Optional[Path
|
||||
working_dir = controller.projects_directory()
|
||||
with tempfile.TemporaryDirectory(dir=working_dir) as tmpdir:
|
||||
temp_project_path = os.path.join(tmpdir, "project.zip")
|
||||
async with aiofiles.open(temp_project_path, 'wb') as f:
|
||||
async with aiofiles.open(temp_project_path, "wb") as f:
|
||||
async for chunk in request.stream():
|
||||
await f.write(chunk)
|
||||
with open(temp_project_path, "rb") as f:
|
||||
@ -330,13 +326,12 @@ async def import_project(project_id: UUID, request: Request, path: Optional[Path
|
||||
return project.__json__()
|
||||
|
||||
|
||||
@router.post("/{project_id}/duplicate",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
responses={
|
||||
**responses,
|
||||
409: {"model": schemas.ErrorMessage, "description": "Could not duplicate project"}
|
||||
})
|
||||
@router.post(
|
||||
"/{project_id}/duplicate",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project,
|
||||
responses={**responses, 409: {"model": schemas.ErrorMessage, "description": "Could not duplicate project"}},
|
||||
)
|
||||
async def duplicate_project(project_data: schemas.ProjectDuplicate, project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Duplicate a project.
|
||||
@ -350,7 +345,9 @@ async def duplicate_project(project_data: schemas.ProjectDuplicate, project: Pro
|
||||
location = None
|
||||
|
||||
reset_mac_addresses = project_data.reset_mac_addresses
|
||||
new_project = await project.duplicate(name=project_data.name, location=location, reset_mac_addresses=reset_mac_addresses)
|
||||
new_project = await project.duplicate(
|
||||
name=project_data.name, location=location, reset_mac_addresses=reset_mac_addresses
|
||||
)
|
||||
return new_project.__json__()
|
||||
|
||||
|
||||
@ -360,7 +357,7 @@ async def get_file(file_path: str, project: Project = Depends(dep_project)):
|
||||
Return a file from a project.
|
||||
"""
|
||||
|
||||
path = os.path.normpath(file_path).strip('/')
|
||||
path = os.path.normpath(file_path).strip("/")
|
||||
|
||||
# Raise error if user try to escape
|
||||
if path[0] == ".":
|
||||
@ -373,8 +370,7 @@ async def get_file(file_path: str, project: Project = Depends(dep_project)):
|
||||
return FileResponse(path, media_type="application/octet-stream")
|
||||
|
||||
|
||||
@router.post("/{project_id}/files/{file_path:path}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{project_id}/files/{file_path:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def write_file(file_path: str, request: Request, project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Write a file from a project.
|
||||
@ -389,7 +385,7 @@ async def write_file(file_path: str, request: Request, project: Project = Depend
|
||||
path = os.path.join(project.path, path)
|
||||
|
||||
try:
|
||||
async with aiofiles.open(path, 'wb+') as f:
|
||||
async with aiofiles.open(path, "wb+") as f:
|
||||
async for chunk in request.stream():
|
||||
await f.write(chunk)
|
||||
except FileNotFoundError:
|
||||
|
@ -20,6 +20,7 @@ API routes for snapshots.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger()
|
||||
|
||||
from fastapi import APIRouter, Depends, status
|
||||
@ -30,9 +31,7 @@ from gns3server.controller.project import Project
|
||||
from gns3server import schemas
|
||||
from gns3server.controller import Controller
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find project or snapshot"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or snapshot"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
@ -46,9 +45,7 @@ def dep_project(project_id: UUID):
|
||||
return project
|
||||
|
||||
|
||||
@router.post("",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Snapshot)
|
||||
@router.post("", status_code=status.HTTP_201_CREATED, response_model=schemas.Snapshot)
|
||||
async def create_snapshot(snapshot_data: schemas.SnapshotCreate, project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Create a new snapshot of a project.
|
||||
@ -58,9 +55,7 @@ async def create_snapshot(snapshot_data: schemas.SnapshotCreate, project: Projec
|
||||
return snapshot.__json__()
|
||||
|
||||
|
||||
@router.get("",
|
||||
response_model=List[schemas.Snapshot],
|
||||
response_model_exclude_unset=True)
|
||||
@router.get("", response_model=List[schemas.Snapshot], response_model_exclude_unset=True)
|
||||
def get_snapshots(project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Return all snapshots belonging to a given project.
|
||||
@ -70,8 +65,7 @@ def get_snapshots(project: Project = Depends(dep_project)):
|
||||
return [s.__json__() for s in sorted(snapshots, key=lambda s: (s.created_at, s.name))]
|
||||
|
||||
|
||||
@router.delete("/{snapshot_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{snapshot_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Delete a snapshot.
|
||||
@ -80,9 +74,7 @@ async def delete_snapshot(snapshot_id: UUID, project: Project = Depends(dep_proj
|
||||
await project.delete_snapshot(str(snapshot_id))
|
||||
|
||||
|
||||
@router.post("/{snapshot_id}/restore",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
response_model=schemas.Project)
|
||||
@router.post("/{snapshot_id}/restore", status_code=status.HTTP_201_CREATED, response_model=schemas.Project)
|
||||
async def restore_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)):
|
||||
"""
|
||||
Restore a snapshot.
|
||||
|
@ -29,6 +29,7 @@ from gns3server import schemas
|
||||
from gns3server.controller.controller_error import ControllerError, ControllerNotFoundError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -42,8 +43,9 @@ def get_symbols():
|
||||
return controller.symbols.list()
|
||||
|
||||
|
||||
@router.get("/{symbol_id:path}/raw",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find symbol"}})
|
||||
@router.get(
|
||||
"/{symbol_id:path}/raw", responses={404: {"model": schemas.ErrorMessage, "description": "Could not find symbol"}}
|
||||
)
|
||||
async def get_symbol(symbol_id: str):
|
||||
"""
|
||||
Download a symbol file.
|
||||
@ -57,8 +59,10 @@ async def get_symbol(symbol_id: str):
|
||||
return ControllerNotFoundError(f"Could not get symbol file: {e}")
|
||||
|
||||
|
||||
@router.get("/{symbol_id:path}/dimensions",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find symbol"}})
|
||||
@router.get(
|
||||
"/{symbol_id:path}/dimensions",
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find symbol"}},
|
||||
)
|
||||
async def get_symbol_dimensions(symbol_id: str):
|
||||
"""
|
||||
Get a symbol dimensions.
|
||||
@ -67,14 +71,13 @@ async def get_symbol_dimensions(symbol_id: str):
|
||||
controller = Controller.instance()
|
||||
try:
|
||||
width, height, _ = controller.symbols.get_size(symbol_id)
|
||||
symbol_dimensions = {'width': width, 'height': height}
|
||||
symbol_dimensions = {"width": width, "height": height}
|
||||
return symbol_dimensions
|
||||
except (KeyError, OSError, ValueError) as e:
|
||||
return ControllerNotFoundError(f"Could not get symbol file: {e}")
|
||||
|
||||
|
||||
@router.post("/{symbol_id:path}/raw",
|
||||
status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.post("/{symbol_id:path}/raw", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def upload_symbol(symbol_id: str, request: Request):
|
||||
"""
|
||||
Upload a symbol file.
|
||||
|
@ -22,6 +22,7 @@ import hashlib
|
||||
import json
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
from fastapi import APIRouter, Request, Response, HTTPException, Depends, status
|
||||
@ -34,17 +35,15 @@ from gns3server.db.repositories.templates import TemplatesRepository
|
||||
from gns3server.services.templates import TemplatesService
|
||||
from .dependencies.database import get_repository
|
||||
|
||||
responses = {
|
||||
404: {"model": schemas.ErrorMessage, "description": "Could not find template"}
|
||||
}
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find template"}}
|
||||
|
||||
router = APIRouter(responses=responses)
|
||||
|
||||
|
||||
@router.post("/templates", response_model=schemas.Template, status_code=status.HTTP_201_CREATED)
|
||||
async def create_template(
|
||||
template_create: schemas.TemplateCreate,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
template_create: schemas.TemplateCreate,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
) -> dict:
|
||||
"""
|
||||
Create a new template.
|
||||
@ -53,14 +52,12 @@ async def create_template(
|
||||
return await TemplatesService(templates_repo).create_template(template_create)
|
||||
|
||||
|
||||
@router.get("/templates/{template_id}",
|
||||
response_model=schemas.Template,
|
||||
response_model_exclude_unset=True)
|
||||
@router.get("/templates/{template_id}", response_model=schemas.Template, response_model_exclude_unset=True)
|
||||
async def get_template(
|
||||
template_id: UUID,
|
||||
request: Request,
|
||||
response: Response,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
template_id: UUID,
|
||||
request: Request,
|
||||
response: Response,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
) -> dict:
|
||||
"""
|
||||
Return a template.
|
||||
@ -77,13 +74,11 @@ async def get_template(
|
||||
return template
|
||||
|
||||
|
||||
@router.put("/templates/{template_id}",
|
||||
response_model=schemas.Template,
|
||||
response_model_exclude_unset=True)
|
||||
@router.put("/templates/{template_id}", response_model=schemas.Template, response_model_exclude_unset=True)
|
||||
async def update_template(
|
||||
template_id: UUID,
|
||||
template_update: schemas.TemplateUpdate,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
template_id: UUID,
|
||||
template_update: schemas.TemplateUpdate,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
) -> dict:
|
||||
"""
|
||||
Update a template.
|
||||
@ -92,11 +87,12 @@ async def update_template(
|
||||
return await TemplatesService(templates_repo).update_template(template_id, template_update)
|
||||
|
||||
|
||||
@router.delete("/templates/{template_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,)
|
||||
@router.delete(
|
||||
"/templates/{template_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
async def delete_template(
|
||||
template_id: UUID,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
template_id: UUID, templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
) -> None:
|
||||
"""
|
||||
Delete a template.
|
||||
@ -105,11 +101,9 @@ async def delete_template(
|
||||
await TemplatesService(templates_repo).delete_template(template_id)
|
||||
|
||||
|
||||
@router.get("/templates",
|
||||
response_model=List[schemas.Template],
|
||||
response_model_exclude_unset=True)
|
||||
@router.get("/templates", response_model=List[schemas.Template], response_model_exclude_unset=True)
|
||||
async def get_templates(
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
) -> List[dict]:
|
||||
"""
|
||||
Return all templates.
|
||||
@ -118,12 +112,9 @@ async def get_templates(
|
||||
return await TemplatesService(templates_repo).get_templates()
|
||||
|
||||
|
||||
@router.post("/templates/{template_id}/duplicate",
|
||||
response_model=schemas.Template,
|
||||
status_code=status.HTTP_201_CREATED)
|
||||
@router.post("/templates/{template_id}/duplicate", response_model=schemas.Template, status_code=status.HTTP_201_CREATED)
|
||||
async def duplicate_template(
|
||||
template_id: UUID,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
template_id: UUID, templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
) -> dict:
|
||||
"""
|
||||
Duplicate a template.
|
||||
@ -132,15 +123,17 @@ async def duplicate_template(
|
||||
return await TemplatesService(templates_repo).duplicate_template(template_id)
|
||||
|
||||
|
||||
@router.post("/projects/{project_id}/templates/{template_id}",
|
||||
response_model=schemas.Node,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or template"}})
|
||||
@router.post(
|
||||
"/projects/{project_id}/templates/{template_id}",
|
||||
response_model=schemas.Node,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or template"}},
|
||||
)
|
||||
async def create_node_from_template(
|
||||
project_id: UUID,
|
||||
template_id: UUID,
|
||||
template_usage: schemas.TemplateUsage,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||
project_id: UUID,
|
||||
template_id: UUID,
|
||||
template_usage: schemas.TemplateUsage,
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
) -> schemas.Node:
|
||||
"""
|
||||
Create a new node from a template.
|
||||
@ -149,8 +142,7 @@ async def create_node_from_template(
|
||||
template = TemplatesService(templates_repo).get_template(template_id)
|
||||
controller = Controller.instance()
|
||||
project = controller.get_project(str(project_id))
|
||||
node = await project.add_node_from_template(template,
|
||||
x=template_usage.x,
|
||||
y=template_usage.y,
|
||||
compute_id=template_usage.compute_id)
|
||||
node = await project.add_node_from_template(
|
||||
template, x=template_usage.x, y=template_usage.y, compute_id=template_usage.compute_id
|
||||
)
|
||||
return node.__json__()
|
||||
|
@ -28,7 +28,7 @@ from gns3server import schemas
|
||||
from gns3server.controller.controller_error import (
|
||||
ControllerBadRequestError,
|
||||
ControllerNotFoundError,
|
||||
ControllerUnauthorizedError
|
||||
ControllerUnauthorizedError,
|
||||
)
|
||||
|
||||
from gns3server.db.repositories.users import UsersRepository
|
||||
@ -38,6 +38,7 @@ from .dependencies.authentication import get_current_active_user
|
||||
from .dependencies.database import get_repository
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter()
|
||||
@ -54,8 +55,7 @@ async def get_users(users_repo: UsersRepository = Depends(get_repository(UsersRe
|
||||
|
||||
@router.post("", response_model=schemas.User, status_code=status.HTTP_201_CREATED)
|
||||
async def create_user(
|
||||
user_create: schemas.UserCreate,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
user_create: schemas.UserCreate, users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
) -> schemas.User:
|
||||
"""
|
||||
Create a new user.
|
||||
@ -72,8 +72,7 @@ async def create_user(
|
||||
|
||||
@router.get("/{user_id}", response_model=schemas.User)
|
||||
async def get_user(
|
||||
user_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
user_id: UUID, users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
) -> schemas.User:
|
||||
"""
|
||||
Get an user.
|
||||
@ -87,9 +86,9 @@ async def get_user(
|
||||
|
||||
@router.put("/{user_id}", response_model=schemas.User)
|
||||
async def update_user(
|
||||
user_id: UUID,
|
||||
user_update: schemas.UserUpdate,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
user_id: UUID,
|
||||
user_update: schemas.UserUpdate,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> schemas.User:
|
||||
"""
|
||||
Update an user.
|
||||
@ -103,9 +102,9 @@ async def update_user(
|
||||
|
||||
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_user(
|
||||
user_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
current_user: schemas.User = Depends(get_current_active_user)
|
||||
user_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
) -> None:
|
||||
"""
|
||||
Delete an user.
|
||||
@ -121,8 +120,8 @@ async def delete_user(
|
||||
|
||||
@router.post("/login", response_model=schemas.Token)
|
||||
async def login(
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
form_data: OAuth2PasswordRequestForm = Depends()
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
form_data: OAuth2PasswordRequestForm = Depends(),
|
||||
) -> schemas.Token:
|
||||
"""
|
||||
User login.
|
||||
@ -130,9 +129,11 @@ async def login(
|
||||
|
||||
user = await users_repo.authenticate_user(username=form_data.username, password=form_data.password)
|
||||
if not user:
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Authentication was unsuccessful.",
|
||||
headers={"WWW-Authenticate": "Bearer"})
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Authentication was unsuccessful.",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
token = schemas.Token(access_token=auth_service.create_access_token(user.username), token_type="bearer")
|
||||
return token
|
||||
|
@ -21,7 +21,7 @@ from fastapi.responses import RedirectResponse, HTMLResponse, FileResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
from gns3server.version import __version__
|
||||
from gns3server.utils.get_resource import get_resource
|
||||
from gns3server.utils.get_resource import get_resource
|
||||
|
||||
router = APIRouter()
|
||||
templates = Jinja2Templates(directory=os.path.join("gns3server", "templates"))
|
||||
@ -33,24 +33,18 @@ async def root():
|
||||
return RedirectResponse("/static/web-ui/bundled", status_code=308) # permanent redirect
|
||||
|
||||
|
||||
@router.get("/debug",
|
||||
response_class=HTMLResponse,
|
||||
deprecated=True)
|
||||
@router.get("/debug", response_class=HTMLResponse, deprecated=True)
|
||||
def debug(request: Request):
|
||||
|
||||
kwargs = {"request": request,
|
||||
"gns3_version": __version__,
|
||||
"gns3_host": request.client.host}
|
||||
kwargs = {"request": request, "gns3_version": __version__, "gns3_host": request.client.host}
|
||||
return templates.TemplateResponse("index.html", kwargs)
|
||||
|
||||
|
||||
@router.get("/static/web-ui/{file_path:path}",
|
||||
description="Web user interface"
|
||||
)
|
||||
@router.get("/static/web-ui/{file_path:path}", description="Web user interface")
|
||||
async def web_ui(file_path: str):
|
||||
|
||||
file_path = os.path.normpath(file_path).strip("/")
|
||||
file_path = os.path.join('static', 'web-ui', file_path)
|
||||
file_path = os.path.join("static", "web-ui", file_path)
|
||||
|
||||
# Raise error if user try to escape
|
||||
if file_path[0] == ".":
|
||||
@ -59,13 +53,13 @@ async def web_ui(file_path: str):
|
||||
static = get_resource(file_path)
|
||||
|
||||
if static is None or not os.path.exists(static):
|
||||
static = get_resource(os.path.join('static', 'web-ui', 'index.html'))
|
||||
static = get_resource(os.path.join("static", "web-ui", "index.html"))
|
||||
|
||||
# guesstype prefers to have text/html type than application/javascript
|
||||
# which results with warnings in Firefox 66 on Windows
|
||||
# Ref. gns3-server#1559
|
||||
_, ext = os.path.splitext(static)
|
||||
mimetype = ext == '.js' and 'application/javascript' or None
|
||||
mimetype = ext == ".js" and "application/javascript" or None
|
||||
return FileResponse(static, media_type=mimetype)
|
||||
|
||||
|
||||
|
@ -33,7 +33,7 @@ from gns3server.controller.controller_error import (
|
||||
ControllerBadRequestError,
|
||||
ControllerTimeoutError,
|
||||
ControllerForbiddenError,
|
||||
ControllerUnauthorizedError
|
||||
ControllerUnauthorizedError,
|
||||
)
|
||||
|
||||
from gns3server.api.routes import controller, index
|
||||
@ -42,15 +42,14 @@ from gns3server.core import tasks
|
||||
from gns3server.version import __version__
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_application() -> FastAPI:
|
||||
|
||||
application = FastAPI(
|
||||
title="GNS3 controller API",
|
||||
description="This page describes the public controller API for GNS3",
|
||||
version="v3"
|
||||
title="GNS3 controller API", description="This page describes the public controller API for GNS3", version="v3"
|
||||
)
|
||||
|
||||
origins = [
|
||||
@ -61,16 +60,16 @@ def get_application() -> FastAPI:
|
||||
"http://127.0.0.1:3080",
|
||||
"http://localhost:3080",
|
||||
"http://gns3.github.io",
|
||||
"https://gns3.github.io"
|
||||
"https://gns3.github.io",
|
||||
]
|
||||
|
||||
application.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
CORSMiddleware,
|
||||
allow_origins=origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
application.add_event_handler("startup", tasks.create_startup_handler(application))
|
||||
application.add_event_handler("shutdown", tasks.create_shutdown_handler(application))
|
||||
|
@ -27,10 +27,16 @@ from .traceng import TraceNG
|
||||
|
||||
MODULES = [Builtin, VPCS, VirtualBox, Dynamips, Qemu, VMware, TraceNG]
|
||||
|
||||
if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1":
|
||||
if (
|
||||
sys.platform.startswith("linux")
|
||||
or hasattr(sys, "_called_from_test")
|
||||
or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1"
|
||||
):
|
||||
# IOU & Docker only runs on Linux but test suite works on UNIX platform
|
||||
if not sys.platform.startswith("win"):
|
||||
from .docker import Docker
|
||||
|
||||
MODULES.append(Docker)
|
||||
from .iou import IOU
|
||||
|
||||
MODULES.append(IOU)
|
||||
|
@ -72,7 +72,7 @@ class BaseManager:
|
||||
"""
|
||||
|
||||
# By default we transform DockerVM => docker but you can override this (see builtins)
|
||||
return [cls._NODE_CLASS.__name__.rstrip('VM').lower()]
|
||||
return [cls._NODE_CLASS.__name__.rstrip("VM").lower()]
|
||||
|
||||
@property
|
||||
def nodes(self):
|
||||
@ -313,7 +313,9 @@ class BaseManager:
|
||||
# we are root, so we should have privileged access.
|
||||
return True
|
||||
|
||||
if os.stat(executable).st_uid == 0 and (os.stat(executable).st_mode & stat.S_ISUID or os.stat(executable).st_mode & stat.S_ISGID):
|
||||
if os.stat(executable).st_uid == 0 and (
|
||||
os.stat(executable).st_mode & stat.S_ISUID or os.stat(executable).st_mode & stat.S_ISGID
|
||||
):
|
||||
# the executable has set UID bit.
|
||||
return True
|
||||
|
||||
@ -425,7 +427,9 @@ class BaseManager:
|
||||
# Windows path should not be send to a unix server
|
||||
if not sys.platform.startswith("win"):
|
||||
if re.match(r"^[A-Z]:", path) is not None:
|
||||
raise NodeError(f"'{path}' is not allowed on this remote server. Please only use a file from '{img_directory}'")
|
||||
raise NodeError(
|
||||
f"'{path}' is not allowed on this remote server. Please only use a file from '{img_directory}'"
|
||||
)
|
||||
|
||||
if not os.path.isabs(path):
|
||||
for directory in valid_directory_prefices:
|
||||
@ -471,7 +475,7 @@ class BaseManager:
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for file in files:
|
||||
# If filename is the same
|
||||
if s[1] == file and (s[0] == '' or s[0] == os.path.basename(root)):
|
||||
if s[1] == file and (s[0] == "" or s[0] == os.path.basename(root)):
|
||||
path = os.path.normpath(os.path.join(root, s[1]))
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
@ -540,7 +544,7 @@ class BaseManager:
|
||||
# We store the file under his final name only when the upload is finished
|
||||
tmp_path = path + ".tmp"
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
async with aiofiles.open(tmp_path, 'wb') as f:
|
||||
async with aiofiles.open(tmp_path, "wb") as f:
|
||||
async for chunk in stream:
|
||||
await f.write(chunk)
|
||||
os.chmod(tmp_path, stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)
|
||||
|
@ -36,6 +36,7 @@ from .nios.nio_udp import NIOUDP
|
||||
from .error import NodeError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -57,7 +58,20 @@ class BaseNode:
|
||||
:param wrap_aux: The auxiliary console is wrapped using AsyncioTelnetServer
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, console=None, console_type="telnet", aux=None, aux_type="none", linked_clone=True, wrap_console=False, wrap_aux=False):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
linked_clone=True,
|
||||
wrap_console=False,
|
||||
wrap_aux=False,
|
||||
):
|
||||
|
||||
self._name = name
|
||||
self._usage = ""
|
||||
@ -102,7 +116,9 @@ class BaseNode:
|
||||
# use a previously allocated auxiliary console port
|
||||
if aux_type == "vnc":
|
||||
# VNC is a special case and the range must be 5900-6000
|
||||
self._aux = self._manager.port_manager.reserve_tcp_port(self._aux, self._project, port_range_start=5900, port_range_end=6000)
|
||||
self._aux = self._manager.port_manager.reserve_tcp_port(
|
||||
self._aux, self._project, port_range_start=5900, port_range_end=6000
|
||||
)
|
||||
elif aux_type == "none":
|
||||
self._aux = None
|
||||
else:
|
||||
@ -115,7 +131,8 @@ class BaseNode:
|
||||
self._console = self._manager.port_manager.get_free_tcp_port(
|
||||
self._project,
|
||||
port_range_start=vnc_console_start_port_range,
|
||||
port_range_end=vnc_console_end_port_range)
|
||||
port_range_end=vnc_console_end_port_range,
|
||||
)
|
||||
elif console_type != "none":
|
||||
self._console = self._manager.port_manager.get_free_tcp_port(self._project)
|
||||
|
||||
@ -123,7 +140,9 @@ class BaseNode:
|
||||
# allocate a new auxiliary console
|
||||
if aux_type == "vnc":
|
||||
# VNC is a special case and the range must be 5900-6000
|
||||
self._aux = self._manager.port_manager.get_free_tcp_port(self._project, port_range_start=5900, port_range_end=6000)
|
||||
self._aux = self._manager.port_manager.get_free_tcp_port(
|
||||
self._project, port_range_start=5900, port_range_end=6000
|
||||
)
|
||||
elif aux_type != "none":
|
||||
self._aux = self._manager.port_manager.get_free_tcp_port(self._project)
|
||||
|
||||
@ -133,10 +152,11 @@ class BaseNode:
|
||||
if self._wrap_aux:
|
||||
self._internal_aux_port = self._manager.port_manager.get_free_tcp_port(self._project)
|
||||
|
||||
log.debug("{module}: {name} [{id}] initialized. Console port {console}".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
console=self._console))
|
||||
log.debug(
|
||||
"{module}: {name} [{id}] initialized. Console port {console}".format(
|
||||
module=self.manager.module_name, name=self.name, id=self.id, console=self._console
|
||||
)
|
||||
)
|
||||
|
||||
def __del__(self):
|
||||
|
||||
@ -221,10 +241,11 @@ class BaseNode:
|
||||
:param new_name: name
|
||||
"""
|
||||
|
||||
log.info("{module}: {name} [{id}] renamed to {new_name}".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
new_name=new_name))
|
||||
log.info(
|
||||
"{module}: {name} [{id}] renamed to {new_name}".format(
|
||||
module=self.manager.module_name, name=self.name, id=self.id, new_name=new_name
|
||||
)
|
||||
)
|
||||
self._name = new_name
|
||||
|
||||
@property
|
||||
@ -297,9 +318,7 @@ class BaseNode:
|
||||
Creates the node.
|
||||
"""
|
||||
|
||||
log.info("{module}: {name} [{id}] created".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id))
|
||||
log.info("{module}: {name} [{id}] created".format(module=self.manager.module_name, name=self.name, id=self.id))
|
||||
|
||||
async def delete(self):
|
||||
"""
|
||||
@ -346,9 +365,9 @@ class BaseNode:
|
||||
if self._closed:
|
||||
return False
|
||||
|
||||
log.info("{module}: '{name}' [{id}]: is closing".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id))
|
||||
log.info(
|
||||
"{module}: '{name}' [{id}]: is closing".format(module=self.manager.module_name, name=self.name, id=self.id)
|
||||
)
|
||||
|
||||
if self._console:
|
||||
self._manager.port_manager.release_tcp_port(self._console, self._project)
|
||||
@ -380,8 +399,10 @@ class BaseNode:
|
||||
if not 5900 <= vnc_console_end_port_range <= 65535:
|
||||
raise NodeError("The VNC console start port range must be between 5900 and 65535")
|
||||
if vnc_console_start_port_range >= vnc_console_end_port_range:
|
||||
raise NodeError(f"The VNC console start port range value ({vnc_console_start_port_range}) "
|
||||
f"cannot be above or equal to the end value ({vnc_console_end_port_range})")
|
||||
raise NodeError(
|
||||
f"The VNC console start port range value ({vnc_console_start_port_range}) "
|
||||
f"cannot be above or equal to the end value ({vnc_console_end_port_range})"
|
||||
)
|
||||
|
||||
return vnc_console_start_port_range, vnc_console_end_port_range
|
||||
|
||||
@ -415,13 +436,17 @@ class BaseNode:
|
||||
|
||||
if self._wrap_console and self._console_type == "telnet":
|
||||
await self._wrap_telnet_proxy(self._internal_console_port, self.console)
|
||||
log.info(f"New Telnet proxy server for console started "
|
||||
f"(internal port = {self._internal_console_port}, external port = {self.console})")
|
||||
log.info(
|
||||
f"New Telnet proxy server for console started "
|
||||
f"(internal port = {self._internal_console_port}, external port = {self.console})"
|
||||
)
|
||||
|
||||
if self._wrap_aux and self._aux_type == "telnet":
|
||||
await self._wrap_telnet_proxy(self._internal_aux_port, self.aux)
|
||||
log.info(f"New Telnet proxy server for auxiliary console started "
|
||||
f"(internal port = {self._internal_aux_port}, external port = {self.aux})")
|
||||
log.info(
|
||||
f"New Telnet proxy server for auxiliary console started "
|
||||
f"(internal port = {self._internal_aux_port}, external port = {self.aux})"
|
||||
)
|
||||
|
||||
async def stop_wrap_console(self):
|
||||
"""
|
||||
@ -455,16 +480,19 @@ class BaseNode:
|
||||
raise NodeError(f"Node {self.name} console type is not telnet")
|
||||
|
||||
try:
|
||||
(telnet_reader, telnet_writer) = await asyncio.open_connection(self._manager.port_manager.console_host,
|
||||
self.console)
|
||||
(telnet_reader, telnet_writer) = await asyncio.open_connection(
|
||||
self._manager.port_manager.console_host, self.console
|
||||
)
|
||||
except ConnectionError as e:
|
||||
raise NodeError(f"Cannot connect to node {self.name} telnet server: {e}")
|
||||
|
||||
log.info("Connected to Telnet server")
|
||||
|
||||
await websocket.accept()
|
||||
log.info(f"New client {websocket.client.host}:{websocket.client.port} has connected to compute"
|
||||
f" console WebSocket")
|
||||
log.info(
|
||||
f"New client {websocket.client.host}:{websocket.client.port} has connected to compute"
|
||||
f" console WebSocket"
|
||||
)
|
||||
|
||||
async def ws_forward(telnet_writer):
|
||||
|
||||
@ -475,8 +503,10 @@ class BaseNode:
|
||||
telnet_writer.write(data.encode())
|
||||
await telnet_writer.drain()
|
||||
except WebSocketDisconnect:
|
||||
log.info(f"Client {websocket.client.host}:{websocket.client.port} has disconnected from compute"
|
||||
f" console WebSocket")
|
||||
log.info(
|
||||
f"Client {websocket.client.host}:{websocket.client.port} has disconnected from compute"
|
||||
f" console WebSocket"
|
||||
)
|
||||
|
||||
async def telnet_forward(telnet_reader):
|
||||
|
||||
@ -486,8 +516,9 @@ class BaseNode:
|
||||
await websocket.send_bytes(data)
|
||||
|
||||
# keep forwarding WebSocket data in both direction
|
||||
done, pending = await asyncio.wait([ws_forward(telnet_writer), telnet_forward(telnet_reader)],
|
||||
return_when=asyncio.FIRST_COMPLETED)
|
||||
done, pending = await asyncio.wait(
|
||||
[ws_forward(telnet_writer), telnet_forward(telnet_reader)], return_when=asyncio.FIRST_COMPLETED
|
||||
)
|
||||
for task in done:
|
||||
if task.exception():
|
||||
log.warning(f"Exception while forwarding WebSocket data to Telnet server {task.exception()}")
|
||||
@ -524,14 +555,17 @@ class BaseNode:
|
||||
self._aux = None
|
||||
if aux is not None:
|
||||
if self.aux_type == "vnc":
|
||||
self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project, port_range_start=5900, port_range_end=6000)
|
||||
self._aux = self._manager.port_manager.reserve_tcp_port(
|
||||
aux, self._project, port_range_start=5900, port_range_end=6000
|
||||
)
|
||||
else:
|
||||
self._aux = self._manager.port_manager.reserve_tcp_port(aux, self._project)
|
||||
|
||||
log.info("{module}: '{name}' [{id}]: auxiliary console port set to {port}".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
port=aux))
|
||||
log.info(
|
||||
"{module}: '{name}' [{id}]: auxiliary console port set to {port}".format(
|
||||
module=self.manager.module_name, name=self.name, id=self.id, port=aux
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def console(self):
|
||||
@ -567,15 +601,16 @@ class BaseNode:
|
||||
console,
|
||||
self._project,
|
||||
port_range_start=vnc_console_start_port_range,
|
||||
port_range_end=vnc_console_end_port_range
|
||||
port_range_end=vnc_console_end_port_range,
|
||||
)
|
||||
else:
|
||||
self._console = self._manager.port_manager.reserve_tcp_port(console, self._project)
|
||||
|
||||
log.info("{module}: '{name}' [{id}]: console port set to {port}".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
port=console))
|
||||
log.info(
|
||||
"{module}: '{name}' [{id}]: console port set to {port}".format(
|
||||
module=self.manager.module_name, name=self.name, id=self.id, port=console
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def console_type(self):
|
||||
@ -609,11 +644,15 @@ class BaseNode:
|
||||
self._console = self._manager.port_manager.get_free_tcp_port(self._project)
|
||||
|
||||
self._console_type = console_type
|
||||
log.info("{module}: '{name}' [{id}]: console type set to {console_type} (console port is {console})".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
console_type=console_type,
|
||||
console=self.console))
|
||||
log.info(
|
||||
"{module}: '{name}' [{id}]: console type set to {console_type} (console port is {console})".format(
|
||||
module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
console_type=console_type,
|
||||
console=self.console,
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def aux_type(self):
|
||||
@ -647,11 +686,11 @@ class BaseNode:
|
||||
self._aux = self._manager.port_manager.get_free_tcp_port(self._project)
|
||||
|
||||
self._aux_type = aux_type
|
||||
log.info("{module}: '{name}' [{id}]: console type set to {aux_type} (auxiliary console port is {aux})".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
aux_type=aux_type,
|
||||
aux=self.aux))
|
||||
log.info(
|
||||
"{module}: '{name}' [{id}]: console type set to {aux_type} (auxiliary console port is {aux})".format(
|
||||
module=self.manager.module_name, name=self.name, id=self.id, aux_type=aux_type, aux=self.aux
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def ubridge(self):
|
||||
@ -700,7 +739,9 @@ class BaseNode:
|
||||
try:
|
||||
await self._ubridge_hypervisor.send(command)
|
||||
except UbridgeError as e:
|
||||
raise UbridgeError(f"Error while sending command '{command}': {e}: {self._ubridge_hypervisor.read_stdout()}")
|
||||
raise UbridgeError(
|
||||
f"Error while sending command '{command}': {e}: {self._ubridge_hypervisor.read_stdout()}"
|
||||
)
|
||||
|
||||
@locking
|
||||
async def _start_ubridge(self, require_privileged_access=False):
|
||||
@ -713,7 +754,9 @@ class BaseNode:
|
||||
return
|
||||
|
||||
if self.ubridge_path is None:
|
||||
raise NodeError("uBridge is not available, path doesn't exist, or you just installed GNS3 and need to restart your user session to refresh user permissions.")
|
||||
raise NodeError(
|
||||
"uBridge is not available, path doesn't exist, or you just installed GNS3 and need to restart your user session to refresh user permissions."
|
||||
)
|
||||
|
||||
if require_privileged_access and not self._manager.has_privileged_access(self.ubridge_path):
|
||||
raise NodeError("uBridge requires root access or the capability to interact with network adapters")
|
||||
@ -724,7 +767,9 @@ class BaseNode:
|
||||
log.info(f"Starting new uBridge hypervisor {self._ubridge_hypervisor.host}:{self._ubridge_hypervisor.port}")
|
||||
await self._ubridge_hypervisor.start()
|
||||
if self._ubridge_hypervisor:
|
||||
log.info(f"Hypervisor {self._ubridge_hypervisor.host}:{self._ubridge_hypervisor.port} has successfully started")
|
||||
log.info(
|
||||
f"Hypervisor {self._ubridge_hypervisor.host}:{self._ubridge_hypervisor.port} has successfully started"
|
||||
)
|
||||
await self._ubridge_hypervisor.connect()
|
||||
# save if privileged are required in case uBridge needs to be restarted in self._ubridge_send()
|
||||
self._ubridge_require_privileged_access = require_privileged_access
|
||||
@ -753,24 +798,23 @@ class BaseNode:
|
||||
if not isinstance(destination_nio, NIOUDP):
|
||||
raise NodeError("Destination NIO is not UDP")
|
||||
|
||||
await self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(
|
||||
name=bridge_name,
|
||||
lport=source_nio.lport,
|
||||
rhost=source_nio.rhost,
|
||||
rport=source_nio.rport)
|
||||
await self._ubridge_send(
|
||||
"bridge add_nio_udp {name} {lport} {rhost} {rport}".format(
|
||||
name=bridge_name, lport=source_nio.lport, rhost=source_nio.rhost, rport=source_nio.rport
|
||||
)
|
||||
)
|
||||
|
||||
await self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(
|
||||
name=bridge_name,
|
||||
lport=destination_nio.lport,
|
||||
rhost=destination_nio.rhost,
|
||||
rport=destination_nio.rport)
|
||||
await self._ubridge_send(
|
||||
"bridge add_nio_udp {name} {lport} {rhost} {rport}".format(
|
||||
name=bridge_name, lport=destination_nio.lport, rhost=destination_nio.rhost, rport=destination_nio.rport
|
||||
)
|
||||
)
|
||||
|
||||
if destination_nio.capturing:
|
||||
await self._ubridge_send('bridge start_capture {name} "{pcap_file}"'.format(
|
||||
name=bridge_name,
|
||||
pcap_file=destination_nio.pcap_output_file)
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {name} "{pcap_file}"'.format(
|
||||
name=bridge_name, pcap_file=destination_nio.pcap_output_file
|
||||
)
|
||||
)
|
||||
|
||||
await self._ubridge_send(f"bridge start {bridge_name}")
|
||||
@ -796,7 +840,7 @@ class BaseNode:
|
||||
:param filters: Array of filter dictionary
|
||||
"""
|
||||
|
||||
await self._ubridge_send('bridge reset_packet_filters ' + bridge_name)
|
||||
await self._ubridge_send("bridge reset_packet_filters " + bridge_name)
|
||||
for packet_filter in self._build_filter_list(filters):
|
||||
cmd = f"bridge add_packet_filter {bridge_name} {packet_filter}"
|
||||
try:
|
||||
@ -818,18 +862,20 @@ class BaseNode:
|
||||
i = 0
|
||||
for (filter_type, values) in filters.items():
|
||||
if isinstance(values[0], str):
|
||||
for line in values[0].split('\n'):
|
||||
for line in values[0].split("\n"):
|
||||
line = line.strip()
|
||||
yield "{filter_name} {filter_type} {filter_value}".format(
|
||||
filter_name="filter" + str(i),
|
||||
filter_type=filter_type,
|
||||
filter_value='"{}" {}'.format(line, " ".join([str(v) for v in values[1:]]))).strip()
|
||||
filter_value='"{}" {}'.format(line, " ".join([str(v) for v in values[1:]])),
|
||||
).strip()
|
||||
i += 1
|
||||
else:
|
||||
yield "{filter_name} {filter_type} {filter_value}".format(
|
||||
filter_name="filter" + str(i),
|
||||
filter_type=filter_type,
|
||||
filter_value=" ".join([str(v) for v in values]))
|
||||
filter_value=" ".join([str(v) for v in values]),
|
||||
)
|
||||
i += 1
|
||||
|
||||
async def _add_ubridge_ethernet_connection(self, bridge_name, ethernet_interface, block_host_traffic=False):
|
||||
@ -843,9 +889,8 @@ class BaseNode:
|
||||
|
||||
if sys.platform.startswith("linux") and block_host_traffic is False:
|
||||
# on Linux we use RAW sockets by default excepting if host traffic must be blocked
|
||||
await self._ubridge_send('bridge add_nio_linux_raw {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=ethernet_interface)
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_linux_raw {name} "{interface}"'.format(name=bridge_name, interface=ethernet_interface)
|
||||
)
|
||||
elif sys.platform.startswith("win"):
|
||||
# on Windows we use Winpcap/Npcap
|
||||
@ -861,36 +906,32 @@ class BaseNode:
|
||||
npf_id = interface["id"]
|
||||
source_mac = interface["mac_address"]
|
||||
if npf_id:
|
||||
await self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=npf_id)
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_ethernet {name} "{interface}"'.format(name=bridge_name, interface=npf_id)
|
||||
)
|
||||
else:
|
||||
raise NodeError(f"Could not find NPF id for interface {ethernet_interface}")
|
||||
|
||||
if block_host_traffic:
|
||||
if source_mac:
|
||||
await self._ubridge_send('bridge set_pcap_filter {name} "not ether src {mac}"'.format(
|
||||
name=bridge_name,
|
||||
mac=source_mac)
|
||||
await self._ubridge_send(
|
||||
'bridge set_pcap_filter {name} "not ether src {mac}"'.format(name=bridge_name, mac=source_mac)
|
||||
)
|
||||
log.info(f"PCAP filter applied on '{ethernet_interface}' for source MAC {source_mac}")
|
||||
else:
|
||||
log.warning(f"Could not block host network traffic on {ethernet_interface} (no MAC address found)")
|
||||
else:
|
||||
# on other platforms we just rely on the pcap library
|
||||
await self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=ethernet_interface)
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_ethernet {name} "{interface}"'.format(name=bridge_name, interface=ethernet_interface)
|
||||
)
|
||||
source_mac = None
|
||||
for interface in interfaces():
|
||||
if interface["name"] == ethernet_interface:
|
||||
source_mac = interface["mac_address"]
|
||||
if source_mac:
|
||||
await self._ubridge_send('bridge set_pcap_filter {name} "not ether src {mac}"'.format(
|
||||
name=bridge_name,
|
||||
mac=source_mac)
|
||||
await self._ubridge_send(
|
||||
'bridge set_pcap_filter {name} "not ether src {mac}"'.format(name=bridge_name, mac=source_mac)
|
||||
)
|
||||
log.info(f"PCAP filter applied on '{ethernet_interface}' for source MAC {source_mac}")
|
||||
|
||||
@ -904,16 +945,14 @@ class BaseNode:
|
||||
m = PortManager.instance()
|
||||
lport = m.get_free_udp_port(self.project)
|
||||
rport = m.get_free_udp_port(self.project)
|
||||
source_nio_settings = {'lport': lport, 'rhost': '127.0.0.1', 'rport': rport, 'type': 'nio_udp'}
|
||||
destination_nio_settings = {'lport': rport, 'rhost': '127.0.0.1', 'rport': lport, 'type': 'nio_udp'}
|
||||
source_nio_settings = {"lport": lport, "rhost": "127.0.0.1", "rport": rport, "type": "nio_udp"}
|
||||
destination_nio_settings = {"lport": rport, "rhost": "127.0.0.1", "rport": lport, "type": "nio_udp"}
|
||||
source_nio = self.manager.create_nio(source_nio_settings)
|
||||
destination_nio = self.manager.create_nio(destination_nio_settings)
|
||||
log.info("{module}: '{name}' [{id}]:local UDP tunnel created between port {port1} and {port2}".format(
|
||||
module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
port1=lport,
|
||||
port2=rport)
|
||||
log.info(
|
||||
"{module}: '{name}' [{id}]:local UDP tunnel created between port {port1} and {port2}".format(
|
||||
module=self.manager.module_name, name=self.name, id=self.id, port1=lport, port2=rport
|
||||
)
|
||||
)
|
||||
return source_nio, destination_nio
|
||||
|
||||
@ -938,11 +977,7 @@ class BaseNode:
|
||||
percentage_left = psutil.virtual_memory().percent
|
||||
if requested_ram > available_ram:
|
||||
message = '"{}" requires {}MB of RAM to run but there is only {}MB - {}% of RAM left on "{}"'.format(
|
||||
self.name,
|
||||
requested_ram,
|
||||
available_ram,
|
||||
percentage_left,
|
||||
platform.node()
|
||||
self.name, requested_ram, available_ram, percentage_left, platform.node()
|
||||
)
|
||||
self.project.emit("log.warning", {"message": message})
|
||||
|
||||
|
@ -23,6 +23,7 @@ from ..base_manager import BaseManager
|
||||
from .builtin_node_factory import BuiltinNodeFactory, BUILTIN_NODES
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -39,7 +40,7 @@ class Builtin(BaseManager):
|
||||
"""
|
||||
:returns: List of node type supported by this class and computer
|
||||
"""
|
||||
types = ['cloud', 'ethernet_hub', 'ethernet_switch']
|
||||
if BUILTIN_NODES['nat'].is_supported():
|
||||
types.append('nat')
|
||||
types = ["cloud", "ethernet_hub", "ethernet_switch"]
|
||||
if BUILTIN_NODES["nat"].is_supported():
|
||||
types.append("nat")
|
||||
return types
|
||||
|
@ -22,12 +22,10 @@ from .nodes.ethernet_hub import EthernetHub
|
||||
from .nodes.ethernet_switch import EthernetSwitch
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
BUILTIN_NODES = {'cloud': Cloud,
|
||||
'nat': Nat,
|
||||
'ethernet_hub': EthernetHub,
|
||||
'ethernet_switch': EthernetSwitch}
|
||||
BUILTIN_NODES = {"cloud": Cloud, "nat": Nat, "ethernet_hub": EthernetHub, "ethernet_switch": EthernetSwitch}
|
||||
|
||||
|
||||
class BuiltinNodeFactory:
|
||||
|
@ -26,6 +26,7 @@ import gns3server.utils.interfaces
|
||||
import gns3server.utils.asyncio
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -54,12 +55,14 @@ class Cloud(BaseNode):
|
||||
self._ports_mapping = []
|
||||
for interface in self._interfaces():
|
||||
if not interface["special"]:
|
||||
self._ports_mapping.append({
|
||||
"interface": interface["name"],
|
||||
"type": interface["type"],
|
||||
"port_number": len(self._ports_mapping),
|
||||
"name": interface["name"]
|
||||
})
|
||||
self._ports_mapping.append(
|
||||
{
|
||||
"interface": interface["name"],
|
||||
"type": interface["type"],
|
||||
"port_number": len(self._ports_mapping),
|
||||
"name": interface["name"],
|
||||
}
|
||||
)
|
||||
else:
|
||||
port_number = 0
|
||||
for port in ports:
|
||||
@ -79,23 +82,24 @@ class Cloud(BaseNode):
|
||||
host_interfaces = []
|
||||
network_interfaces = gns3server.utils.interfaces.interfaces()
|
||||
for interface in network_interfaces:
|
||||
host_interfaces.append({"name": interface["name"],
|
||||
"type": interface["type"],
|
||||
"special": interface["special"]})
|
||||
host_interfaces.append(
|
||||
{"name": interface["name"], "type": interface["type"], "special": interface["special"]}
|
||||
)
|
||||
|
||||
return {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"remote_console_host": self.remote_console_host,
|
||||
"remote_console_port": self.remote_console_port,
|
||||
"remote_console_type": self.remote_console_type,
|
||||
"remote_console_http_path": self.remote_console_http_path,
|
||||
"ports_mapping": self._ports_mapping,
|
||||
"interfaces": host_interfaces,
|
||||
"status": self.status,
|
||||
"node_directory": self.working_path
|
||||
}
|
||||
return {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"remote_console_host": self.remote_console_host,
|
||||
"remote_console_port": self.remote_console_port,
|
||||
"remote_console_type": self.remote_console_type,
|
||||
"remote_console_http_path": self.remote_console_http_path,
|
||||
"ports_mapping": self._ports_mapping,
|
||||
"interfaces": host_interfaces,
|
||||
"status": self.status,
|
||||
"node_directory": self.working_path,
|
||||
}
|
||||
|
||||
@property
|
||||
def remote_console_host(self):
|
||||
@ -265,7 +269,7 @@ class Cloud(BaseNode):
|
||||
return True
|
||||
is_wifi = False
|
||||
else:
|
||||
if 'Wi-Fi' in line:
|
||||
if "Wi-Fi" in line:
|
||||
is_wifi = True
|
||||
return False
|
||||
|
||||
@ -284,27 +288,27 @@ class Cloud(BaseNode):
|
||||
break
|
||||
|
||||
if not port_info:
|
||||
raise NodeError("Port {port_number} doesn't exist on cloud '{name}'".format(
|
||||
name=self.name,
|
||||
port_number=port_number)
|
||||
raise NodeError(
|
||||
"Port {port_number} doesn't exist on cloud '{name}'".format(name=self.name, port_number=port_number)
|
||||
)
|
||||
|
||||
bridge_name = f"{self._id}-{port_number}"
|
||||
await self._ubridge_send(f"bridge create {bridge_name}")
|
||||
if not isinstance(nio, NIOUDP):
|
||||
raise NodeError("Source NIO is not UDP")
|
||||
await self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(
|
||||
name=bridge_name,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport)
|
||||
await self._ubridge_send(
|
||||
"bridge add_nio_udp {name} {lport} {rhost} {rport}".format(
|
||||
name=bridge_name, lport=nio.lport, rhost=nio.rhost, rport=nio.rport
|
||||
)
|
||||
)
|
||||
|
||||
await self._ubridge_apply_filters(bridge_name, nio.filters)
|
||||
if port_info["type"] in ("ethernet", "tap"):
|
||||
|
||||
if not self.manager.has_privileged_access(self.ubridge_path):
|
||||
raise NodeError("uBridge requires root access or the capability to interact with Ethernet and TAP adapters")
|
||||
raise NodeError(
|
||||
"uBridge requires root access or the capability to interact with Ethernet and TAP adapters"
|
||||
)
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
await self._add_ubridge_ethernet_connection(bridge_name, port_info["interface"])
|
||||
@ -313,7 +317,9 @@ class Cloud(BaseNode):
|
||||
if port_info["type"] == "ethernet":
|
||||
network_interfaces = [interface["name"] for interface in self._interfaces()]
|
||||
if not port_info["interface"] in network_interfaces:
|
||||
raise NodeError(f"Interface '{port_info['interface']}' could not be found on this system, please update '{self.name}'")
|
||||
raise NodeError(
|
||||
f"Interface '{port_info['interface']}' could not be found on this system, please update '{self.name}'"
|
||||
)
|
||||
|
||||
if sys.platform.startswith("linux"):
|
||||
await self._add_linux_ethernet(port_info, bridge_name)
|
||||
@ -323,23 +329,22 @@ class Cloud(BaseNode):
|
||||
await self._add_windows_ethernet(port_info, bridge_name)
|
||||
|
||||
elif port_info["type"] == "tap":
|
||||
await self._ubridge_send('bridge add_nio_tap {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=port_info["interface"])
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_tap {name} "{interface}"'.format(
|
||||
name=bridge_name, interface=port_info["interface"]
|
||||
)
|
||||
)
|
||||
|
||||
elif port_info["type"] == "udp":
|
||||
await self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(
|
||||
name=bridge_name,
|
||||
lport=port_info["lport"],
|
||||
rhost=port_info["rhost"],
|
||||
rport=port_info["rport"])
|
||||
await self._ubridge_send(
|
||||
"bridge add_nio_udp {name} {lport} {rhost} {rport}".format(
|
||||
name=bridge_name, lport=port_info["lport"], rhost=port_info["rhost"], rport=port_info["rport"]
|
||||
)
|
||||
)
|
||||
|
||||
if nio.capturing:
|
||||
await self._ubridge_send('bridge start_capture {name} "{pcap_file}"'.format(
|
||||
name=bridge_name,
|
||||
pcap_file=nio.pcap_output_file)
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {name} "{pcap_file}"'.format(name=bridge_name, pcap_file=nio.pcap_output_file)
|
||||
)
|
||||
|
||||
await self._ubridge_send(f"bridge start {bridge_name}")
|
||||
@ -362,18 +367,13 @@ class Cloud(BaseNode):
|
||||
break
|
||||
i += 1
|
||||
|
||||
await self._ubridge_send('bridge add_nio_tap "{name}" "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=tap)
|
||||
)
|
||||
await self._ubridge_send('brctl addif "{interface}" "{tap}"'.format(
|
||||
tap=tap,
|
||||
interface=interface)
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_tap "{name}" "{interface}"'.format(name=bridge_name, interface=tap)
|
||||
)
|
||||
await self._ubridge_send('brctl addif "{interface}" "{tap}"'.format(tap=tap, interface=interface))
|
||||
else:
|
||||
await self._ubridge_send('bridge add_nio_linux_raw {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=interface)
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_linux_raw {name} "{interface}"'.format(name=bridge_name, interface=interface)
|
||||
)
|
||||
|
||||
async def _add_osx_ethernet(self, port_info, bridge_name):
|
||||
@ -382,20 +382,20 @@ class Cloud(BaseNode):
|
||||
"""
|
||||
|
||||
# Wireless adapters are not well supported by the libpcap on OSX
|
||||
if (await self._is_wifi_adapter_osx(port_info["interface"])):
|
||||
if await self._is_wifi_adapter_osx(port_info["interface"]):
|
||||
raise NodeError("Connecting to a Wireless adapter is not supported on Mac OS")
|
||||
if port_info["interface"].startswith("vmnet"):
|
||||
# Use a special NIO to connect to VMware vmnet interfaces on OSX (libpcap doesn't support them)
|
||||
await self._ubridge_send('bridge add_nio_fusion_vmnet {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=port_info["interface"])
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_fusion_vmnet {name} "{interface}"'.format(
|
||||
name=bridge_name, interface=port_info["interface"]
|
||||
)
|
||||
)
|
||||
return
|
||||
if not gns3server.utils.interfaces.has_netmask(port_info["interface"]):
|
||||
raise NodeError(f"Interface {port_info['interface']} has no netmask, interface down?")
|
||||
await self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=port_info["interface"])
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_ethernet {name} "{interface}"'.format(name=bridge_name, interface=port_info["interface"])
|
||||
)
|
||||
|
||||
async def _add_windows_ethernet(self, port_info, bridge_name):
|
||||
@ -405,9 +405,8 @@ class Cloud(BaseNode):
|
||||
|
||||
if not gns3server.utils.interfaces.has_netmask(port_info["interface"]):
|
||||
raise NodeError(f"Interface {port_info['interface']} has no netmask, interface down?")
|
||||
await self._ubridge_send('bridge add_nio_ethernet {name} "{interface}"'.format(
|
||||
name=bridge_name,
|
||||
interface=port_info["interface"])
|
||||
await self._ubridge_send(
|
||||
'bridge add_nio_ethernet {name} "{interface}"'.format(name=bridge_name, interface=port_info["interface"])
|
||||
)
|
||||
|
||||
async def add_nio(self, nio, port_number):
|
||||
@ -421,10 +420,11 @@ class Cloud(BaseNode):
|
||||
if port_number in self._nios:
|
||||
raise NodeError(f"Port {port_number} isn't free")
|
||||
|
||||
log.info('Cloud "{name}" [{id}]: NIO {nio} bound to port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Cloud "{name}" [{id}]: NIO {nio} bound to port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
try:
|
||||
await self.start()
|
||||
await self._add_ubridge_connection(nio, port_number)
|
||||
@ -474,11 +474,10 @@ class Cloud(BaseNode):
|
||||
if isinstance(nio, NIOUDP):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
|
||||
log.info('Cloud "{name}" [{id}]: NIO {nio} removed from port {port}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number)
|
||||
log.info(
|
||||
'Cloud "{name}" [{id}]: NIO {nio} removed from port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
del self._nios[port_number]
|
||||
@ -497,9 +496,8 @@ class Cloud(BaseNode):
|
||||
"""
|
||||
|
||||
if not [port["port_number"] for port in self._ports_mapping if port_number == port["port_number"]]:
|
||||
raise NodeError("Port {port_number} doesn't exist on cloud '{name}'".format(
|
||||
name=self.name,
|
||||
port_number=port_number)
|
||||
raise NodeError(
|
||||
"Port {port_number} doesn't exist on cloud '{name}'".format(name=self.name, port_number=port_number)
|
||||
)
|
||||
|
||||
if port_number not in self._nios:
|
||||
@ -523,14 +521,13 @@ class Cloud(BaseNode):
|
||||
raise NodeError(f"Packet capture is already activated on port {port_number}")
|
||||
nio.start_packet_capture(output_file)
|
||||
bridge_name = f"{self._id}-{port_number}"
|
||||
await self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(
|
||||
name=bridge_name,
|
||||
output_file=output_file)
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {name} "{output_file}"'.format(name=bridge_name, output_file=output_file)
|
||||
)
|
||||
log.info("Cloud '{name}' [{id}]: starting packet capture on port {port_number}".format(
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
port_number=port_number)
|
||||
log.info(
|
||||
"Cloud '{name}' [{id}]: starting packet capture on port {port_number}".format(
|
||||
name=self.name, id=self.id, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, port_number):
|
||||
@ -547,8 +544,8 @@ class Cloud(BaseNode):
|
||||
bridge_name = f"{self._id}-{port_number}"
|
||||
await self._ubridge_send(f"bridge stop_capture {bridge_name}")
|
||||
|
||||
log.info("Cloud'{name}' [{id}]: stopping packet capture on port {port_number}".format(
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
port_number=port_number)
|
||||
log.info(
|
||||
"Cloud'{name}' [{id}]: stopping packet capture on port {port_number}".format(
|
||||
name=self.name, id=self.id, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
@ -19,6 +19,7 @@ import asyncio
|
||||
from ...base_node import BaseNode
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -39,10 +40,7 @@ class EthernetHub(BaseNode):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id}
|
||||
return {"name": self.name, "usage": self.usage, "node_id": self.id, "project_id": self.project.id}
|
||||
|
||||
async def create(self):
|
||||
"""
|
||||
|
@ -19,6 +19,7 @@ import asyncio
|
||||
from ...base_node import BaseNode
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -39,10 +40,7 @@ class EthernetSwitch(BaseNode):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id}
|
||||
return {"name": self.name, "usage": self.usage, "node_id": self.id, "project_id": self.project.id}
|
||||
|
||||
async def create(self):
|
||||
"""
|
||||
|
@ -24,6 +24,7 @@ import gns3server.utils.interfaces
|
||||
from gns3server.config import Config
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -46,22 +47,21 @@ class Nat(Cloud):
|
||||
nat_interface = Config.instance().settings.Server.default_nat_interface
|
||||
if not nat_interface:
|
||||
nat_interface = "vmnet8"
|
||||
interfaces = list(filter(lambda x: nat_interface in x.lower(),
|
||||
[interface["name"] for interface in gns3server.utils.interfaces.interfaces()]))
|
||||
interfaces = list(
|
||||
filter(
|
||||
lambda x: nat_interface in x.lower(),
|
||||
[interface["name"] for interface in gns3server.utils.interfaces.interfaces()],
|
||||
)
|
||||
)
|
||||
if not len(interfaces):
|
||||
raise NodeError(f"NAT interface {nat_interface} is missing. "
|
||||
f"You need to install VMware or use the NAT node on GNS3 VM")
|
||||
raise NodeError(
|
||||
f"NAT interface {nat_interface} is missing. "
|
||||
f"You need to install VMware or use the NAT node on GNS3 VM"
|
||||
)
|
||||
interface = interfaces[0] # take the first available interface containing the vmnet8 name
|
||||
|
||||
log.info(f"NAT node '{name}' configured to use NAT interface '{interface}'")
|
||||
ports = [
|
||||
{
|
||||
"name": "nat0",
|
||||
"type": "ethernet",
|
||||
"interface": interface,
|
||||
"port_number": 0
|
||||
}
|
||||
]
|
||||
ports = [{"name": "nat0", "type": "ethernet", "interface": interface, "port_number": 0}]
|
||||
super().__init__(name, node_id, project, manager, ports=ports)
|
||||
|
||||
@property
|
||||
@ -84,5 +84,5 @@ class Nat(Cloud):
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"status": "started",
|
||||
"ports_mapping": self.ports_mapping
|
||||
"ports_mapping": self.ports_mapping,
|
||||
}
|
||||
|
@ -17,7 +17,6 @@
|
||||
|
||||
|
||||
class ComputeError(Exception):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__()
|
||||
self._message = message
|
||||
@ -30,24 +29,20 @@ class ComputeError(Exception):
|
||||
|
||||
|
||||
class ComputeNotFoundError(ComputeError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ComputeUnauthorizedError(ComputeError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ComputeForbiddenError(ComputeError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
|
||||
class ComputeTimeoutError(ComputeError):
|
||||
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
@ -46,7 +46,7 @@ class Docker(BaseManager):
|
||||
def __init__(self):
|
||||
|
||||
super().__init__()
|
||||
self._server_url = '/var/run/docker.sock'
|
||||
self._server_url = "/var/run/docker.sock"
|
||||
self._connected = False
|
||||
# Allow locking during ubridge operations
|
||||
self.ubridge_lock = asyncio.Lock()
|
||||
@ -64,11 +64,13 @@ class Docker(BaseManager):
|
||||
self._connected = False
|
||||
raise DockerError("Can't connect to docker daemon")
|
||||
|
||||
docker_version = parse_version(version['ApiVersion'])
|
||||
docker_version = parse_version(version["ApiVersion"])
|
||||
|
||||
if docker_version < parse_version(DOCKER_MINIMUM_API_VERSION):
|
||||
raise DockerError(f"Docker version is {version['Version']}. "
|
||||
f"GNS3 requires a minimum version of {DOCKER_MINIMUM_VERSION}")
|
||||
raise DockerError(
|
||||
f"Docker version is {version['Version']}. "
|
||||
f"GNS3 requires a minimum version of {DOCKER_MINIMUM_VERSION}"
|
||||
)
|
||||
|
||||
preferred_api_version = parse_version(DOCKER_PREFERRED_API_VERSION)
|
||||
if docker_version >= preferred_api_version:
|
||||
@ -108,7 +110,7 @@ class Docker(BaseManager):
|
||||
body = await response.read()
|
||||
response.close()
|
||||
if body and len(body):
|
||||
if response.headers['CONTENT-TYPE'] == 'application/json':
|
||||
if response.headers["CONTENT-TYPE"] == "application/json":
|
||||
body = json.loads(body.decode("utf-8"))
|
||||
else:
|
||||
body = body.decode("utf-8")
|
||||
@ -131,8 +133,8 @@ class Docker(BaseManager):
|
||||
if timeout is None:
|
||||
timeout = 60 * 60 * 24 * 31 # One month timeout
|
||||
|
||||
if path == 'version':
|
||||
url = "http://docker/v1.12/" + path # API of docker v1.0
|
||||
if path == "version":
|
||||
url = "http://docker/v1.12/" + path # API of docker v1.0
|
||||
else:
|
||||
url = "http://docker/v" + DOCKER_MINIMUM_API_VERSION + "/" + path
|
||||
try:
|
||||
@ -141,12 +143,16 @@ class Docker(BaseManager):
|
||||
if self._session is None or self._session.closed:
|
||||
connector = self.connector()
|
||||
self._session = aiohttp.ClientSession(connector=connector)
|
||||
response = await self._session.request(method,
|
||||
url,
|
||||
params=params,
|
||||
data=data,
|
||||
headers={"content-type": "application/json", },
|
||||
timeout=timeout)
|
||||
response = await self._session.request(
|
||||
method,
|
||||
url,
|
||||
params=params,
|
||||
data=data,
|
||||
headers={
|
||||
"content-type": "application/json",
|
||||
},
|
||||
timeout=timeout,
|
||||
)
|
||||
except aiohttp.ClientError as e:
|
||||
raise DockerError(f"Docker has returned an error: {e}")
|
||||
except (asyncio.TimeoutError):
|
||||
@ -199,8 +205,10 @@ class Docker(BaseManager):
|
||||
try:
|
||||
response = await self.http_query("POST", "images/create", params={"fromImage": image}, timeout=None)
|
||||
except DockerError as e:
|
||||
raise DockerError(f"Could not pull the '{image}' image from Docker Hub, "
|
||||
f"please check your Internet connection (original error: {e})")
|
||||
raise DockerError(
|
||||
f"Could not pull the '{image}' image from Docker Hub, "
|
||||
f"please check your Internet connection (original error: {e})"
|
||||
)
|
||||
# The pull api will stream status via an HTTP JSON stream
|
||||
content = ""
|
||||
while True:
|
||||
@ -238,9 +246,9 @@ class Docker(BaseManager):
|
||||
"""
|
||||
|
||||
images = []
|
||||
for image in (await self.query("GET", "images/json", params={"all": 0})):
|
||||
if image['RepoTags']:
|
||||
for tag in image['RepoTags']:
|
||||
for image in await self.query("GET", "images/json", params={"all": 0}):
|
||||
if image["RepoTags"]:
|
||||
for tag in image["RepoTags"]:
|
||||
if tag != "<none>:<none>":
|
||||
images.append({'image': tag})
|
||||
return sorted(images, key=lambda i: i['image'])
|
||||
images.append({"image": tag})
|
||||
return sorted(images, key=lambda i: i["image"])
|
||||
|
@ -39,13 +39,10 @@ from ..base_node import BaseNode
|
||||
|
||||
from ..adapters.ethernet_adapter import EthernetAdapter
|
||||
from ..nios.nio_udp import NIOUDP
|
||||
from .docker_error import (
|
||||
DockerError,
|
||||
DockerHttp304Error,
|
||||
DockerHttp404Error
|
||||
)
|
||||
from .docker_error import DockerError, DockerHttp304Error, DockerHttp404Error
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -69,11 +66,32 @@ class DockerVM(BaseNode):
|
||||
:param extra_volumes: Additional directories to make persistent
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, image, console=None, aux=None, start_command=None,
|
||||
adapters=None, environment=None, console_type="telnet", aux_type="none", console_resolution="1024x768",
|
||||
console_http_port=80, console_http_path="/", extra_hosts=None, extra_volumes=[], memory=0, cpus=0):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
image,
|
||||
console=None,
|
||||
aux=None,
|
||||
start_command=None,
|
||||
adapters=None,
|
||||
environment=None,
|
||||
console_type="telnet",
|
||||
aux_type="none",
|
||||
console_resolution="1024x768",
|
||||
console_http_port=80,
|
||||
console_http_path="/",
|
||||
extra_hosts=None,
|
||||
extra_volumes=[],
|
||||
memory=0,
|
||||
cpus=0,
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type)
|
||||
super().__init__(
|
||||
name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type
|
||||
)
|
||||
|
||||
# force the latest image if no version is specified
|
||||
if ":" not in image:
|
||||
@ -109,10 +127,10 @@ class DockerVM(BaseNode):
|
||||
else:
|
||||
self.adapters = adapters
|
||||
|
||||
log.debug("{module}: {name} [{image}] initialized.".format(
|
||||
module=self.manager.module_name,
|
||||
name=self.name,
|
||||
image=self._image)
|
||||
log.debug(
|
||||
"{module}: {name} [{image}] initialized.".format(
|
||||
module=self.manager.module_name, name=self.name, image=self._image
|
||||
)
|
||||
)
|
||||
|
||||
def __json__(self):
|
||||
@ -138,7 +156,7 @@ class DockerVM(BaseNode):
|
||||
"extra_hosts": self.extra_hosts,
|
||||
"extra_volumes": self.extra_volumes,
|
||||
"memory": self.memory,
|
||||
"cpus": self.cpus
|
||||
"cpus": self.cpus,
|
||||
}
|
||||
|
||||
def _get_free_display_port(self):
|
||||
@ -281,7 +299,9 @@ class DockerVM(BaseNode):
|
||||
volumes.extend((image_info.get("Config", {}).get("Volumes") or {}).keys())
|
||||
for volume in self._extra_volumes:
|
||||
if not volume.strip() or volume[0] != "/" or volume.find("..") >= 0:
|
||||
raise DockerError(f"Persistent volume '{volume}' has invalid format. It must start with a '/' and not contain '..'.")
|
||||
raise DockerError(
|
||||
f"Persistent volume '{volume}' has invalid format. It must start with a '/' and not contain '..'."
|
||||
)
|
||||
volumes.extend(self._extra_volumes)
|
||||
|
||||
self._volumes = []
|
||||
@ -292,7 +312,7 @@ class DockerVM(BaseNode):
|
||||
# remove any mount that is equal or more specific, then append this one
|
||||
self._volumes = list(filter(lambda v: not generalises(volume, v), self._volumes))
|
||||
# if there is nothing more general, append this mount
|
||||
if not [ v for v in self._volumes if generalises(v, volume) ] :
|
||||
if not [v for v in self._volumes if generalises(v, volume)]:
|
||||
self._volumes.append(volume)
|
||||
|
||||
for volume in self._volumes:
|
||||
@ -308,7 +328,7 @@ class DockerVM(BaseNode):
|
||||
"""
|
||||
path = os.path.join(self.working_dir, "etc", "network")
|
||||
os.makedirs(path, exist_ok=True)
|
||||
open(os.path.join(path, ".gns3_perms"), 'a').close()
|
||||
open(os.path.join(path, ".gns3_perms"), "a").close()
|
||||
os.makedirs(os.path.join(path, "if-up.d"), exist_ok=True)
|
||||
os.makedirs(os.path.join(path, "if-down.d"), exist_ok=True)
|
||||
os.makedirs(os.path.join(path, "if-pre-up.d"), exist_ok=True)
|
||||
@ -316,13 +336,16 @@ class DockerVM(BaseNode):
|
||||
|
||||
if not os.path.exists(os.path.join(path, "interfaces")):
|
||||
with open(os.path.join(path, "interfaces"), "w+") as f:
|
||||
f.write("""#
|
||||
f.write(
|
||||
"""#
|
||||
# This is a sample network config uncomment lines to configure the network
|
||||
#
|
||||
|
||||
""")
|
||||
"""
|
||||
)
|
||||
for adapter in range(0, self.adapters):
|
||||
f.write("""
|
||||
f.write(
|
||||
"""
|
||||
# Static config for eth{adapter}
|
||||
#auto eth{adapter}
|
||||
#iface eth{adapter} inet static
|
||||
@ -333,7 +356,10 @@ class DockerVM(BaseNode):
|
||||
|
||||
# DHCP config for eth{adapter}
|
||||
# auto eth{adapter}
|
||||
# iface eth{adapter} inet dhcp""".format(adapter=adapter))
|
||||
# iface eth{adapter} inet dhcp""".format(
|
||||
adapter=adapter
|
||||
)
|
||||
)
|
||||
return path
|
||||
|
||||
async def create(self):
|
||||
@ -353,8 +379,10 @@ class DockerVM(BaseNode):
|
||||
|
||||
available_cpus = psutil.cpu_count(logical=True)
|
||||
if self._cpus > available_cpus:
|
||||
raise DockerError(f"You have allocated too many CPUs for the Docker container "
|
||||
f"(max available is {available_cpus} CPUs)")
|
||||
raise DockerError(
|
||||
f"You have allocated too many CPUs for the Docker container "
|
||||
f"(max available is {available_cpus} CPUs)"
|
||||
)
|
||||
|
||||
params = {
|
||||
"Hostname": self._name,
|
||||
@ -369,12 +397,12 @@ class DockerVM(BaseNode):
|
||||
"Privileged": True,
|
||||
"Binds": self._mount_binds(image_infos),
|
||||
"Memory": self._memory * (1024 * 1024), # convert memory to bytes
|
||||
"NanoCpus": int(self._cpus * 1e9) # convert cpus to nano cpus
|
||||
"NanoCpus": int(self._cpus * 1e9), # convert cpus to nano cpus
|
||||
},
|
||||
"Volumes": {},
|
||||
"Env": ["container=docker"], # Systemd compliant: https://github.com/GNS3/gns3-server/issues/573
|
||||
"Cmd": [],
|
||||
"Entrypoint": image_infos.get("Config", {"Entrypoint": []}).get("Entrypoint")
|
||||
"Entrypoint": image_infos.get("Config", {"Entrypoint": []}).get("Entrypoint"),
|
||||
}
|
||||
|
||||
if params["Entrypoint"] is None:
|
||||
@ -407,7 +435,7 @@ class DockerVM(BaseNode):
|
||||
variables = []
|
||||
|
||||
for var in variables:
|
||||
formatted = self._format_env(variables, var.get('value', ''))
|
||||
formatted = self._format_env(variables, var.get("value", ""))
|
||||
params["Env"].append("{}={}".format(var["name"], formatted))
|
||||
|
||||
if self._environment:
|
||||
@ -422,7 +450,9 @@ class DockerVM(BaseNode):
|
||||
|
||||
if self._console_type == "vnc":
|
||||
await self._start_vnc()
|
||||
params["Env"].append("QT_GRAPHICSSYSTEM=native") # To fix a Qt issue: https://github.com/GNS3/gns3-server/issues/556
|
||||
params["Env"].append(
|
||||
"QT_GRAPHICSSYSTEM=native"
|
||||
) # To fix a Qt issue: https://github.com/GNS3/gns3-server/issues/556
|
||||
params["Env"].append(f"DISPLAY=:{self._display}")
|
||||
params["HostConfig"]["Binds"].append("/tmp/.X11-unix/:/tmp/.X11-unix/")
|
||||
|
||||
@ -432,13 +462,13 @@ class DockerVM(BaseNode):
|
||||
params["Env"].append(f"GNS3_EXTRA_HOSTS={extra_hosts}")
|
||||
|
||||
result = await self.manager.query("POST", "containers/create", data=params)
|
||||
self._cid = result['Id']
|
||||
self._cid = result["Id"]
|
||||
log.info(f"Docker container '{self._name}' [{self._id}] created")
|
||||
return True
|
||||
|
||||
def _format_env(self, variables, env):
|
||||
for variable in variables:
|
||||
env = env.replace('${' + variable["name"] + '}', variable.get("value", ""))
|
||||
env = env.replace("${" + variable["name"] + "}", variable.get("value", ""))
|
||||
return env
|
||||
|
||||
def _format_extra_hosts(self, extra_hosts):
|
||||
@ -481,9 +511,10 @@ class DockerVM(BaseNode):
|
||||
try:
|
||||
state = await self._get_container_state()
|
||||
except DockerHttp404Error:
|
||||
raise DockerError("Docker container '{name}' with ID {cid} does not exist or is not ready yet. Please try again in a few seconds.".format(
|
||||
name=self.name,
|
||||
cid=self._cid)
|
||||
raise DockerError(
|
||||
"Docker container '{name}' with ID {cid} does not exist or is not ready yet. Please try again in a few seconds.".format(
|
||||
name=self.name, cid=self._cid
|
||||
)
|
||||
)
|
||||
if state == "paused":
|
||||
await self.unpause()
|
||||
@ -514,7 +545,7 @@ class DockerVM(BaseNode):
|
||||
|
||||
# The container can crash soon after the start, this means we can not move the interface to the container namespace
|
||||
logdata = await self._get_log()
|
||||
for line in logdata.split('\n'):
|
||||
for line in logdata.split("\n"):
|
||||
log.error(line)
|
||||
raise DockerError(logdata)
|
||||
|
||||
@ -528,11 +559,10 @@ class DockerVM(BaseNode):
|
||||
|
||||
self._permissions_fixed = False
|
||||
self.status = "started"
|
||||
log.info("Docker container '{name}' [{image}] started listen for {console_type} on {console}".format(
|
||||
name=self._name,
|
||||
image=self._image,
|
||||
console=self.console,
|
||||
console_type=self.console_type)
|
||||
log.info(
|
||||
"Docker container '{name}' [{image}] started listen for {console_type} on {console}".format(
|
||||
name=self._name, image=self._image, console=self.console, console_type=self.console_type
|
||||
)
|
||||
)
|
||||
|
||||
async def _start_aux(self):
|
||||
@ -544,17 +574,30 @@ class DockerVM(BaseNode):
|
||||
# https://github.com/GNS3/gns3-gui/issues/1039
|
||||
try:
|
||||
process = await asyncio.subprocess.create_subprocess_exec(
|
||||
"docker", "exec", "-i", self._cid, "/gns3/bin/busybox", "script", "-qfc", "while true; do TERM=vt100 /gns3/bin/busybox sh; done", "/dev/null",
|
||||
"docker",
|
||||
"exec",
|
||||
"-i",
|
||||
self._cid,
|
||||
"/gns3/bin/busybox",
|
||||
"script",
|
||||
"-qfc",
|
||||
"while true; do TERM=vt100 /gns3/bin/busybox sh; done",
|
||||
"/dev/null",
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.STDOUT,
|
||||
stdin=asyncio.subprocess.PIPE)
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
)
|
||||
except OSError as e:
|
||||
raise DockerError(f"Could not start auxiliary console process: {e}")
|
||||
server = AsyncioTelnetServer(reader=process.stdout, writer=process.stdin, binary=True, echo=True)
|
||||
try:
|
||||
self._telnet_servers.append(await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.aux))
|
||||
self._telnet_servers.append(
|
||||
await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.aux)
|
||||
)
|
||||
except OSError as e:
|
||||
raise DockerError(f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.aux}: {e}")
|
||||
raise DockerError(
|
||||
f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.aux}: {e}"
|
||||
)
|
||||
log.debug(f"Docker container '{self.name}' started listen for auxiliary telnet on {self.aux}")
|
||||
|
||||
async def _fix_permissions(self):
|
||||
@ -570,8 +613,11 @@ class DockerVM(BaseNode):
|
||||
await self.manager.query("POST", f"containers/{self._cid}/start")
|
||||
|
||||
for volume in self._volumes:
|
||||
log.debug("Docker container '{name}' [{image}] fix ownership on {path}".format(
|
||||
name=self._name, image=self._image, path=volume))
|
||||
log.debug(
|
||||
"Docker container '{name}' [{image}] fix ownership on {path}".format(
|
||||
name=self._name, image=self._image, path=volume
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
process = await asyncio.subprocess.create_subprocess_exec(
|
||||
@ -582,12 +628,13 @@ class DockerVM(BaseNode):
|
||||
"sh",
|
||||
"-c",
|
||||
"("
|
||||
"/gns3/bin/busybox find \"{path}\" -depth -print0"
|
||||
'/gns3/bin/busybox find "{path}" -depth -print0'
|
||||
" | /gns3/bin/busybox xargs -0 /gns3/bin/busybox stat -c '%a:%u:%g:%n' > \"{path}/.gns3_perms\""
|
||||
")"
|
||||
" && /gns3/bin/busybox chmod -R u+rX \"{path}\""
|
||||
" && /gns3/bin/busybox chown {uid}:{gid} -R \"{path}\""
|
||||
.format(uid=os.getuid(), gid=os.getgid(), path=volume),
|
||||
' && /gns3/bin/busybox chmod -R u+rX "{path}"'
|
||||
' && /gns3/bin/busybox chown {uid}:{gid} -R "{path}"'.format(
|
||||
uid=os.getuid(), gid=os.getgid(), path=volume
|
||||
),
|
||||
)
|
||||
except OSError as e:
|
||||
raise DockerError(f"Could not fix permissions for {volume}: {e}")
|
||||
@ -607,36 +654,50 @@ class DockerVM(BaseNode):
|
||||
|
||||
if tigervnc_path:
|
||||
with open(os.path.join(self.working_dir, "vnc.log"), "w") as fd:
|
||||
self._vnc_process = await asyncio.create_subprocess_exec(tigervnc_path,
|
||||
"-geometry", self._console_resolution,
|
||||
"-depth", "16",
|
||||
"-interface", self._manager.port_manager.console_host,
|
||||
"-rfbport", str(self.console),
|
||||
"-AlwaysShared",
|
||||
"-SecurityTypes", "None",
|
||||
f":{self._display}",
|
||||
stdout=fd, stderr=subprocess.STDOUT)
|
||||
self._vnc_process = await asyncio.create_subprocess_exec(
|
||||
tigervnc_path,
|
||||
"-geometry",
|
||||
self._console_resolution,
|
||||
"-depth",
|
||||
"16",
|
||||
"-interface",
|
||||
self._manager.port_manager.console_host,
|
||||
"-rfbport",
|
||||
str(self.console),
|
||||
"-AlwaysShared",
|
||||
"-SecurityTypes",
|
||||
"None",
|
||||
f":{self._display}",
|
||||
stdout=fd,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
else:
|
||||
if restart is False:
|
||||
self._xvfb_process = await asyncio.create_subprocess_exec("Xvfb",
|
||||
"-nolisten",
|
||||
"tcp", f":{self._display}",
|
||||
"-screen", "0",
|
||||
self._console_resolution + "x16")
|
||||
self._xvfb_process = await asyncio.create_subprocess_exec(
|
||||
"Xvfb", "-nolisten", "tcp", f":{self._display}", "-screen", "0", self._console_resolution + "x16"
|
||||
)
|
||||
|
||||
# We pass a port for TCPV6 due to a crash in X11VNC if not here: https://github.com/GNS3/gns3-server/issues/569
|
||||
with open(os.path.join(self.working_dir, "vnc.log"), "w") as fd:
|
||||
self._vnc_process = await asyncio.create_subprocess_exec("x11vnc",
|
||||
"-forever",
|
||||
"-nopw",
|
||||
"-shared",
|
||||
"-geometry", self._console_resolution,
|
||||
"-display", f"WAIT:{self._display}",
|
||||
"-rfbport", str(self.console),
|
||||
"-rfbportv6", str(self.console),
|
||||
"-noncache",
|
||||
"-listen", self._manager.port_manager.console_host,
|
||||
stdout=fd, stderr=subprocess.STDOUT)
|
||||
self._vnc_process = await asyncio.create_subprocess_exec(
|
||||
"x11vnc",
|
||||
"-forever",
|
||||
"-nopw",
|
||||
"-shared",
|
||||
"-geometry",
|
||||
self._console_resolution,
|
||||
"-display",
|
||||
f"WAIT:{self._display}",
|
||||
"-rfbport",
|
||||
str(self.console),
|
||||
"-rfbportv6",
|
||||
str(self.console),
|
||||
"-noncache",
|
||||
"-listen",
|
||||
self._manager.port_manager.console_host,
|
||||
stdout=fd,
|
||||
stderr=subprocess.STDOUT,
|
||||
)
|
||||
|
||||
async def _start_vnc(self):
|
||||
"""
|
||||
@ -658,7 +719,9 @@ class DockerVM(BaseNode):
|
||||
# Start vncconfig for tigervnc clipboard support, connection available only after socket creation.
|
||||
tigervncconfig_path = shutil.which("vncconfig")
|
||||
if tigervnc_path and tigervncconfig_path:
|
||||
self._vncconfig_process = await asyncio.create_subprocess_exec(tigervncconfig_path, "-display", f":{self._display}", "-nowin")
|
||||
self._vncconfig_process = await asyncio.create_subprocess_exec(
|
||||
tigervncconfig_path, "-display", f":{self._display}", "-nowin"
|
||||
)
|
||||
|
||||
# sometimes the VNC process can crash
|
||||
monitor_process(self._vnc_process, self._vnc_callback)
|
||||
@ -671,7 +734,12 @@ class DockerVM(BaseNode):
|
||||
"""
|
||||
|
||||
if returncode != 0 and self._closing is False:
|
||||
self.project.emit("log.error", {"message": f"The vnc process has stopped with return code {returncode} for node '{self.name}'. Please restart this node."})
|
||||
self.project.emit(
|
||||
"log.error",
|
||||
{
|
||||
"message": f"The vnc process has stopped with return code {returncode} for node '{self.name}'. Please restart this node."
|
||||
},
|
||||
)
|
||||
self._vnc_process = None
|
||||
|
||||
async def _start_http(self):
|
||||
@ -681,19 +749,33 @@ class DockerVM(BaseNode):
|
||||
"""
|
||||
|
||||
log.debug("Forward HTTP for %s to %d", self.name, self._console_http_port)
|
||||
command = ["docker", "exec", "-i", self._cid, "/gns3/bin/busybox", "nc", "127.0.0.1", str(self._console_http_port)]
|
||||
command = [
|
||||
"docker",
|
||||
"exec",
|
||||
"-i",
|
||||
self._cid,
|
||||
"/gns3/bin/busybox",
|
||||
"nc",
|
||||
"127.0.0.1",
|
||||
str(self._console_http_port),
|
||||
]
|
||||
# We replace host and port in the server answer otherwise some link could be broken
|
||||
server = AsyncioRawCommandServer(command, replaces=[
|
||||
(
|
||||
b'://127.0.0.1', # {{HOST}} mean client host
|
||||
b'://{{HOST}}',
|
||||
),
|
||||
(
|
||||
f':{self._console_http_port}'.encode(),
|
||||
f':{self.console}'.encode(),
|
||||
)
|
||||
])
|
||||
self._telnet_servers.append(await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console))
|
||||
server = AsyncioRawCommandServer(
|
||||
command,
|
||||
replaces=[
|
||||
(
|
||||
b"://127.0.0.1", # {{HOST}} mean client host
|
||||
b"://{{HOST}}",
|
||||
),
|
||||
(
|
||||
f":{self._console_http_port}".encode(),
|
||||
f":{self.console}".encode(),
|
||||
),
|
||||
],
|
||||
)
|
||||
self._telnet_servers.append(
|
||||
await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)
|
||||
)
|
||||
|
||||
async def _window_size_changed_callback(self, columns, rows):
|
||||
"""
|
||||
@ -707,14 +789,12 @@ class DockerVM(BaseNode):
|
||||
# resize the container TTY.
|
||||
await self._manager.query("POST", f"containers/{self._cid}/resize?h={rows}&w={columns}")
|
||||
|
||||
|
||||
async def _start_console(self):
|
||||
"""
|
||||
Starts streaming the console via telnet
|
||||
"""
|
||||
|
||||
class InputStream:
|
||||
|
||||
def __init__(self):
|
||||
self._data = b""
|
||||
|
||||
@ -728,13 +808,25 @@ class DockerVM(BaseNode):
|
||||
|
||||
output_stream = asyncio.StreamReader()
|
||||
input_stream = InputStream()
|
||||
telnet = AsyncioTelnetServer(reader=output_stream, writer=input_stream, echo=True, naws=True, window_size_changed_callback=self._window_size_changed_callback)
|
||||
telnet = AsyncioTelnetServer(
|
||||
reader=output_stream,
|
||||
writer=input_stream,
|
||||
echo=True,
|
||||
naws=True,
|
||||
window_size_changed_callback=self._window_size_changed_callback,
|
||||
)
|
||||
try:
|
||||
self._telnet_servers.append(await asyncio.start_server(telnet.run, self._manager.port_manager.console_host, self.console))
|
||||
self._telnet_servers.append(
|
||||
await asyncio.start_server(telnet.run, self._manager.port_manager.console_host, self.console)
|
||||
)
|
||||
except OSError as e:
|
||||
raise DockerError(f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.console}: {e}")
|
||||
raise DockerError(
|
||||
f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.console}: {e}"
|
||||
)
|
||||
|
||||
self._console_websocket = await self.manager.websocket_query(f"containers/{self._cid}/attach/ws?stream=1&stdin=1&stdout=1&stderr=1")
|
||||
self._console_websocket = await self.manager.websocket_query(
|
||||
f"containers/{self._cid}/attach/ws?stream=1&stdin=1&stdout=1&stderr=1"
|
||||
)
|
||||
input_stream.ws = self._console_websocket
|
||||
|
||||
output_stream.feed_data(self.name.encode() + b" console is now available... Press RETURN to get started.\r\n")
|
||||
@ -792,8 +884,7 @@ class DockerVM(BaseNode):
|
||||
"""
|
||||
|
||||
await self.manager.query("POST", f"containers/{self._cid}/restart")
|
||||
log.info("Docker container '{name}' [{image}] restarted".format(
|
||||
name=self._name, image=self._image))
|
||||
log.info("Docker container '{name}' [{image}] restarted".format(name=self._name, image=self._image))
|
||||
|
||||
async def _clean_servers(self):
|
||||
"""
|
||||
@ -911,8 +1002,7 @@ class DockerVM(BaseNode):
|
||||
await self.manager.query("DELETE", f"containers/{self._cid}", params={"force": 1, "v": 1})
|
||||
except DockerError:
|
||||
pass
|
||||
log.info("Docker container '{name}' [{image}] removed".format(
|
||||
name=self._name, image=self._image))
|
||||
log.info("Docker container '{name}' [{image}] removed".format(name=self._name, image=self._image))
|
||||
|
||||
if release_nio_udp_ports:
|
||||
for adapter in self._ethernet_adapters:
|
||||
@ -936,26 +1026,37 @@ class DockerVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker container '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise DockerError(
|
||||
"Adapter {adapter_number} doesn't exist on Docker container '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
for index in range(4096):
|
||||
if f"tap-gns3-e{index}" not in psutil.net_if_addrs():
|
||||
adapter.host_ifc = f"tap-gns3-e{str(index)}"
|
||||
break
|
||||
if adapter.host_ifc is None:
|
||||
raise DockerError("Adapter {adapter_number} couldn't allocate interface on Docker container '{name}'. Too many Docker interfaces already exists".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
bridge_name = f'bridge{adapter_number}'
|
||||
await self._ubridge_send(f'bridge create {bridge_name}')
|
||||
raise DockerError(
|
||||
"Adapter {adapter_number} couldn't allocate interface on Docker container '{name}'. Too many Docker interfaces already exists".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
bridge_name = f"bridge{adapter_number}"
|
||||
await self._ubridge_send(f"bridge create {bridge_name}")
|
||||
self._bridges.add(bridge_name)
|
||||
await self._ubridge_send('bridge add_nio_tap bridge{adapter_number} {hostif}'.format(adapter_number=adapter_number,
|
||||
hostif=adapter.host_ifc))
|
||||
await self._ubridge_send(
|
||||
"bridge add_nio_tap bridge{adapter_number} {hostif}".format(
|
||||
adapter_number=adapter_number, hostif=adapter.host_ifc
|
||||
)
|
||||
)
|
||||
log.debug("Move container %s adapter %s to namespace %s", self.name, adapter.host_ifc, self._namespace)
|
||||
try:
|
||||
await self._ubridge_send('docker move_to_ns {ifc} {ns} eth{adapter}'.format(ifc=adapter.host_ifc,
|
||||
ns=self._namespace,
|
||||
adapter=adapter_number))
|
||||
await self._ubridge_send(
|
||||
"docker move_to_ns {ifc} {ns} eth{adapter}".format(
|
||||
ifc=adapter.host_ifc, ns=self._namespace, adapter=adapter_number
|
||||
)
|
||||
)
|
||||
except UbridgeError as e:
|
||||
raise UbridgeNamespaceError(e)
|
||||
|
||||
@ -965,20 +1066,24 @@ class DockerVM(BaseNode):
|
||||
async def _get_namespace(self):
|
||||
|
||||
result = await self.manager.query("GET", f"containers/{self._cid}/json")
|
||||
return int(result['State']['Pid'])
|
||||
return int(result["State"]["Pid"])
|
||||
|
||||
async def _connect_nio(self, adapter_number, nio):
|
||||
|
||||
bridge_name = f'bridge{adapter_number}'
|
||||
await self._ubridge_send('bridge add_nio_udp {bridge_name} {lport} {rhost} {rport}'.format(bridge_name=bridge_name,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport))
|
||||
bridge_name = f"bridge{adapter_number}"
|
||||
await self._ubridge_send(
|
||||
"bridge add_nio_udp {bridge_name} {lport} {rhost} {rport}".format(
|
||||
bridge_name=bridge_name, lport=nio.lport, rhost=nio.rhost, rport=nio.rport
|
||||
)
|
||||
)
|
||||
|
||||
if nio.capturing:
|
||||
await self._ubridge_send('bridge start_capture {bridge_name} "{pcap_file}"'.format(bridge_name=bridge_name,
|
||||
pcap_file=nio.pcap_output_file))
|
||||
await self._ubridge_send(f'bridge start {bridge_name}')
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {bridge_name} "{pcap_file}"'.format(
|
||||
bridge_name=bridge_name, pcap_file=nio.pcap_output_file
|
||||
)
|
||||
)
|
||||
await self._ubridge_send(f"bridge start {bridge_name}")
|
||||
await self._ubridge_apply_filters(bridge_name, nio.filters)
|
||||
|
||||
async def adapter_add_nio_binding(self, adapter_number, nio):
|
||||
@ -992,17 +1097,21 @@ class DockerVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker container '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise DockerError(
|
||||
"Adapter {adapter_number} doesn't exist on Docker container '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
if self.status == "started" and self.ubridge:
|
||||
await self._connect_nio(adapter_number, nio)
|
||||
|
||||
adapter.add_nio(0, nio)
|
||||
log.info("Docker container '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"Docker container '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(
|
||||
name=self.name, id=self._id, nio=nio, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
@ -1013,7 +1122,7 @@ class DockerVM(BaseNode):
|
||||
"""
|
||||
|
||||
if self.ubridge:
|
||||
bridge_name = f'bridge{adapter_number}'
|
||||
bridge_name = f"bridge{adapter_number}"
|
||||
if bridge_name in self._bridges:
|
||||
await self._ubridge_apply_filters(bridge_name, nio.filters)
|
||||
|
||||
@ -1029,25 +1138,30 @@ class DockerVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise DockerError(
|
||||
"Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
await self.stop_capture(adapter_number)
|
||||
if self.ubridge:
|
||||
nio = adapter.get_nio(0)
|
||||
bridge_name = f'bridge{adapter_number}'
|
||||
bridge_name = f"bridge{adapter_number}"
|
||||
await self._ubridge_send(f"bridge stop {bridge_name}")
|
||||
await self._ubridge_send('bridge remove_nio_udp bridge{adapter} {lport} {rhost} {rport}'.format(adapter=adapter_number,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport))
|
||||
await self._ubridge_send(
|
||||
"bridge remove_nio_udp bridge{adapter} {lport} {rhost} {rport}".format(
|
||||
adapter=adapter_number, lport=nio.lport, rhost=nio.rhost, rport=nio.rport
|
||||
)
|
||||
)
|
||||
|
||||
adapter.remove_nio(0)
|
||||
|
||||
log.info("Docker VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
nio=adapter.host_ifc,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"Docker VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, nio=adapter.host_ifc, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
def get_nio(self, adapter_number):
|
||||
"""
|
||||
@ -1061,8 +1175,11 @@ class DockerVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise DockerError(
|
||||
"Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
@ -1097,9 +1214,11 @@ class DockerVM(BaseNode):
|
||||
for adapter_number in range(0, adapters):
|
||||
self._ethernet_adapters.append(EthernetAdapter())
|
||||
|
||||
log.info('Docker container "{name}" [{id}]: number of Ethernet adapters changed to {adapters}'.format(name=self._name,
|
||||
id=self._id,
|
||||
adapters=adapters))
|
||||
log.info(
|
||||
'Docker container "{name}" [{id}]: number of Ethernet adapters changed to {adapters}'.format(
|
||||
name=self._name, id=self._id, adapters=adapters
|
||||
)
|
||||
)
|
||||
|
||||
async def pull_image(self, image):
|
||||
"""
|
||||
@ -1108,6 +1227,7 @@ class DockerVM(BaseNode):
|
||||
|
||||
def callback(msg):
|
||||
self.project.emit("log.info", {"message": msg})
|
||||
|
||||
await self.manager.pull_image(image, progress_callback=callback)
|
||||
|
||||
async def _start_ubridge_capture(self, adapter_number, output_file):
|
||||
@ -1151,9 +1271,11 @@ class DockerVM(BaseNode):
|
||||
if self.status == "started" and self.ubridge:
|
||||
await self._start_ubridge_capture(adapter_number, output_file)
|
||||
|
||||
log.info("Docker VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"Docker VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, adapter_number):
|
||||
"""
|
||||
@ -1169,9 +1291,11 @@ class DockerVM(BaseNode):
|
||||
if self.status == "started" and self.ubridge:
|
||||
await self._stop_ubridge_capture(adapter_number)
|
||||
|
||||
log.info("Docker VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"Docker VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def _get_log(self):
|
||||
"""
|
||||
|
@ -74,36 +74,38 @@ from .adapters.wic_1t import WIC_1T
|
||||
from .adapters.wic_2t import WIC_2T
|
||||
|
||||
|
||||
ADAPTER_MATRIX = {"C7200-IO-2FE": C7200_IO_2FE,
|
||||
"C7200-IO-FE": C7200_IO_FE,
|
||||
"C7200-IO-GE-E": C7200_IO_GE_E,
|
||||
"NM-16ESW": NM_16ESW,
|
||||
"NM-1E": NM_1E,
|
||||
"NM-1FE-TX": NM_1FE_TX,
|
||||
"NM-4E": NM_4E,
|
||||
"NM-4T": NM_4T,
|
||||
"PA-2FE-TX": PA_2FE_TX,
|
||||
"PA-4E": PA_4E,
|
||||
"PA-4T+": PA_4T,
|
||||
"PA-8E": PA_8E,
|
||||
"PA-8T": PA_8T,
|
||||
"PA-A1": PA_A1,
|
||||
"PA-FE-TX": PA_FE_TX,
|
||||
"PA-GE": PA_GE,
|
||||
"PA-POS-OC3": PA_POS_OC3}
|
||||
ADAPTER_MATRIX = {
|
||||
"C7200-IO-2FE": C7200_IO_2FE,
|
||||
"C7200-IO-FE": C7200_IO_FE,
|
||||
"C7200-IO-GE-E": C7200_IO_GE_E,
|
||||
"NM-16ESW": NM_16ESW,
|
||||
"NM-1E": NM_1E,
|
||||
"NM-1FE-TX": NM_1FE_TX,
|
||||
"NM-4E": NM_4E,
|
||||
"NM-4T": NM_4T,
|
||||
"PA-2FE-TX": PA_2FE_TX,
|
||||
"PA-4E": PA_4E,
|
||||
"PA-4T+": PA_4T,
|
||||
"PA-8E": PA_8E,
|
||||
"PA-8T": PA_8T,
|
||||
"PA-A1": PA_A1,
|
||||
"PA-FE-TX": PA_FE_TX,
|
||||
"PA-GE": PA_GE,
|
||||
"PA-POS-OC3": PA_POS_OC3,
|
||||
}
|
||||
|
||||
WIC_MATRIX = {"WIC-1ENET": WIC_1ENET,
|
||||
"WIC-1T": WIC_1T,
|
||||
"WIC-2T": WIC_2T}
|
||||
WIC_MATRIX = {"WIC-1ENET": WIC_1ENET, "WIC-1T": WIC_1T, "WIC-2T": WIC_2T}
|
||||
|
||||
|
||||
PLATFORMS_DEFAULT_RAM = {"c1700": 160,
|
||||
"c2600": 160,
|
||||
"c2691": 192,
|
||||
"c3600": 192,
|
||||
"c3725": 128,
|
||||
"c3745": 256,
|
||||
"c7200": 512}
|
||||
PLATFORMS_DEFAULT_RAM = {
|
||||
"c1700": 160,
|
||||
"c2600": 160,
|
||||
"c2691": 192,
|
||||
"c3600": 192,
|
||||
"c3725": 128,
|
||||
"c3745": 256,
|
||||
"c7200": 512,
|
||||
}
|
||||
|
||||
|
||||
class Dynamips(BaseManager):
|
||||
@ -126,7 +128,7 @@ class Dynamips(BaseManager):
|
||||
"""
|
||||
:returns: List of node type supported by this class and computer
|
||||
"""
|
||||
return ['dynamips', 'frame_relay_switch', 'atm_switch']
|
||||
return ["dynamips", "frame_relay_switch", "atm_switch"]
|
||||
|
||||
def get_dynamips_id(self, project_id):
|
||||
"""
|
||||
@ -301,7 +303,7 @@ class Dynamips(BaseManager):
|
||||
await hypervisor.start()
|
||||
log.info(f"Hypervisor {hypervisor.host}:{hypervisor.port} has successfully started")
|
||||
await hypervisor.connect()
|
||||
if parse_version(hypervisor.version) < parse_version('0.2.11'):
|
||||
if parse_version(hypervisor.version) < parse_version("0.2.11"):
|
||||
raise DynamipsError(f"Dynamips version must be >= 0.2.11, detected version is {hypervisor.version}")
|
||||
|
||||
return hypervisor
|
||||
@ -408,7 +410,15 @@ class Dynamips(BaseManager):
|
||||
if ghost_file_path not in self._ghost_files:
|
||||
# create a new ghost IOS instance
|
||||
ghost_id = str(uuid4())
|
||||
ghost = Router("ghost-" + ghost_file, ghost_id, vm.project, vm.manager, platform=vm.platform, hypervisor=vm.hypervisor, ghost_flag=True)
|
||||
ghost = Router(
|
||||
"ghost-" + ghost_file,
|
||||
ghost_id,
|
||||
vm.project,
|
||||
vm.manager,
|
||||
platform=vm.platform,
|
||||
hypervisor=vm.hypervisor,
|
||||
ghost_flag=True,
|
||||
)
|
||||
try:
|
||||
await ghost.create()
|
||||
await ghost.set_image(vm.image)
|
||||
@ -538,7 +548,7 @@ class Dynamips(BaseManager):
|
||||
with open(path, "wb") as f:
|
||||
if content:
|
||||
content = "!\n" + content.replace("\r", "")
|
||||
content = content.replace('%h', vm.name)
|
||||
content = content.replace("%h", vm.name)
|
||||
f.write(content.encode("utf-8"))
|
||||
except OSError as e:
|
||||
raise DynamipsError(f"Could not create config file '{path}': {e}")
|
||||
@ -571,7 +581,7 @@ class Dynamips(BaseManager):
|
||||
for idlepc in idlepcs:
|
||||
match = re.search(r"^0x[0-9a-f]{8}$", idlepc.split()[0])
|
||||
if not match:
|
||||
continue
|
||||
continue
|
||||
await vm.set_idlepc(idlepc.split()[0])
|
||||
log.debug(f"Auto Idle-PC: trying idle-PC value {vm.idlepc}")
|
||||
start_time = time.time()
|
||||
@ -615,7 +625,7 @@ class Dynamips(BaseManager):
|
||||
|
||||
# Not a Dynamips router
|
||||
if not hasattr(source_node, "startup_config_path"):
|
||||
return (await super().duplicate_node(source_node_id, destination_node_id))
|
||||
return await super().duplicate_node(source_node_id, destination_node_id)
|
||||
|
||||
try:
|
||||
with open(source_node.startup_config_path) as f:
|
||||
@ -627,10 +637,9 @@ class Dynamips(BaseManager):
|
||||
private_config = f.read()
|
||||
except OSError:
|
||||
private_config = None
|
||||
await self.set_vm_configs(destination_node, {
|
||||
"startup_config_content": startup_config,
|
||||
"private_config_content": private_config
|
||||
})
|
||||
await self.set_vm_configs(
|
||||
destination_node, {"startup_config_content": startup_config, "private_config_content": private_config}
|
||||
)
|
||||
|
||||
# Force refresh of the name in configuration files
|
||||
new_name = destination_node.name
|
||||
|
@ -18,7 +18,6 @@ from .adapter import Adapter
|
||||
|
||||
|
||||
class GT96100_FE(Adapter):
|
||||
|
||||
def __init__(self):
|
||||
|
||||
super().__init__(interfaces=2, wics=3)
|
||||
|
@ -29,21 +29,26 @@ from .nodes.ethernet_hub import EthernetHub
|
||||
from .nodes.frame_relay_switch import FrameRelaySwitch
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
PLATFORMS = {'c1700': C1700,
|
||||
'c2600': C2600,
|
||||
'c2691': C2691,
|
||||
'c3725': C3725,
|
||||
'c3745': C3745,
|
||||
'c3600': C3600,
|
||||
'c7200': C7200}
|
||||
PLATFORMS = {
|
||||
"c1700": C1700,
|
||||
"c2600": C2600,
|
||||
"c2691": C2691,
|
||||
"c3725": C3725,
|
||||
"c3745": C3745,
|
||||
"c3600": C3600,
|
||||
"c7200": C7200,
|
||||
}
|
||||
|
||||
|
||||
DEVICES = {'atm_switch': ATMSwitch,
|
||||
'frame_relay_switch': FrameRelaySwitch,
|
||||
'ethernet_switch': EthernetSwitch,
|
||||
'ethernet_hub': EthernetHub}
|
||||
DEVICES = {
|
||||
"atm_switch": ATMSwitch,
|
||||
"frame_relay_switch": FrameRelaySwitch,
|
||||
"ethernet_switch": EthernetSwitch,
|
||||
"ethernet_hub": EthernetHub,
|
||||
}
|
||||
|
||||
|
||||
class DynamipsFactory:
|
||||
|
@ -78,7 +78,9 @@ class DynamipsHypervisor:
|
||||
while time.time() - begin < timeout:
|
||||
await asyncio.sleep(0.01)
|
||||
try:
|
||||
self._reader, self._writer = await asyncio.wait_for(asyncio.open_connection(host, self._port), timeout=1)
|
||||
self._reader, self._writer = await asyncio.wait_for(
|
||||
asyncio.open_connection(host, self._port), timeout=1
|
||||
)
|
||||
except (asyncio.TimeoutError, OSError) as e:
|
||||
last_exception = e
|
||||
continue
|
||||
@ -242,17 +244,20 @@ class DynamipsHypervisor:
|
||||
raise DynamipsError("Not connected")
|
||||
|
||||
try:
|
||||
command = command.strip() + '\n'
|
||||
command = command.strip() + "\n"
|
||||
log.debug(f"sending {command}")
|
||||
self._writer.write(command.encode())
|
||||
await self._writer.drain()
|
||||
except OSError as e:
|
||||
raise DynamipsError("Could not send Dynamips command '{command}' to {host}:{port}: {error}, process running: {run}"
|
||||
.format(command=command.strip(), host=self._host, port=self._port, error=e, run=self.is_running()))
|
||||
raise DynamipsError(
|
||||
"Could not send Dynamips command '{command}' to {host}:{port}: {error}, process running: {run}".format(
|
||||
command=command.strip(), host=self._host, port=self._port, error=e, run=self.is_running()
|
||||
)
|
||||
)
|
||||
|
||||
# Now retrieve the result
|
||||
data = []
|
||||
buf = ''
|
||||
buf = ""
|
||||
retries = 0
|
||||
max_retries = 10
|
||||
while True:
|
||||
@ -272,8 +277,11 @@ class DynamipsHypervisor:
|
||||
continue
|
||||
if not chunk:
|
||||
if retries > max_retries:
|
||||
raise DynamipsError("No data returned from {host}:{port}, Dynamips process running: {run}"
|
||||
.format(host=self._host, port=self._port, run=self.is_running()))
|
||||
raise DynamipsError(
|
||||
"No data returned from {host}:{port}, Dynamips process running: {run}".format(
|
||||
host=self._host, port=self._port, run=self.is_running()
|
||||
)
|
||||
)
|
||||
else:
|
||||
retries += 1
|
||||
await asyncio.sleep(0.1)
|
||||
@ -281,30 +289,36 @@ class DynamipsHypervisor:
|
||||
retries = 0
|
||||
buf += chunk.decode("utf-8", errors="ignore")
|
||||
except OSError as e:
|
||||
raise DynamipsError("Could not read response for '{command}' from {host}:{port}: {error}, process running: {run}"
|
||||
.format(command=command.strip(), host=self._host, port=self._port, error=e, run=self.is_running()))
|
||||
raise DynamipsError(
|
||||
"Could not read response for '{command}' from {host}:{port}: {error}, process running: {run}".format(
|
||||
command=command.strip(), host=self._host, port=self._port, error=e, run=self.is_running()
|
||||
)
|
||||
)
|
||||
|
||||
# If the buffer doesn't end in '\n' then we can't be done
|
||||
try:
|
||||
if buf[-1] != '\n':
|
||||
if buf[-1] != "\n":
|
||||
continue
|
||||
except IndexError:
|
||||
raise DynamipsError("Could not communicate with {host}:{port}, Dynamips process running: {run}"
|
||||
.format(host=self._host, port=self._port, run=self.is_running()))
|
||||
raise DynamipsError(
|
||||
"Could not communicate with {host}:{port}, Dynamips process running: {run}".format(
|
||||
host=self._host, port=self._port, run=self.is_running()
|
||||
)
|
||||
)
|
||||
|
||||
data += buf.split('\r\n')
|
||||
if data[-1] == '':
|
||||
data += buf.split("\r\n")
|
||||
if data[-1] == "":
|
||||
data.pop()
|
||||
buf = ''
|
||||
buf = ""
|
||||
|
||||
# Does it contain an error code?
|
||||
if self.error_re.search(data[-1]):
|
||||
raise DynamipsError(f"Dynamips error when running command '{command}': {data[-1][4:]}")
|
||||
|
||||
# Or does the last line begin with '100-'? Then we are done!
|
||||
if data[-1][:4] == '100-':
|
||||
if data[-1][:4] == "100-":
|
||||
data[-1] = data[-1][4:]
|
||||
if data[-1] == 'OK':
|
||||
if data[-1] == "OK":
|
||||
data.pop()
|
||||
break
|
||||
|
||||
|
@ -28,6 +28,7 @@ from .dynamips_hypervisor import DynamipsHypervisor
|
||||
from .dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -121,17 +122,15 @@ class Hypervisor(DynamipsHypervisor):
|
||||
# add the Npcap directory to $PATH to force Dynamips to use npcap DLL instead of Winpcap (if installed)
|
||||
system_root = os.path.join(os.path.expandvars("%SystemRoot%"), "System32", "Npcap")
|
||||
if os.path.isdir(system_root):
|
||||
env["PATH"] = system_root + ';' + env["PATH"]
|
||||
env["PATH"] = system_root + ";" + env["PATH"]
|
||||
try:
|
||||
log.info(f"Starting Dynamips: {self._command}")
|
||||
self._stdout_file = os.path.join(self.working_dir, f"dynamips_i{self._id}_stdout.txt")
|
||||
log.info(f"Dynamips process logging to {self._stdout_file}")
|
||||
with open(self._stdout_file, "w", encoding="utf-8") as fd:
|
||||
self._process = await asyncio.create_subprocess_exec(*self._command,
|
||||
stdout=fd,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=self._working_dir,
|
||||
env=env)
|
||||
self._process = await asyncio.create_subprocess_exec(
|
||||
*self._command, stdout=fd, stderr=subprocess.STDOUT, cwd=self._working_dir, env=env
|
||||
)
|
||||
log.info(f"Dynamips process started PID={self._process.pid}")
|
||||
self._started = True
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
@ -159,7 +158,7 @@ class Hypervisor(DynamipsHypervisor):
|
||||
except OSError as e:
|
||||
log.error(f"Cannot stop the Dynamips process: {e}")
|
||||
if self._process.returncode is None:
|
||||
log.warning(f'Dynamips hypervisor with PID={self._process.pid} is still running')
|
||||
log.warning(f"Dynamips hypervisor with PID={self._process.pid} is still running")
|
||||
|
||||
if self._stdout_file and os.access(self._stdout_file, os.W_OK):
|
||||
try:
|
||||
|
@ -23,6 +23,7 @@ import asyncio
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -130,9 +131,11 @@ class NIO:
|
||||
raise DynamipsError(f"Unknown direction {direction} to bind filter {filter_name}:")
|
||||
dynamips_direction = self._dynamips_direction[direction]
|
||||
|
||||
await self._hypervisor.send("nio bind_filter {name} {direction} {filter}".format(name=self._name,
|
||||
direction=dynamips_direction,
|
||||
filter=filter_name))
|
||||
await self._hypervisor.send(
|
||||
"nio bind_filter {name} {direction} {filter}".format(
|
||||
name=self._name, direction=dynamips_direction, filter=filter_name
|
||||
)
|
||||
)
|
||||
|
||||
if direction == "in":
|
||||
self._input_filter = filter_name
|
||||
@ -153,8 +156,9 @@ class NIO:
|
||||
raise DynamipsError(f"Unknown direction {direction} to unbind filter:")
|
||||
dynamips_direction = self._dynamips_direction[direction]
|
||||
|
||||
await self._hypervisor.send("nio unbind_filter {name} {direction}".format(name=self._name,
|
||||
direction=dynamips_direction))
|
||||
await self._hypervisor.send(
|
||||
"nio unbind_filter {name} {direction}".format(name=self._name, direction=dynamips_direction)
|
||||
)
|
||||
|
||||
if direction == "in":
|
||||
self._input_filter = None
|
||||
@ -187,9 +191,11 @@ class NIO:
|
||||
raise DynamipsError(f"Unknown direction {direction} to setup filter:")
|
||||
dynamips_direction = self._dynamips_direction[direction]
|
||||
|
||||
await self._hypervisor.send("nio setup_filter {name} {direction} {options}".format(name=self._name,
|
||||
direction=dynamips_direction,
|
||||
options=options))
|
||||
await self._hypervisor.send(
|
||||
"nio setup_filter {name} {direction} {options}".format(
|
||||
name=self._name, direction=dynamips_direction, options=options
|
||||
)
|
||||
)
|
||||
|
||||
if direction == "in":
|
||||
self._input_filter_options = options
|
||||
|
@ -23,6 +23,7 @@ import uuid
|
||||
from .nio import NIO
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -38,17 +39,21 @@ class NIOGenericEthernet(NIO):
|
||||
def __init__(self, hypervisor, ethernet_device):
|
||||
|
||||
# create an unique name
|
||||
name = f'generic_ethernet-{uuid.uuid4()}'
|
||||
name = f"generic_ethernet-{uuid.uuid4()}"
|
||||
self._ethernet_device = ethernet_device
|
||||
super().__init__(name, hypervisor)
|
||||
|
||||
async def create(self):
|
||||
|
||||
await self._hypervisor.send("nio create_gen_eth {name} {eth_device}".format(name=self._name,
|
||||
eth_device=self._ethernet_device))
|
||||
await self._hypervisor.send(
|
||||
"nio create_gen_eth {name} {eth_device}".format(name=self._name, eth_device=self._ethernet_device)
|
||||
)
|
||||
|
||||
log.info("NIO Generic Ethernet {name} created with device {device}".format(name=self._name,
|
||||
device=self._ethernet_device))
|
||||
log.info(
|
||||
"NIO Generic Ethernet {name} created with device {device}".format(
|
||||
name=self._name, device=self._ethernet_device
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def ethernet_device(self):
|
||||
@ -62,5 +67,4 @@ class NIOGenericEthernet(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_generic_ethernet",
|
||||
"ethernet_device": self._ethernet_device}
|
||||
return {"type": "nio_generic_ethernet", "ethernet_device": self._ethernet_device}
|
||||
|
@ -23,6 +23,7 @@ import uuid
|
||||
from .nio import NIO
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -37,17 +38,21 @@ class NIOLinuxEthernet(NIO):
|
||||
|
||||
def __init__(self, hypervisor, ethernet_device):
|
||||
# create an unique name
|
||||
name = f'linux_ethernet-{uuid.uuid4()}'
|
||||
name = f"linux_ethernet-{uuid.uuid4()}"
|
||||
self._ethernet_device = ethernet_device
|
||||
super().__init__(name, hypervisor)
|
||||
|
||||
async def create(self):
|
||||
|
||||
await self._hypervisor.send("nio create_linux_eth {name} {eth_device}".format(name=self._name,
|
||||
eth_device=self._ethernet_device))
|
||||
await self._hypervisor.send(
|
||||
"nio create_linux_eth {name} {eth_device}".format(name=self._name, eth_device=self._ethernet_device)
|
||||
)
|
||||
|
||||
log.info("NIO Linux Ethernet {name} created with device {device}".format(name=self._name,
|
||||
device=self._ethernet_device))
|
||||
log.info(
|
||||
"NIO Linux Ethernet {name} created with device {device}".format(
|
||||
name=self._name, device=self._ethernet_device
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def ethernet_device(self):
|
||||
@ -61,5 +66,4 @@ class NIOLinuxEthernet(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_linux_ethernet",
|
||||
"ethernet_device": self._ethernet_device}
|
||||
return {"type": "nio_linux_ethernet", "ethernet_device": self._ethernet_device}
|
||||
|
@ -23,6 +23,7 @@ import uuid
|
||||
from .nio import NIO
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -37,7 +38,7 @@ class NIONull(NIO):
|
||||
def __init__(self, hypervisor):
|
||||
|
||||
# create an unique name
|
||||
name = f'null-{uuid.uuid4()}'
|
||||
name = f"null-{uuid.uuid4()}"
|
||||
super().__init__(name, hypervisor)
|
||||
|
||||
async def create(self):
|
||||
|
@ -23,6 +23,7 @@ import uuid
|
||||
from .nio import NIO
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -38,7 +39,7 @@ class NIOTAP(NIO):
|
||||
def __init__(self, hypervisor, tap_device):
|
||||
|
||||
# create an unique name
|
||||
name = f'tap-{uuid.uuid4()}'
|
||||
name = f"tap-{uuid.uuid4()}"
|
||||
self._tap_device = tap_device
|
||||
super().__init__(name, hypervisor)
|
||||
|
||||
@ -59,5 +60,4 @@ class NIOTAP(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_tap",
|
||||
"tap_device": self._tap_device}
|
||||
return {"type": "nio_tap", "tap_device": self._tap_device}
|
||||
|
@ -26,6 +26,7 @@ from .nio import NIO
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -43,7 +44,7 @@ class NIOUDP(NIO):
|
||||
def __init__(self, node, lport, rhost, rport):
|
||||
|
||||
# create an unique name
|
||||
name = f'udp-{uuid.uuid4()}'
|
||||
name = f"udp-{uuid.uuid4()}"
|
||||
self._lport = lport
|
||||
self._rhost = rhost
|
||||
self._rport = rport
|
||||
@ -57,48 +58,40 @@ class NIOUDP(NIO):
|
||||
return
|
||||
# Ubridge is not supported
|
||||
if not hasattr(self._node, "add_ubridge_udp_connection"):
|
||||
await self._hypervisor.send("nio create_udp {name} {lport} {rhost} {rport}".format(name=self._name,
|
||||
lport=self._lport,
|
||||
rhost=self._rhost,
|
||||
rport=self._rport))
|
||||
await self._hypervisor.send(
|
||||
"nio create_udp {name} {lport} {rhost} {rport}".format(
|
||||
name=self._name, lport=self._lport, rhost=self._rhost, rport=self._rport
|
||||
)
|
||||
)
|
||||
return
|
||||
self._local_tunnel_lport = self._node.manager.port_manager.get_free_udp_port(self._node.project)
|
||||
self._local_tunnel_rport = self._node.manager.port_manager.get_free_udp_port(self._node.project)
|
||||
self._bridge_name = f'DYNAMIPS-{self._local_tunnel_lport}-{self._local_tunnel_rport}'
|
||||
await self._hypervisor.send("nio create_udp {name} {lport} {rhost} {rport}".format(name=self._name,
|
||||
lport=self._local_tunnel_lport,
|
||||
rhost='127.0.0.1',
|
||||
rport=self._local_tunnel_rport))
|
||||
|
||||
log.info("NIO UDP {name} created with lport={lport}, rhost={rhost}, rport={rport}".format(name=self._name,
|
||||
lport=self._lport,
|
||||
rhost=self._rhost,
|
||||
rport=self._rport))
|
||||
|
||||
self._source_nio = nio_udp.NIOUDP(self._local_tunnel_rport,
|
||||
'127.0.0.1',
|
||||
self._local_tunnel_lport)
|
||||
self._destination_nio = nio_udp.NIOUDP(self._lport,
|
||||
self._rhost,
|
||||
self._rport)
|
||||
self._destination_nio.filters = self._filters
|
||||
await self._node.add_ubridge_udp_connection(
|
||||
self._bridge_name,
|
||||
self._source_nio,
|
||||
self._destination_nio
|
||||
self._bridge_name = f"DYNAMIPS-{self._local_tunnel_lport}-{self._local_tunnel_rport}"
|
||||
await self._hypervisor.send(
|
||||
"nio create_udp {name} {lport} {rhost} {rport}".format(
|
||||
name=self._name, lport=self._local_tunnel_lport, rhost="127.0.0.1", rport=self._local_tunnel_rport
|
||||
)
|
||||
)
|
||||
|
||||
log.info(
|
||||
"NIO UDP {name} created with lport={lport}, rhost={rhost}, rport={rport}".format(
|
||||
name=self._name, lport=self._lport, rhost=self._rhost, rport=self._rport
|
||||
)
|
||||
)
|
||||
|
||||
self._source_nio = nio_udp.NIOUDP(self._local_tunnel_rport, "127.0.0.1", self._local_tunnel_lport)
|
||||
self._destination_nio = nio_udp.NIOUDP(self._lport, self._rhost, self._rport)
|
||||
self._destination_nio.filters = self._filters
|
||||
await self._node.add_ubridge_udp_connection(self._bridge_name, self._source_nio, self._destination_nio)
|
||||
|
||||
async def update(self):
|
||||
self._destination_nio.filters = self._filters
|
||||
await self._node.update_ubridge_udp_connection(
|
||||
self._bridge_name,
|
||||
self._source_nio,
|
||||
self._destination_nio)
|
||||
await self._node.update_ubridge_udp_connection(self._bridge_name, self._source_nio, self._destination_nio)
|
||||
|
||||
async def close(self):
|
||||
if self._local_tunnel_lport:
|
||||
await self._node.ubridge_delete_bridge(self._bridge_name)
|
||||
self._node.manager.port_manager.release_udp_port(self._local_tunnel_lport, self ._node.project)
|
||||
self._node.manager.port_manager.release_udp_port(self._local_tunnel_lport, self._node.project)
|
||||
if self._local_tunnel_rport:
|
||||
self._node.manager.port_manager.release_udp_port(self._local_tunnel_rport, self._node.project)
|
||||
self._node.manager.port_manager.release_udp_port(self._lport, self._node.project)
|
||||
@ -135,7 +128,4 @@ class NIOUDP(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_udp",
|
||||
"lport": self._lport,
|
||||
"rport": self._rport,
|
||||
"rhost": self._rhost}
|
||||
return {"type": "nio_udp", "lport": self._lport, "rport": self._rport, "rhost": self._rhost}
|
||||
|
@ -23,6 +23,7 @@ import uuid
|
||||
from .nio import NIO
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -39,20 +40,24 @@ class NIOUNIX(NIO):
|
||||
def __init__(self, hypervisor, local_file, remote_file):
|
||||
|
||||
# create an unique name
|
||||
name = f'unix-{uuid.uuid4()}'
|
||||
name = f"unix-{uuid.uuid4()}"
|
||||
self._local_file = local_file
|
||||
self._remote_file = remote_file
|
||||
super().__init__(name, hypervisor)
|
||||
|
||||
async def create(self):
|
||||
|
||||
await self._hypervisor.send("nio create_unix {name} {local} {remote}".format(name=self._name,
|
||||
local=self._local_file,
|
||||
remote=self._remote_file))
|
||||
await self._hypervisor.send(
|
||||
"nio create_unix {name} {local} {remote}".format(
|
||||
name=self._name, local=self._local_file, remote=self._remote_file
|
||||
)
|
||||
)
|
||||
|
||||
log.info("NIO UNIX {name} created with local file {local} and remote file {remote}".format(name=self._name,
|
||||
local=self._local_file,
|
||||
remote=self._remote_file))
|
||||
log.info(
|
||||
"NIO UNIX {name} created with local file {local} and remote file {remote}".format(
|
||||
name=self._name, local=self._local_file, remote=self._remote_file
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def local_file(self):
|
||||
@ -76,6 +81,4 @@ class NIOUNIX(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_unix",
|
||||
"local_file": self._local_file,
|
||||
"remote_file": self._remote_file}
|
||||
return {"type": "nio_unix", "local_file": self._local_file, "remote_file": self._remote_file}
|
||||
|
@ -23,6 +23,7 @@ import uuid
|
||||
from .nio import NIO
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -39,20 +40,24 @@ class NIOVDE(NIO):
|
||||
def __init__(self, hypervisor, control_file, local_file):
|
||||
|
||||
# create an unique name
|
||||
name = f'vde-{uuid.uuid4()}'
|
||||
name = f"vde-{uuid.uuid4()}"
|
||||
self._control_file = control_file
|
||||
self._local_file = local_file
|
||||
super().__init__(name, hypervisor)
|
||||
|
||||
async def create(self):
|
||||
|
||||
await self._hypervisor.send("nio create_vde {name} {control} {local}".format(name=self._name,
|
||||
control=self._control_file,
|
||||
local=self._local_file))
|
||||
await self._hypervisor.send(
|
||||
"nio create_vde {name} {control} {local}".format(
|
||||
name=self._name, control=self._control_file, local=self._local_file
|
||||
)
|
||||
)
|
||||
|
||||
log.info("NIO VDE {name} created with control={control}, local={local}".format(name=self._name,
|
||||
control=self._control_file,
|
||||
local=self._local_file))
|
||||
log.info(
|
||||
"NIO VDE {name} created with control={control}, local={local}".format(
|
||||
name=self._name, control=self._control_file, local=self._local_file
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def control_file(self):
|
||||
@ -76,6 +81,4 @@ class NIOVDE(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_vde",
|
||||
"local_file": self._local_file,
|
||||
"control_file": self._control_file}
|
||||
return {"type": "nio_vde", "local_file": self._local_file, "control_file": self._control_file}
|
||||
|
@ -27,6 +27,7 @@ from ..nios.nio_udp import NIOUDP
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -57,12 +58,14 @@ class ATMSwitch(Device):
|
||||
for source, destination in self._mappings.items():
|
||||
mappings[source] = destination
|
||||
|
||||
return {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"mappings": mappings,
|
||||
"status": "started"}
|
||||
return {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"mappings": mappings,
|
||||
"status": "started",
|
||||
}
|
||||
|
||||
async def create(self):
|
||||
|
||||
@ -82,9 +85,11 @@ class ATMSwitch(Device):
|
||||
"""
|
||||
|
||||
await self._hypervisor.send(f'atmsw rename "{self._name}" "{new_name}"')
|
||||
log.info('ATM switch "{name}" [{id}]: renamed to "{new_name}"'.format(name=self._name,
|
||||
id=self._id,
|
||||
new_name=new_name))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: renamed to "{new_name}"'.format(
|
||||
name=self._name, id=self._id, new_name=new_name
|
||||
)
|
||||
)
|
||||
self._name = new_name
|
||||
|
||||
@property
|
||||
@ -163,10 +168,11 @@ class ATMSwitch(Device):
|
||||
if port_number in self._nios:
|
||||
raise DynamipsError(f"Port {port_number} isn't free")
|
||||
|
||||
log.info('ATM switch "{name}" [id={id}]: NIO {nio} bound to port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'ATM switch "{name}" [id={id}]: NIO {nio} bound to port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
self._nios[port_number] = nio
|
||||
await self.set_mappings(self._mappings)
|
||||
@ -189,37 +195,50 @@ class ATMSwitch(Device):
|
||||
source_port, source_vpi, source_vci = source
|
||||
destination_port, destination_vpi, destination_vci = destination
|
||||
if port_number == source_port:
|
||||
log.info('ATM switch "{name}" [{id}]: unmapping VCC between port {source_port} VPI {source_vpi} VCI {source_vci} and port {destination_port} VPI {destination_vpi} VCI {destination_vci}'.format(name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
source_vci=source_vci,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi,
|
||||
destination_vci=destination_vci))
|
||||
await self.unmap_pvc(source_port, source_vpi, source_vci, destination_port, destination_vpi, destination_vci)
|
||||
await self.unmap_pvc(destination_port, destination_vpi, destination_vci, source_port, source_vpi, source_vci)
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: unmapping VCC between port {source_port} VPI {source_vpi} VCI {source_vci} and port {destination_port} VPI {destination_vpi} VCI {destination_vci}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
source_vci=source_vci,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi,
|
||||
destination_vci=destination_vci,
|
||||
)
|
||||
)
|
||||
await self.unmap_pvc(
|
||||
source_port, source_vpi, source_vci, destination_port, destination_vpi, destination_vci
|
||||
)
|
||||
await self.unmap_pvc(
|
||||
destination_port, destination_vpi, destination_vci, source_port, source_vpi, source_vci
|
||||
)
|
||||
else:
|
||||
# remove the virtual paths mapped with this port/nio
|
||||
source_port, source_vpi = source
|
||||
destination_port, destination_vpi = destination
|
||||
if port_number == source_port:
|
||||
log.info('ATM switch "{name}" [{id}]: unmapping VPC between port {source_port} VPI {source_vpi} and port {destination_port} VPI {destination_vpi}'.format(name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: unmapping VPC between port {source_port} VPI {source_vpi} and port {destination_port} VPI {destination_vpi}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi,
|
||||
)
|
||||
)
|
||||
await self.unmap_vp(source_port, source_vpi, destination_port, destination_vpi)
|
||||
await self.unmap_vp(destination_port, destination_vpi, source_port, source_vpi)
|
||||
|
||||
nio = self._nios[port_number]
|
||||
if isinstance(nio, NIOUDP):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
log.info('ATM switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
del self._nios[port_number]
|
||||
return nio
|
||||
@ -261,30 +280,48 @@ class ATMSwitch(Device):
|
||||
source_port, source_vpi, source_vci = map(int, match_source_pvc.group(1, 2, 3))
|
||||
destination_port, destination_vpi, destination_vci = map(int, match_destination_pvc.group(1, 2, 3))
|
||||
if self.has_port(destination_port):
|
||||
if (source_port, source_vpi, source_vci) not in self._active_mappings and \
|
||||
(destination_port, destination_vpi, destination_vci) not in self._active_mappings:
|
||||
log.info('ATM switch "{name}" [{id}]: mapping VCC between port {source_port} VPI {source_vpi} VCI {source_vci} and port {destination_port} VPI {destination_vpi} VCI {destination_vci}'.format(name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
source_vci=source_vci,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi,
|
||||
destination_vci=destination_vci))
|
||||
await self.map_pvc(source_port, source_vpi, source_vci, destination_port, destination_vpi, destination_vci)
|
||||
await self.map_pvc(destination_port, destination_vpi, destination_vci, source_port, source_vpi, source_vci)
|
||||
if (source_port, source_vpi, source_vci) not in self._active_mappings and (
|
||||
destination_port,
|
||||
destination_vpi,
|
||||
destination_vci,
|
||||
) not in self._active_mappings:
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: mapping VCC between port {source_port} VPI {source_vpi} VCI {source_vci} and port {destination_port} VPI {destination_vpi} VCI {destination_vci}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
source_vci=source_vci,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi,
|
||||
destination_vci=destination_vci,
|
||||
)
|
||||
)
|
||||
await self.map_pvc(
|
||||
source_port, source_vpi, source_vci, destination_port, destination_vpi, destination_vci
|
||||
)
|
||||
await self.map_pvc(
|
||||
destination_port, destination_vpi, destination_vci, source_port, source_vpi, source_vci
|
||||
)
|
||||
else:
|
||||
# add the virtual paths
|
||||
source_port, source_vpi = map(int, source.split(':'))
|
||||
destination_port, destination_vpi = map(int, destination.split(':'))
|
||||
source_port, source_vpi = map(int, source.split(":"))
|
||||
destination_port, destination_vpi = map(int, destination.split(":"))
|
||||
if self.has_port(destination_port):
|
||||
if (source_port, source_vpi) not in self._active_mappings and (destination_port, destination_vpi) not in self._active_mappings:
|
||||
log.info('ATM switch "{name}" [{id}]: mapping VPC between port {source_port} VPI {source_vpi} and port {destination_port} VPI {destination_vpi}'.format(name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi))
|
||||
if (source_port, source_vpi) not in self._active_mappings and (
|
||||
destination_port,
|
||||
destination_vpi,
|
||||
) not in self._active_mappings:
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: mapping VPC between port {source_port} VPI {source_vpi} and port {destination_port} VPI {destination_vpi}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_vpi=source_vpi,
|
||||
destination_port=destination_port,
|
||||
destination_vpi=destination_vpi,
|
||||
)
|
||||
)
|
||||
await self.map_vp(source_port, source_vpi, destination_port, destination_vpi)
|
||||
await self.map_vp(destination_port, destination_vpi, source_port, source_vpi)
|
||||
|
||||
@ -307,18 +344,17 @@ class ATMSwitch(Device):
|
||||
nio1 = self._nios[port1]
|
||||
nio2 = self._nios[port2]
|
||||
|
||||
await self._hypervisor.send('atmsw create_vpc "{name}" {input_nio} {input_vpi} {output_nio} {output_vpi}'.format(name=self._name,
|
||||
input_nio=nio1,
|
||||
input_vpi=vpi1,
|
||||
output_nio=nio2,
|
||||
output_vpi=vpi2))
|
||||
await self._hypervisor.send(
|
||||
'atmsw create_vpc "{name}" {input_nio} {input_vpi} {output_nio} {output_vpi}'.format(
|
||||
name=self._name, input_nio=nio1, input_vpi=vpi1, output_nio=nio2, output_vpi=vpi2
|
||||
)
|
||||
)
|
||||
|
||||
log.info('ATM switch "{name}" [{id}]: VPC from port {port1} VPI {vpi1} to port {port2} VPI {vpi2} created'.format(name=self._name,
|
||||
id=self._id,
|
||||
port1=port1,
|
||||
vpi1=vpi1,
|
||||
port2=port2,
|
||||
vpi2=vpi2))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: VPC from port {port1} VPI {vpi1} to port {port2} VPI {vpi2} created'.format(
|
||||
name=self._name, id=self._id, port1=port1, vpi1=vpi1, port2=port2, vpi2=vpi2
|
||||
)
|
||||
)
|
||||
|
||||
self._active_mappings[(port1, vpi1)] = (port2, vpi2)
|
||||
|
||||
@ -341,18 +377,17 @@ class ATMSwitch(Device):
|
||||
nio1 = self._nios[port1]
|
||||
nio2 = self._nios[port2]
|
||||
|
||||
await self._hypervisor.send('atmsw delete_vpc "{name}" {input_nio} {input_vpi} {output_nio} {output_vpi}'.format(name=self._name,
|
||||
input_nio=nio1,
|
||||
input_vpi=vpi1,
|
||||
output_nio=nio2,
|
||||
output_vpi=vpi2))
|
||||
await self._hypervisor.send(
|
||||
'atmsw delete_vpc "{name}" {input_nio} {input_vpi} {output_nio} {output_vpi}'.format(
|
||||
name=self._name, input_nio=nio1, input_vpi=vpi1, output_nio=nio2, output_vpi=vpi2
|
||||
)
|
||||
)
|
||||
|
||||
log.info('ATM switch "{name}" [{id}]: VPC from port {port1} VPI {vpi1} to port {port2} VPI {vpi2} deleted'.format(name=self._name,
|
||||
id=self._id,
|
||||
port1=port1,
|
||||
vpi1=vpi1,
|
||||
port2=port2,
|
||||
vpi2=vpi2))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: VPC from port {port1} VPI {vpi1} to port {port2} VPI {vpi2} deleted'.format(
|
||||
name=self._name, id=self._id, port1=port1, vpi1=vpi1, port2=port2, vpi2=vpi2
|
||||
)
|
||||
)
|
||||
|
||||
del self._active_mappings[(port1, vpi1)]
|
||||
|
||||
@ -377,22 +412,23 @@ class ATMSwitch(Device):
|
||||
nio1 = self._nios[port1]
|
||||
nio2 = self._nios[port2]
|
||||
|
||||
await self._hypervisor.send('atmsw create_vcc "{name}" {input_nio} {input_vpi} {input_vci} {output_nio} {output_vpi} {output_vci}'.format(name=self._name,
|
||||
input_nio=nio1,
|
||||
input_vpi=vpi1,
|
||||
input_vci=vci1,
|
||||
output_nio=nio2,
|
||||
output_vpi=vpi2,
|
||||
output_vci=vci2))
|
||||
await self._hypervisor.send(
|
||||
'atmsw create_vcc "{name}" {input_nio} {input_vpi} {input_vci} {output_nio} {output_vpi} {output_vci}'.format(
|
||||
name=self._name,
|
||||
input_nio=nio1,
|
||||
input_vpi=vpi1,
|
||||
input_vci=vci1,
|
||||
output_nio=nio2,
|
||||
output_vpi=vpi2,
|
||||
output_vci=vci2,
|
||||
)
|
||||
)
|
||||
|
||||
log.info('ATM switch "{name}" [{id}]: VCC from port {port1} VPI {vpi1} VCI {vci1} to port {port2} VPI {vpi2} VCI {vci2} created'.format(name=self._name,
|
||||
id=self._id,
|
||||
port1=port1,
|
||||
vpi1=vpi1,
|
||||
vci1=vci1,
|
||||
port2=port2,
|
||||
vpi2=vpi2,
|
||||
vci2=vci2))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: VCC from port {port1} VPI {vpi1} VCI {vci1} to port {port2} VPI {vpi2} VCI {vci2} created'.format(
|
||||
name=self._name, id=self._id, port1=port1, vpi1=vpi1, vci1=vci1, port2=port2, vpi2=vpi2, vci2=vci2
|
||||
)
|
||||
)
|
||||
|
||||
self._active_mappings[(port1, vpi1, vci1)] = (port2, vpi2, vci2)
|
||||
|
||||
@ -417,22 +453,23 @@ class ATMSwitch(Device):
|
||||
nio1 = self._nios[port1]
|
||||
nio2 = self._nios[port2]
|
||||
|
||||
await self._hypervisor.send('atmsw delete_vcc "{name}" {input_nio} {input_vpi} {input_vci} {output_nio} {output_vpi} {output_vci}'.format(name=self._name,
|
||||
input_nio=nio1,
|
||||
input_vpi=vpi1,
|
||||
input_vci=vci1,
|
||||
output_nio=nio2,
|
||||
output_vpi=vpi2,
|
||||
output_vci=vci2))
|
||||
await self._hypervisor.send(
|
||||
'atmsw delete_vcc "{name}" {input_nio} {input_vpi} {input_vci} {output_nio} {output_vpi} {output_vci}'.format(
|
||||
name=self._name,
|
||||
input_nio=nio1,
|
||||
input_vpi=vpi1,
|
||||
input_vci=vci1,
|
||||
output_nio=nio2,
|
||||
output_vpi=vpi2,
|
||||
output_vci=vci2,
|
||||
)
|
||||
)
|
||||
|
||||
log.info('ATM switch "{name}" [{id}]: VCC from port {port1} VPI {vpi1} VCI {vci1} to port {port2} VPI {vpi2} VCI {vci2} deleted'.format(name=self._name,
|
||||
id=self._id,
|
||||
port1=port1,
|
||||
vpi1=vpi1,
|
||||
vci1=vci1,
|
||||
port2=port2,
|
||||
vpi2=vpi2,
|
||||
vci2=vci2))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: VCC from port {port1} VPI {vpi1} VCI {vci1} to port {port2} VPI {vpi2} VCI {vci2} deleted'.format(
|
||||
name=self._name, id=self._id, port1=port1, vpi1=vpi1, vci1=vci1, port2=port2, vpi2=vpi2, vci2=vci2
|
||||
)
|
||||
)
|
||||
del self._active_mappings[(port1, vpi1, vci1)]
|
||||
|
||||
async def start_capture(self, port_number, output_file, data_link_type="DLT_ATM_RFC1483"):
|
||||
@ -453,9 +490,11 @@ class ATMSwitch(Device):
|
||||
raise DynamipsError(f"Port {port_number} has already a filter applied")
|
||||
|
||||
await nio.start_packet_capture(output_file, data_link_type)
|
||||
log.info('ATM switch "{name}" [{id}]: starting packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: starting packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, port_number):
|
||||
"""
|
||||
@ -468,6 +507,8 @@ class ATMSwitch(Device):
|
||||
if not nio.capturing:
|
||||
return
|
||||
await nio.stop_packet_capture()
|
||||
log.info('ATM switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'ATM switch "{name}" [{id}]: stopping packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
|
@ -56,8 +56,9 @@ class Bridge(Device):
|
||||
:param new_name: New name for this bridge
|
||||
"""
|
||||
|
||||
await self._hypervisor.send('nio_bridge rename "{name}" "{new_name}"'.format(name=self._name,
|
||||
new_name=new_name))
|
||||
await self._hypervisor.send(
|
||||
'nio_bridge rename "{name}" "{new_name}"'.format(name=self._name, new_name=new_name)
|
||||
)
|
||||
|
||||
self._name = new_name
|
||||
|
||||
|
@ -25,6 +25,7 @@ from ..adapters.c1700_mb_1fe import C1700_MB_1FE
|
||||
from ..adapters.c1700_mb_wic1 import C1700_MB_WIC1
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -47,9 +48,23 @@ class C1700(Router):
|
||||
1710 is not supported.
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="1720"):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
chassis="1720",
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c1700")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c1700"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 64
|
||||
@ -63,9 +78,7 @@ class C1700(Router):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
c1700_router_info = {"iomem": self._iomem,
|
||||
"chassis": self._chassis,
|
||||
"sparsemem": self._sparsemem}
|
||||
c1700_router_info = {"iomem": self._iomem, "chassis": self._chassis, "sparsemem": self._sparsemem}
|
||||
|
||||
router_info = Router.__json__(self)
|
||||
router_info.update(c1700_router_info)
|
||||
@ -86,7 +99,7 @@ class C1700(Router):
|
||||
|
||||
# With 1751 and 1760, WICs in WIC slot 1 show up as in slot 1, not 0
|
||||
# e.g. s1/0 not s0/2
|
||||
if self._chassis in ['1751', '1760']:
|
||||
if self._chassis in ["1751", "1760"]:
|
||||
self._create_slots(2)
|
||||
self._slots[1] = C1700_MB_WIC1()
|
||||
else:
|
||||
@ -113,9 +126,9 @@ class C1700(Router):
|
||||
|
||||
await self._hypervisor.send(f'c1700 set_chassis "{self._name}" {chassis}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: chassis set to {chassis}'.format(name=self._name,
|
||||
id=self._id,
|
||||
chassis=chassis))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: chassis set to {chassis}'.format(name=self._name, id=self._id, chassis=chassis)
|
||||
)
|
||||
|
||||
self._chassis = chassis
|
||||
self._setup_chassis()
|
||||
@ -139,8 +152,9 @@ class C1700(Router):
|
||||
|
||||
await self._hypervisor.send(f'c1700 set_iomem "{self._name}" {iomem}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_iomem=self._iomem,
|
||||
new_iomem=iomem))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(
|
||||
name=self._name, id=self._id, old_iomem=self._iomem, new_iomem=iomem
|
||||
)
|
||||
)
|
||||
self._iomem = iomem
|
||||
|
@ -27,6 +27,7 @@ from ..adapters.c2600_mb_1fe import C2600_MB_1FE
|
||||
from ..adapters.c2600_mb_2fe import C2600_MB_2FE
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -51,20 +52,36 @@ class C2600(Router):
|
||||
|
||||
# adapters to insert by default corresponding the
|
||||
# chosen chassis.
|
||||
integrated_adapters = {"2610": C2600_MB_1E,
|
||||
"2611": C2600_MB_2E,
|
||||
"2620": C2600_MB_1FE,
|
||||
"2621": C2600_MB_2FE,
|
||||
"2610XM": C2600_MB_1FE,
|
||||
"2611XM": C2600_MB_2FE,
|
||||
"2620XM": C2600_MB_1FE,
|
||||
"2621XM": C2600_MB_2FE,
|
||||
"2650XM": C2600_MB_1FE,
|
||||
"2651XM": C2600_MB_2FE}
|
||||
integrated_adapters = {
|
||||
"2610": C2600_MB_1E,
|
||||
"2611": C2600_MB_2E,
|
||||
"2620": C2600_MB_1FE,
|
||||
"2621": C2600_MB_2FE,
|
||||
"2610XM": C2600_MB_1FE,
|
||||
"2611XM": C2600_MB_2FE,
|
||||
"2620XM": C2600_MB_1FE,
|
||||
"2621XM": C2600_MB_2FE,
|
||||
"2650XM": C2600_MB_1FE,
|
||||
"2651XM": C2600_MB_2FE,
|
||||
}
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="2610"):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
chassis="2610",
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c2600")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c2600"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 64
|
||||
@ -78,9 +95,7 @@ class C2600(Router):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
c2600_router_info = {"iomem": self._iomem,
|
||||
"chassis": self._chassis,
|
||||
"sparsemem": self._sparsemem}
|
||||
c2600_router_info = {"iomem": self._iomem, "chassis": self._chassis, "sparsemem": self._sparsemem}
|
||||
|
||||
router_info = Router.__json__(self)
|
||||
router_info.update(c2600_router_info)
|
||||
@ -123,9 +138,9 @@ class C2600(Router):
|
||||
|
||||
await self._hypervisor.send(f'c2600 set_chassis "{self._name}" {chassis}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: chassis set to {chassis}'.format(name=self._name,
|
||||
id=self._id,
|
||||
chassis=chassis))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: chassis set to {chassis}'.format(name=self._name, id=self._id, chassis=chassis)
|
||||
)
|
||||
self._chassis = chassis
|
||||
self._setup_chassis()
|
||||
|
||||
@ -148,8 +163,9 @@ class C2600(Router):
|
||||
|
||||
await self._hypervisor.send(f'c2600 set_iomem "{self._name}" {iomem}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_iomem=self._iomem,
|
||||
new_iomem=iomem))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(
|
||||
name=self._name, id=self._id, old_iomem=self._iomem, new_iomem=iomem
|
||||
)
|
||||
)
|
||||
self._iomem = iomem
|
||||
|
@ -25,6 +25,7 @@ from ..adapters.gt96100_fe import GT96100_FE
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -44,9 +45,23 @@ class C2691(Router):
|
||||
:param aux_type: auxiliary console type
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis=None):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
chassis=None,
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c2691")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c2691"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 128
|
||||
@ -89,8 +104,9 @@ class C2691(Router):
|
||||
|
||||
await self._hypervisor.send(f'c2691 set_iomem "{self._name}" {iomem}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_iomem=self._iomem,
|
||||
new_iomem=iomem))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(
|
||||
name=self._name, id=self._id, old_iomem=self._iomem, new_iomem=iomem
|
||||
)
|
||||
)
|
||||
self._iomem = iomem
|
||||
|
@ -24,6 +24,7 @@ from .router import Router
|
||||
from ..adapters.leopard_2fe import Leopard_2FE
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -45,9 +46,23 @@ class C3600(Router):
|
||||
3620, 3640 or 3660 (default = 3640).
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis="3640"):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
chassis="3640",
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3600")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3600"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 128
|
||||
@ -60,8 +75,7 @@ class C3600(Router):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
c3600_router_info = {"iomem": self._iomem,
|
||||
"chassis": self._chassis}
|
||||
c3600_router_info = {"iomem": self._iomem, "chassis": self._chassis}
|
||||
|
||||
router_info = Router.__json__(self)
|
||||
router_info.update(c3600_router_info)
|
||||
@ -107,9 +121,9 @@ class C3600(Router):
|
||||
|
||||
await self._hypervisor.send(f'c3600 set_chassis "{self._name}" {chassis}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: chassis set to {chassis}'.format(name=self._name,
|
||||
id=self._id,
|
||||
chassis=chassis))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: chassis set to {chassis}'.format(name=self._name, id=self._id, chassis=chassis)
|
||||
)
|
||||
|
||||
self._chassis = chassis
|
||||
self._setup_chassis()
|
||||
@ -133,8 +147,9 @@ class C3600(Router):
|
||||
|
||||
await self._hypervisor.send(f'c3600 set_iomem "{self._name}" {iomem}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_iomem=self._iomem,
|
||||
new_iomem=iomem))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(
|
||||
name=self._name, id=self._id, old_iomem=self._iomem, new_iomem=iomem
|
||||
)
|
||||
)
|
||||
self._iomem = iomem
|
||||
|
@ -25,6 +25,7 @@ from ..adapters.gt96100_fe import GT96100_FE
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -44,9 +45,23 @@ class C3725(Router):
|
||||
:param aux_type: auxiliary console type
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis=None):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
chassis=None,
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3725")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3725"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 128
|
||||
@ -89,8 +104,9 @@ class C3725(Router):
|
||||
|
||||
await self._hypervisor.send(f'c3725 set_iomem "{self._name}" {iomem}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_iomem=self._iomem,
|
||||
new_iomem=iomem))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(
|
||||
name=self._name, id=self._id, old_iomem=self._iomem, new_iomem=iomem
|
||||
)
|
||||
)
|
||||
self._iomem = iomem
|
||||
|
@ -25,6 +25,7 @@ from ..adapters.gt96100_fe import GT96100_FE
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -44,9 +45,23 @@ class C3745(Router):
|
||||
:param aux_type: auxiliary console type
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", chassis=None):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
chassis=None,
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3745")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c3745"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 128
|
||||
@ -89,8 +104,9 @@ class C3745(Router):
|
||||
|
||||
await self._hypervisor.send(f'c3745 set_iomem "{self._name}" {iomem}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_iomem=self._iomem,
|
||||
new_iomem=iomem))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: I/O memory updated from {old_iomem}% to {new_iomem}%'.format(
|
||||
name=self._name, id=self._id, old_iomem=self._iomem, new_iomem=iomem
|
||||
)
|
||||
)
|
||||
self._iomem = iomem
|
||||
|
@ -27,6 +27,7 @@ from ..adapters.c7200_io_ge_e import C7200_IO_GE_E
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -47,9 +48,24 @@ class C7200(Router):
|
||||
:param npe: Default NPE
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, dynamips_id, console=None, console_type="telnet", aux=None, aux_type="none", npe="npe-400", chassis=None):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
dynamips_id,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
aux=None,
|
||||
aux_type="none",
|
||||
npe="npe-400",
|
||||
chassis=None,
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c7200")
|
||||
super().__init__(
|
||||
name, node_id, project, manager, dynamips_id, console, console_type, aux, aux_type, platform="c7200"
|
||||
)
|
||||
|
||||
# Set default values for this platform (must be the same as Dynamips)
|
||||
self._ram = 256
|
||||
@ -78,10 +94,12 @@ class C7200(Router):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
c7200_router_info = {"npe": self._npe,
|
||||
"midplane": self._midplane,
|
||||
"sensors": self._sensors,
|
||||
"power_supplies": self._power_supplies}
|
||||
c7200_router_info = {
|
||||
"npe": self._npe,
|
||||
"midplane": self._midplane,
|
||||
"sensors": self._sensors,
|
||||
"power_supplies": self._power_supplies,
|
||||
}
|
||||
|
||||
router_info = Router.__json__(self)
|
||||
router_info.update(c7200_router_info)
|
||||
@ -119,15 +137,16 @@ class C7200(Router):
|
||||
npe-225, npe-300, npe-400 and npe-g2 (PowerPC c7200 only)
|
||||
"""
|
||||
|
||||
if (await self.is_running()):
|
||||
if await self.is_running():
|
||||
raise DynamipsError("Cannot change NPE on running router")
|
||||
|
||||
await self._hypervisor.send(f'c7200 set_npe "{self._name}" {npe}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: NPE updated from {old_npe} to {new_npe}'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_npe=self._npe,
|
||||
new_npe=npe))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: NPE updated from {old_npe} to {new_npe}'.format(
|
||||
name=self._name, id=self._id, old_npe=self._npe, new_npe=npe
|
||||
)
|
||||
)
|
||||
self._npe = npe
|
||||
|
||||
@property
|
||||
@ -149,10 +168,11 @@ class C7200(Router):
|
||||
|
||||
await self._hypervisor.send(f'c7200 set_midplane "{self._name}" {midplane}')
|
||||
|
||||
log.info('Router "{name}" [{id}]: midplane updated from {old_midplane} to {new_midplane}'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_midplane=self._midplane,
|
||||
new_midplane=midplane))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: midplane updated from {old_midplane} to {new_midplane}'.format(
|
||||
name=self._name, id=self._id, old_midplane=self._midplane, new_midplane=midplane
|
||||
)
|
||||
)
|
||||
self._midplane = midplane
|
||||
|
||||
@property
|
||||
@ -179,15 +199,21 @@ class C7200(Router):
|
||||
|
||||
sensor_id = 0
|
||||
for sensor in sensors:
|
||||
await self._hypervisor.send('c7200 set_temp_sensor "{name}" {sensor_id} {temp}'.format(name=self._name,
|
||||
sensor_id=sensor_id,
|
||||
temp=sensor))
|
||||
await self._hypervisor.send(
|
||||
'c7200 set_temp_sensor "{name}" {sensor_id} {temp}'.format(
|
||||
name=self._name, sensor_id=sensor_id, temp=sensor
|
||||
)
|
||||
)
|
||||
|
||||
log.info('Router "{name}" [{id}]: sensor {sensor_id} temperature updated from {old_temp}C to {new_temp}C'.format(name=self._name,
|
||||
id=self._id,
|
||||
sensor_id=sensor_id,
|
||||
old_temp=self._sensors[sensor_id],
|
||||
new_temp=sensors[sensor_id]))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: sensor {sensor_id} temperature updated from {old_temp}C to {new_temp}C'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
sensor_id=sensor_id,
|
||||
old_temp=self._sensors[sensor_id],
|
||||
new_temp=sensors[sensor_id],
|
||||
)
|
||||
)
|
||||
|
||||
sensor_id += 1
|
||||
self._sensors = sensors
|
||||
@ -212,14 +238,17 @@ class C7200(Router):
|
||||
|
||||
power_supply_id = 0
|
||||
for power_supply in power_supplies:
|
||||
await self._hypervisor.send('c7200 set_power_supply "{name}" {power_supply_id} {powered_on}'.format(name=self._name,
|
||||
power_supply_id=power_supply_id,
|
||||
powered_on=power_supply))
|
||||
await self._hypervisor.send(
|
||||
'c7200 set_power_supply "{name}" {power_supply_id} {powered_on}'.format(
|
||||
name=self._name, power_supply_id=power_supply_id, powered_on=power_supply
|
||||
)
|
||||
)
|
||||
|
||||
log.info('Router "{name}" [{id}]: power supply {power_supply_id} state updated to {powered_on}'.format(name=self._name,
|
||||
id=self._id,
|
||||
power_supply_id=power_supply_id,
|
||||
powered_on=power_supply))
|
||||
log.info(
|
||||
'Router "{name}" [{id}]: power supply {power_supply_id} state updated to {powered_on}'.format(
|
||||
name=self._name, id=self._id, power_supply_id=power_supply_id, powered_on=power_supply
|
||||
)
|
||||
)
|
||||
power_supply_id += 1
|
||||
|
||||
self._power_supplies = power_supplies
|
||||
|
@ -24,6 +24,7 @@ from ..dynamips_error import DynamipsError
|
||||
from ...error import NodeError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -48,19 +49,20 @@ class EthernetHub(Bridge):
|
||||
# create 8 ports by default
|
||||
self._ports = []
|
||||
for port_number in range(0, 8):
|
||||
self._ports.append({"port_number": port_number,
|
||||
"name": f"Ethernet{port_number}"})
|
||||
self._ports.append({"port_number": port_number, "name": f"Ethernet{port_number}"})
|
||||
else:
|
||||
self._ports = ports
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"ports_mapping": self._ports,
|
||||
"status": "started"}
|
||||
return {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"ports_mapping": self._ports,
|
||||
"status": "started",
|
||||
}
|
||||
|
||||
@property
|
||||
def ports_mapping(self):
|
||||
@ -107,7 +109,7 @@ class EthernetHub(Bridge):
|
||||
return self._mappings
|
||||
|
||||
async def delete(self):
|
||||
return (await self.close())
|
||||
return await self.close()
|
||||
|
||||
async def close(self):
|
||||
"""
|
||||
@ -144,10 +146,11 @@ class EthernetHub(Bridge):
|
||||
|
||||
await Bridge.add_nio(self, nio)
|
||||
|
||||
log.info('Ethernet hub "{name}" [{id}]: NIO {nio} bound to port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet hub "{name}" [{id}]: NIO {nio} bound to port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
self._mappings[port_number] = nio
|
||||
|
||||
async def remove_nio(self, port_number):
|
||||
@ -168,10 +171,11 @@ class EthernetHub(Bridge):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
await Bridge.remove_nio(self, nio)
|
||||
|
||||
log.info('Ethernet hub "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet hub "{name}" [{id}]: NIO {nio} removed from port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
del self._mappings[port_number]
|
||||
return nio
|
||||
@ -213,9 +217,11 @@ class EthernetHub(Bridge):
|
||||
raise DynamipsError(f"Port {port_number} has already a filter applied")
|
||||
|
||||
await nio.start_packet_capture(output_file, data_link_type)
|
||||
log.info('Ethernet hub "{name}" [{id}]: starting packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet hub "{name}" [{id}]: starting packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, port_number):
|
||||
"""
|
||||
@ -228,6 +234,8 @@ class EthernetHub(Bridge):
|
||||
if not nio.capturing:
|
||||
return
|
||||
await nio.stop_packet_capture()
|
||||
log.info('Ethernet hub "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet hub "{name}" [{id}]: stopping packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
|
@ -21,7 +21,8 @@ http://github.com/GNS3/dynamips/blob/master/README.hypervisor#L558
|
||||
|
||||
import asyncio
|
||||
from gns3server.utils import parse_version
|
||||
#from gns3server.utils.asyncio.embed_shell import EmbedShell, create_telnet_shell
|
||||
|
||||
# from gns3server.utils.asyncio.embed_shell import EmbedShell, create_telnet_shell
|
||||
|
||||
|
||||
from .device import Device
|
||||
@ -30,6 +31,7 @@ from ..dynamips_error import DynamipsError
|
||||
from ...error import NodeError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -84,8 +86,8 @@ class EthernetSwitch(Device):
|
||||
self._nios = {}
|
||||
self._mappings = {}
|
||||
self._telnet_console = None
|
||||
#self._telnet_shell = None
|
||||
#self._telnet_server = None
|
||||
# self._telnet_shell = None
|
||||
# self._telnet_server = None
|
||||
self._console = console
|
||||
self._console_type = console_type
|
||||
|
||||
@ -101,23 +103,24 @@ class EthernetSwitch(Device):
|
||||
# create 8 ports by default
|
||||
self._ports = []
|
||||
for port_number in range(0, 8):
|
||||
self._ports.append({"port_number": port_number,
|
||||
"name": f"Ethernet{port_number}",
|
||||
"type": "access",
|
||||
"vlan": 1})
|
||||
self._ports.append(
|
||||
{"port_number": port_number, "name": f"Ethernet{port_number}", "type": "access", "vlan": 1}
|
||||
)
|
||||
else:
|
||||
self._ports = ports
|
||||
|
||||
def __json__(self):
|
||||
|
||||
ethernet_switch_info = {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"console": self.console,
|
||||
"console_type": self.console_type,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"ports_mapping": self._ports,
|
||||
"status": "started"}
|
||||
ethernet_switch_info = {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"console": self.console,
|
||||
"console_type": self.console_type,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"ports_mapping": self._ports,
|
||||
"status": "started",
|
||||
}
|
||||
|
||||
return ethernet_switch_info
|
||||
|
||||
@ -138,8 +141,10 @@ class EthernetSwitch(Device):
|
||||
|
||||
if self._console_type != console_type:
|
||||
if console_type == "telnet":
|
||||
self.project.emit("log.warning", {
|
||||
"message": f'"{self._name}": Telnet access for switches is not available in this version of GNS3'})
|
||||
self.project.emit(
|
||||
"log.warning",
|
||||
{"message": f'"{self._name}": Telnet access for switches is not available in this version of GNS3'},
|
||||
)
|
||||
self._console_type = console_type
|
||||
|
||||
@property
|
||||
@ -186,15 +191,18 @@ class EthernetSwitch(Device):
|
||||
await self._hypervisor.send(f'ethsw create "{self._name}"')
|
||||
log.info(f'Ethernet switch "{self._name}" [{self._id}] has been created')
|
||||
|
||||
#self._telnet_shell = EthernetSwitchConsole(self)
|
||||
#self._telnet_shell.prompt = self._name + '> '
|
||||
#self._telnet = create_telnet_shell(self._telnet_shell)
|
||||
#try:
|
||||
# self._telnet_shell = EthernetSwitchConsole(self)
|
||||
# self._telnet_shell.prompt = self._name + '> '
|
||||
# self._telnet = create_telnet_shell(self._telnet_shell)
|
||||
# try:
|
||||
# self._telnet_server = (await asyncio.start_server(self._telnet.run, self._manager.port_manager.console_host, self.console))
|
||||
#except OSError as e:
|
||||
# except OSError as e:
|
||||
# self.project.emit("log.warning", {"message": "Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host, self.console, e)})
|
||||
if self._console_type == "telnet":
|
||||
self.project.emit("log.warning", {"message": f'"{self._name}": Telnet access for switches is not available in this version of GNS3'})
|
||||
self.project.emit(
|
||||
"log.warning",
|
||||
{"message": f'"{self._name}": Telnet access for switches is not available in this version of GNS3'},
|
||||
)
|
||||
self._hypervisor.devices.append(self)
|
||||
|
||||
async def set_name(self, new_name):
|
||||
@ -205,9 +213,11 @@ class EthernetSwitch(Device):
|
||||
"""
|
||||
|
||||
await self._hypervisor.send(f'ethsw rename "{self._name}" "{new_name}"')
|
||||
log.info('Ethernet switch "{name}" [{id}]: renamed to "{new_name}"'.format(name=self._name,
|
||||
id=self._id,
|
||||
new_name=new_name))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: renamed to "{new_name}"'.format(
|
||||
name=self._name, id=self._id, new_name=new_name
|
||||
)
|
||||
)
|
||||
self._name = new_name
|
||||
|
||||
@property
|
||||
@ -231,15 +241,15 @@ class EthernetSwitch(Device):
|
||||
return self._mappings
|
||||
|
||||
async def delete(self):
|
||||
return (await self.close())
|
||||
return await self.close()
|
||||
|
||||
async def close(self):
|
||||
"""
|
||||
Deletes this Ethernet switch.
|
||||
"""
|
||||
|
||||
#await self._telnet.close()
|
||||
#if self._telnet_server:
|
||||
# await self._telnet.close()
|
||||
# if self._telnet_server:
|
||||
# self._telnet_server.close()
|
||||
|
||||
for nio in self._nios.values():
|
||||
@ -272,10 +282,11 @@ class EthernetSwitch(Device):
|
||||
|
||||
await self._hypervisor.send(f'ethsw add_nio "{self._name}" {nio}')
|
||||
|
||||
log.info('Ethernet switch "{name}" [{id}]: NIO {nio} bound to port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: NIO {nio} bound to port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
self._nios[port_number] = nio
|
||||
for port_settings in self._ports:
|
||||
if port_settings["port_number"] == port_number:
|
||||
@ -301,10 +312,11 @@ class EthernetSwitch(Device):
|
||||
if self._hypervisor:
|
||||
await self._hypervisor.send(f'ethsw remove_nio "{self._name}" {nio}')
|
||||
|
||||
log.info('Ethernet switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
del self._nios[port_number]
|
||||
if port_number in self._mappings:
|
||||
@ -358,14 +370,15 @@ class EthernetSwitch(Device):
|
||||
raise DynamipsError(f"Port {port_number} is not allocated")
|
||||
|
||||
nio = self._nios[port_number]
|
||||
await self._hypervisor.send('ethsw set_access_port "{name}" {nio} {vlan_id}'.format(name=self._name,
|
||||
nio=nio,
|
||||
vlan_id=vlan_id))
|
||||
await self._hypervisor.send(
|
||||
'ethsw set_access_port "{name}" {nio} {vlan_id}'.format(name=self._name, nio=nio, vlan_id=vlan_id)
|
||||
)
|
||||
|
||||
log.info('Ethernet switch "{name}" [{id}]: port {port} set as an access port in VLAN {vlan_id}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number,
|
||||
vlan_id=vlan_id))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: port {port} set as an access port in VLAN {vlan_id}'.format(
|
||||
name=self._name, id=self._id, port=port_number, vlan_id=vlan_id
|
||||
)
|
||||
)
|
||||
self._mappings[port_number] = ("access", vlan_id)
|
||||
|
||||
async def set_dot1q_port(self, port_number, native_vlan):
|
||||
@ -380,14 +393,17 @@ class EthernetSwitch(Device):
|
||||
raise DynamipsError(f"Port {port_number} is not allocated")
|
||||
|
||||
nio = self._nios[port_number]
|
||||
await self._hypervisor.send('ethsw set_dot1q_port "{name}" {nio} {native_vlan}'.format(name=self._name,
|
||||
nio=nio,
|
||||
native_vlan=native_vlan))
|
||||
await self._hypervisor.send(
|
||||
'ethsw set_dot1q_port "{name}" {nio} {native_vlan}'.format(
|
||||
name=self._name, nio=nio, native_vlan=native_vlan
|
||||
)
|
||||
)
|
||||
|
||||
log.info('Ethernet switch "{name}" [{id}]: port {port} set as a 802.1Q port with native VLAN {vlan_id}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number,
|
||||
vlan_id=native_vlan))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: port {port} set as a 802.1Q port with native VLAN {vlan_id}'.format(
|
||||
name=self._name, id=self._id, port=port_number, vlan_id=native_vlan
|
||||
)
|
||||
)
|
||||
|
||||
self._mappings[port_number] = ("dot1q", native_vlan)
|
||||
|
||||
@ -403,19 +419,22 @@ class EthernetSwitch(Device):
|
||||
raise DynamipsError(f"Port {port_number} is not allocated")
|
||||
|
||||
nio = self._nios[port_number]
|
||||
if ethertype != "0x8100" and parse_version(self.hypervisor.version) < parse_version('0.2.16'):
|
||||
raise DynamipsError(f"Dynamips version required is >= 0.2.16 to change the default QinQ Ethernet type, detected version is {self.hypervisor.version}")
|
||||
if ethertype != "0x8100" and parse_version(self.hypervisor.version) < parse_version("0.2.16"):
|
||||
raise DynamipsError(
|
||||
f"Dynamips version required is >= 0.2.16 to change the default QinQ Ethernet type, detected version is {self.hypervisor.version}"
|
||||
)
|
||||
|
||||
await self._hypervisor.send('ethsw set_qinq_port "{name}" {nio} {outer_vlan} {ethertype}'.format(name=self._name,
|
||||
nio=nio,
|
||||
outer_vlan=outer_vlan,
|
||||
ethertype=ethertype if ethertype != "0x8100" else ""))
|
||||
await self._hypervisor.send(
|
||||
'ethsw set_qinq_port "{name}" {nio} {outer_vlan} {ethertype}'.format(
|
||||
name=self._name, nio=nio, outer_vlan=outer_vlan, ethertype=ethertype if ethertype != "0x8100" else ""
|
||||
)
|
||||
)
|
||||
|
||||
log.info('Ethernet switch "{name}" [{id}]: port {port} set as a QinQ ({ethertype}) port with outer VLAN {vlan_id}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number,
|
||||
vlan_id=outer_vlan,
|
||||
ethertype=ethertype))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: port {port} set as a QinQ ({ethertype}) port with outer VLAN {vlan_id}'.format(
|
||||
name=self._name, id=self._id, port=port_number, vlan_id=outer_vlan, ethertype=ethertype
|
||||
)
|
||||
)
|
||||
self._mappings[port_number] = ("qinq", outer_vlan, ethertype)
|
||||
|
||||
async def get_mac_addr_table(self):
|
||||
@ -453,9 +472,11 @@ class EthernetSwitch(Device):
|
||||
raise DynamipsError(f"Port {port_number} has already a filter applied")
|
||||
|
||||
await nio.start_packet_capture(output_file, data_link_type)
|
||||
log.info('Ethernet switch "{name}" [{id}]: starting packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: starting packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, port_number):
|
||||
"""
|
||||
@ -468,6 +489,8 @@ class EthernetSwitch(Device):
|
||||
if not nio.capturing:
|
||||
return
|
||||
await nio.stop_packet_capture()
|
||||
log.info('Ethernet switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Ethernet switch "{name}" [{id}]: stopping packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
|
@ -26,6 +26,7 @@ from ..nios.nio_udp import NIOUDP
|
||||
from ..dynamips_error import DynamipsError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -56,12 +57,14 @@ class FrameRelaySwitch(Device):
|
||||
for source, destination in self._mappings.items():
|
||||
mappings[source] = destination
|
||||
|
||||
return {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"mappings": mappings,
|
||||
"status": "started"}
|
||||
return {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"project_id": self.project.id,
|
||||
"mappings": mappings,
|
||||
"status": "started",
|
||||
}
|
||||
|
||||
async def create(self):
|
||||
|
||||
@ -81,9 +84,11 @@ class FrameRelaySwitch(Device):
|
||||
"""
|
||||
|
||||
await self._hypervisor.send(f'frsw rename "{self._name}" "{new_name}"')
|
||||
log.info('Frame Relay switch "{name}" [{id}]: renamed to "{new_name}"'.format(name=self._name,
|
||||
id=self._id,
|
||||
new_name=new_name))
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: renamed to "{new_name}"'.format(
|
||||
name=self._name, id=self._id, new_name=new_name
|
||||
)
|
||||
)
|
||||
self._name = new_name
|
||||
|
||||
@property
|
||||
@ -163,10 +168,11 @@ class FrameRelaySwitch(Device):
|
||||
if port_number in self._nios:
|
||||
raise DynamipsError(f"Port {port_number} isn't free")
|
||||
|
||||
log.info('Frame Relay switch "{name}" [{id}]: NIO {nio} bound to port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: NIO {nio} bound to port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
self._nios[port_number] = nio
|
||||
await self.set_mappings(self._mappings)
|
||||
@ -189,12 +195,16 @@ class FrameRelaySwitch(Device):
|
||||
source_port, source_dlci = source
|
||||
destination_port, destination_dlci = destination
|
||||
if port_number == source_port:
|
||||
log.info('Frame Relay switch "{name}" [{id}]: unmapping VC between port {source_port} DLCI {source_dlci} and port {destination_port} DLCI {destination_dlci}'.format(name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_dlci=source_dlci,
|
||||
destination_port=destination_port,
|
||||
destination_dlci=destination_dlci))
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: unmapping VC between port {source_port} DLCI {source_dlci} and port {destination_port} DLCI {destination_dlci}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_dlci=source_dlci,
|
||||
destination_port=destination_port,
|
||||
destination_dlci=destination_dlci,
|
||||
)
|
||||
)
|
||||
await self.unmap_vc(source_port, source_dlci, destination_port, destination_dlci)
|
||||
await self.unmap_vc(destination_port, destination_dlci, source_port, source_dlci)
|
||||
|
||||
@ -202,10 +212,11 @@ class FrameRelaySwitch(Device):
|
||||
if isinstance(nio, NIOUDP):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
|
||||
log.info('Frame Relay switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(
|
||||
name=self._name, id=self._id, nio=nio, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
del self._nios[port_number]
|
||||
return nio
|
||||
@ -239,16 +250,23 @@ class FrameRelaySwitch(Device):
|
||||
for source, destination in mappings.items():
|
||||
if not isinstance(source, str) or not isinstance(destination, str):
|
||||
raise DynamipsError("Invalid Frame-Relay mappings")
|
||||
source_port, source_dlci = map(int, source.split(':'))
|
||||
destination_port, destination_dlci = map(int, destination.split(':'))
|
||||
source_port, source_dlci = map(int, source.split(":"))
|
||||
destination_port, destination_dlci = map(int, destination.split(":"))
|
||||
if self.has_port(destination_port):
|
||||
if (source_port, source_dlci) not in self._active_mappings and (destination_port, destination_dlci) not in self._active_mappings:
|
||||
log.info('Frame Relay switch "{name}" [{id}]: mapping VC between port {source_port} DLCI {source_dlci} and port {destination_port} DLCI {destination_dlci}'.format(name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_dlci=source_dlci,
|
||||
destination_port=destination_port,
|
||||
destination_dlci=destination_dlci))
|
||||
if (source_port, source_dlci) not in self._active_mappings and (
|
||||
destination_port,
|
||||
destination_dlci,
|
||||
) not in self._active_mappings:
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: mapping VC between port {source_port} DLCI {source_dlci} and port {destination_port} DLCI {destination_dlci}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
source_port=source_port,
|
||||
source_dlci=source_dlci,
|
||||
destination_port=destination_port,
|
||||
destination_dlci=destination_dlci,
|
||||
)
|
||||
)
|
||||
|
||||
await self.map_vc(source_port, source_dlci, destination_port, destination_dlci)
|
||||
await self.map_vc(destination_port, destination_dlci, source_port, source_dlci)
|
||||
@ -272,18 +290,17 @@ class FrameRelaySwitch(Device):
|
||||
nio1 = self._nios[port1]
|
||||
nio2 = self._nios[port2]
|
||||
|
||||
await self._hypervisor.send('frsw create_vc "{name}" {input_nio} {input_dlci} {output_nio} {output_dlci}'.format(name=self._name,
|
||||
input_nio=nio1,
|
||||
input_dlci=dlci1,
|
||||
output_nio=nio2,
|
||||
output_dlci=dlci2))
|
||||
await self._hypervisor.send(
|
||||
'frsw create_vc "{name}" {input_nio} {input_dlci} {output_nio} {output_dlci}'.format(
|
||||
name=self._name, input_nio=nio1, input_dlci=dlci1, output_nio=nio2, output_dlci=dlci2
|
||||
)
|
||||
)
|
||||
|
||||
log.info('Frame Relay switch "{name}" [{id}]: VC from port {port1} DLCI {dlci1} to port {port2} DLCI {dlci2} created'.format(name=self._name,
|
||||
id=self._id,
|
||||
port1=port1,
|
||||
dlci1=dlci1,
|
||||
port2=port2,
|
||||
dlci2=dlci2))
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: VC from port {port1} DLCI {dlci1} to port {port2} DLCI {dlci2} created'.format(
|
||||
name=self._name, id=self._id, port1=port1, dlci1=dlci1, port2=port2, dlci2=dlci2
|
||||
)
|
||||
)
|
||||
|
||||
self._active_mappings[(port1, dlci1)] = (port2, dlci2)
|
||||
|
||||
@ -306,18 +323,17 @@ class FrameRelaySwitch(Device):
|
||||
nio1 = self._nios[port1]
|
||||
nio2 = self._nios[port2]
|
||||
|
||||
await self._hypervisor.send('frsw delete_vc "{name}" {input_nio} {input_dlci} {output_nio} {output_dlci}'.format(name=self._name,
|
||||
input_nio=nio1,
|
||||
input_dlci=dlci1,
|
||||
output_nio=nio2,
|
||||
output_dlci=dlci2))
|
||||
await self._hypervisor.send(
|
||||
'frsw delete_vc "{name}" {input_nio} {input_dlci} {output_nio} {output_dlci}'.format(
|
||||
name=self._name, input_nio=nio1, input_dlci=dlci1, output_nio=nio2, output_dlci=dlci2
|
||||
)
|
||||
)
|
||||
|
||||
log.info('Frame Relay switch "{name}" [{id}]: VC from port {port1} DLCI {dlci1} to port {port2} DLCI {dlci2} deleted'.format(name=self._name,
|
||||
id=self._id,
|
||||
port1=port1,
|
||||
dlci1=dlci1,
|
||||
port2=port2,
|
||||
dlci2=dlci2))
|
||||
log.info(
|
||||
'Frame Relay switch "{name}" [{id}]: VC from port {port1} DLCI {dlci1} to port {port2} DLCI {dlci2} deleted'.format(
|
||||
name=self._name, id=self._id, port1=port1, dlci1=dlci1, port2=port2, dlci2=dlci2
|
||||
)
|
||||
)
|
||||
del self._active_mappings[(port1, dlci1)]
|
||||
|
||||
async def start_capture(self, port_number, output_file, data_link_type="DLT_FRELAY"):
|
||||
@ -339,9 +355,11 @@ class FrameRelaySwitch(Device):
|
||||
raise DynamipsError(f"Port {port_number} has already a filter applied")
|
||||
|
||||
await nio.start_packet_capture(output_file, data_link_type)
|
||||
log.info('Frame relay switch "{name}" [{id}]: starting packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Frame relay switch "{name}" [{id}]: starting packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, port_number):
|
||||
"""
|
||||
@ -354,6 +372,8 @@ class FrameRelaySwitch(Device):
|
||||
if not nio.capturing:
|
||||
return
|
||||
await nio.stop_packet_capture()
|
||||
log.info('Frame relay switch "{name}" [{id}]: stopping packet capture on port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
port=port_number))
|
||||
log.info(
|
||||
'Frame relay switch "{name}" [{id}]: stopping packet capture on port {port}'.format(
|
||||
name=self._name, id=self._id, port=port_number
|
||||
)
|
||||
)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -16,7 +16,6 @@
|
||||
|
||||
|
||||
class NodeError(Exception):
|
||||
|
||||
def __init__(self, message, original_exception=None):
|
||||
super().__init__(message)
|
||||
if isinstance(message, Exception):
|
||||
|
@ -26,6 +26,7 @@ from .iou_error import IOUError
|
||||
from .iou_vm import IOUVM
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -48,11 +48,12 @@ import gns3server.utils.images
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IOUVM(BaseNode):
|
||||
module_name = 'iou'
|
||||
module_name = "iou"
|
||||
|
||||
"""
|
||||
IOU VM implementation.
|
||||
@ -65,13 +66,17 @@ class IOUVM(BaseNode):
|
||||
:param console_type: console type
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, application_id=None, path=None, console=None, console_type="telnet"):
|
||||
def __init__(
|
||||
self, name, node_id, project, manager, application_id=None, path=None, console=None, console_type="telnet"
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, console=console, console_type=console_type)
|
||||
|
||||
log.info('IOU "{name}" [{id}]: assigned with application ID {application_id}'.format(name=self._name,
|
||||
id=self._id,
|
||||
application_id=application_id))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: assigned with application ID {application_id}'.format(
|
||||
name=self._name, id=self._id, application_id=application_id
|
||||
)
|
||||
)
|
||||
|
||||
self._iou_process = None
|
||||
self._telnet_server = None
|
||||
@ -170,7 +175,9 @@ class IOUVM(BaseNode):
|
||||
"""
|
||||
|
||||
try:
|
||||
output = await gns3server.utils.asyncio.subprocess_check_output(self._path, "-h", cwd=self.working_dir, stderr=True)
|
||||
output = await gns3server.utils.asyncio.subprocess_check_output(
|
||||
self._path, "-h", cwd=self.working_dir, stderr=True
|
||||
)
|
||||
match = re.search(r"-n <n>\s+Size of nvram in Kb \(default ([0-9]+)KB\)", output)
|
||||
if match:
|
||||
self.nvram = int(match.group(1))
|
||||
@ -206,7 +213,7 @@ class IOUVM(BaseNode):
|
||||
|
||||
# IOU images must start with the ELF magic number, be 32-bit or 64-bit, little endian
|
||||
# and have an ELF version of 1 normal IOS image are big endian!
|
||||
if elf_header_start != b'\x7fELF\x01\x01\x01' and elf_header_start != b'\x7fELF\x02\x01\x01':
|
||||
if elf_header_start != b"\x7fELF\x01\x01\x01" and elf_header_start != b"\x7fELF\x02\x01\x01":
|
||||
raise IOUError(f"'{self._path}' is not a valid IOU image")
|
||||
|
||||
if not os.access(self._path, os.X_OK):
|
||||
@ -214,24 +221,26 @@ class IOUVM(BaseNode):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
iou_vm_info = {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"node_directory": self.working_path,
|
||||
"console": self._console,
|
||||
"console_type": self._console_type,
|
||||
"status": self.status,
|
||||
"project_id": self.project.id,
|
||||
"path": self.path,
|
||||
"md5sum": gns3server.utils.images.md5sum(self.path),
|
||||
"ethernet_adapters": len(self._ethernet_adapters),
|
||||
"serial_adapters": len(self._serial_adapters),
|
||||
"ram": self._ram,
|
||||
"nvram": self._nvram,
|
||||
"l1_keepalives": self._l1_keepalives,
|
||||
"use_default_iou_values": self._use_default_iou_values,
|
||||
"command_line": self.command_line,
|
||||
"application_id": self.application_id}
|
||||
iou_vm_info = {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"node_directory": self.working_path,
|
||||
"console": self._console,
|
||||
"console_type": self._console_type,
|
||||
"status": self.status,
|
||||
"project_id": self.project.id,
|
||||
"path": self.path,
|
||||
"md5sum": gns3server.utils.images.md5sum(self.path),
|
||||
"ethernet_adapters": len(self._ethernet_adapters),
|
||||
"serial_adapters": len(self._serial_adapters),
|
||||
"ram": self._ram,
|
||||
"nvram": self._nvram,
|
||||
"l1_keepalives": self._l1_keepalives,
|
||||
"use_default_iou_values": self._use_default_iou_values,
|
||||
"command_line": self.command_line,
|
||||
"application_id": self.application_id,
|
||||
}
|
||||
|
||||
iou_vm_info["path"] = self.manager.get_relative_image_path(self.path, self.project.path)
|
||||
return iou_vm_info
|
||||
@ -281,10 +290,11 @@ class IOUVM(BaseNode):
|
||||
if self._ram == ram:
|
||||
return
|
||||
|
||||
log.info('IOU "{name}" [{id}]: RAM updated from {old_ram}MB to {new_ram}MB'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_ram=self._ram,
|
||||
new_ram=ram))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: RAM updated from {old_ram}MB to {new_ram}MB'.format(
|
||||
name=self._name, id=self._id, old_ram=self._ram, new_ram=ram
|
||||
)
|
||||
)
|
||||
|
||||
self._ram = ram
|
||||
|
||||
@ -309,10 +319,11 @@ class IOUVM(BaseNode):
|
||||
if self._nvram == nvram:
|
||||
return
|
||||
|
||||
log.info('IOU "{name}" [{id}]: NVRAM updated from {old_nvram}KB to {new_nvram}KB'.format(name=self._name,
|
||||
id=self._id,
|
||||
old_nvram=self._nvram,
|
||||
new_nvram=nvram))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: NVRAM updated from {old_nvram}KB to {new_nvram}KB'.format(
|
||||
name=self._name, id=self._id, old_nvram=self._nvram, new_nvram=nvram
|
||||
)
|
||||
)
|
||||
self._nvram = nvram
|
||||
|
||||
@BaseNode.name.setter
|
||||
@ -383,8 +394,11 @@ class IOUVM(BaseNode):
|
||||
p = re.compile(r"([\.\w]+)\s=>\s+not found")
|
||||
missing_libs = p.findall(output)
|
||||
if missing_libs:
|
||||
raise IOUError("The following shared library dependencies cannot be found for IOU image {}: {}".format(self._path,
|
||||
", ".join(missing_libs)))
|
||||
raise IOUError(
|
||||
"The following shared library dependencies cannot be found for IOU image {}: {}".format(
|
||||
self._path, ", ".join(missing_libs)
|
||||
)
|
||||
)
|
||||
|
||||
async def _check_iou_licence(self):
|
||||
"""
|
||||
@ -422,9 +436,9 @@ class IOUVM(BaseNode):
|
||||
if len(hostname) > 15:
|
||||
log.warning(f"Older IOU images may not boot because hostname '{hostname}' length is above 15 characters")
|
||||
if hostname not in config["license"]:
|
||||
raise IOUError(f"Hostname \"{hostname}\" not found in iourc file {self.iourc_path}")
|
||||
raise IOUError(f'Hostname "{hostname}" not found in iourc file {self.iourc_path}')
|
||||
user_ioukey = config["license"][hostname]
|
||||
if user_ioukey[-1:] != ';':
|
||||
if user_ioukey[-1:] != ";":
|
||||
raise IOUError(f"IOU key not ending with ; in iourc file {self.iourc_path}")
|
||||
if len(user_ioukey) != 17:
|
||||
raise IOUError(f"IOU key length is not 16 characters in iourc file {self.iourc_path}")
|
||||
@ -446,13 +460,15 @@ class IOUVM(BaseNode):
|
||||
raise IOUError(f"Invalid hostid detected: {hostid}")
|
||||
for x in hostname:
|
||||
ioukey += ord(x)
|
||||
pad1 = b'\x4B\x58\x21\x81\x56\x7B\x0D\xF3\x21\x43\x9B\x7E\xAC\x1D\xE6\x8A'
|
||||
pad2 = b'\x80' + 39 * b'\0'
|
||||
ioukey = hashlib.md5(pad1 + pad2 + struct.pack('!I', ioukey) + pad1).hexdigest()[:16]
|
||||
pad1 = b"\x4B\x58\x21\x81\x56\x7B\x0D\xF3\x21\x43\x9B\x7E\xAC\x1D\xE6\x8A"
|
||||
pad2 = b"\x80" + 39 * b"\0"
|
||||
ioukey = hashlib.md5(pad1 + pad2 + struct.pack("!I", ioukey) + pad1).hexdigest()[:16]
|
||||
if ioukey != user_ioukey:
|
||||
raise IOUError("Invalid IOU license key {} detected in iourc file {} for host {}".format(user_ioukey,
|
||||
self.iourc_path,
|
||||
hostname))
|
||||
raise IOUError(
|
||||
"Invalid IOU license key {} detected in iourc file {} for host {}".format(
|
||||
user_ioukey, self.iourc_path, hostname
|
||||
)
|
||||
)
|
||||
|
||||
def _nvram_file(self):
|
||||
"""
|
||||
@ -545,14 +561,15 @@ class IOUVM(BaseNode):
|
||||
command = await self._build_command()
|
||||
try:
|
||||
log.info(f"Starting IOU: {command}")
|
||||
self.command_line = ' '.join(command)
|
||||
self.command_line = " ".join(command)
|
||||
self._iou_process = await asyncio.create_subprocess_exec(
|
||||
*command,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=self.working_dir,
|
||||
env=env)
|
||||
env=env,
|
||||
)
|
||||
log.info(f"IOU instance {self._id} started PID={self._iou_process.pid}")
|
||||
self._started = True
|
||||
self.status = "started"
|
||||
@ -576,16 +593,20 @@ class IOUVM(BaseNode):
|
||||
"""
|
||||
|
||||
if self.console and self.console_type == "telnet":
|
||||
server = AsyncioTelnetServer(reader=self._iou_process.stdout, writer=self._iou_process.stdin, binary=True,
|
||||
echo=True)
|
||||
server = AsyncioTelnetServer(
|
||||
reader=self._iou_process.stdout, writer=self._iou_process.stdin, binary=True, echo=True
|
||||
)
|
||||
try:
|
||||
self._telnet_server = await asyncio.start_server(server.run, self._manager.port_manager.console_host,
|
||||
self.console)
|
||||
self._telnet_server = await asyncio.start_server(
|
||||
server.run, self._manager.port_manager.console_host, self.console
|
||||
)
|
||||
except OSError as e:
|
||||
await self.stop()
|
||||
raise IOUError(
|
||||
"Could not start Telnet server on socket {}:{}: {}".format(self._manager.port_manager.console_host,
|
||||
self.console, e))
|
||||
"Could not start Telnet server on socket {}:{}: {}".format(
|
||||
self._manager.port_manager.console_host, self.console, e
|
||||
)
|
||||
)
|
||||
|
||||
async def reset_console(self):
|
||||
"""
|
||||
@ -618,17 +639,25 @@ class IOUVM(BaseNode):
|
||||
for unit in adapter.ports.keys():
|
||||
nio = adapter.get_nio(unit)
|
||||
if nio and isinstance(nio, NIOUDP):
|
||||
await self._ubridge_send("iol_bridge add_nio_udp {name} {iol_id} {bay} {unit} {lport} {rhost} {rport}".format(name=bridge_name,
|
||||
iol_id=self.application_id,
|
||||
bay=bay_id,
|
||||
unit=unit_id,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport))
|
||||
await self._ubridge_send(
|
||||
"iol_bridge add_nio_udp {name} {iol_id} {bay} {unit} {lport} {rhost} {rport}".format(
|
||||
name=bridge_name,
|
||||
iol_id=self.application_id,
|
||||
bay=bay_id,
|
||||
unit=unit_id,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport,
|
||||
)
|
||||
)
|
||||
if nio.capturing:
|
||||
await self._ubridge_send('iol_bridge start_capture {name} "{output_file}" {data_link_type}'.format(name=bridge_name,
|
||||
output_file=nio.pcap_output_file,
|
||||
data_link_type=re.sub(r"^DLT_", "", nio.pcap_data_link_type)))
|
||||
await self._ubridge_send(
|
||||
'iol_bridge start_capture {name} "{output_file}" {data_link_type}'.format(
|
||||
name=bridge_name,
|
||||
output_file=nio.pcap_output_file,
|
||||
data_link_type=re.sub(r"^DLT_", "", nio.pcap_data_link_type),
|
||||
)
|
||||
)
|
||||
|
||||
await self._ubridge_apply_filters(bay_id, unit_id, nio.filters)
|
||||
unit_id += 1
|
||||
@ -646,11 +675,13 @@ class IOUVM(BaseNode):
|
||||
self._terminate_process_iou()
|
||||
if returncode != 0:
|
||||
if returncode == -11:
|
||||
message = 'IOU VM "{}" process has stopped with return code: {} (segfault). This could be an issue with the IOU image, using a different image may fix this.\n{}'.format(self.name,
|
||||
returncode,
|
||||
self.read_iou_stdout())
|
||||
message = 'IOU VM "{}" process has stopped with return code: {} (segfault). This could be an issue with the IOU image, using a different image may fix this.\n{}'.format(
|
||||
self.name, returncode, self.read_iou_stdout()
|
||||
)
|
||||
else:
|
||||
message = f'IOU VM "{self.name}" process has stopped with return code: {returncode}\n{self.read_iou_stdout()}'
|
||||
message = (
|
||||
f'IOU VM "{self.name}" process has stopped with return code: {returncode}\n{self.read_iou_stdout()}'
|
||||
)
|
||||
log.warning(message)
|
||||
self.project.emit("log.error", {"message": message})
|
||||
if self._telnet_server:
|
||||
@ -764,12 +795,15 @@ class IOUVM(BaseNode):
|
||||
with open(netmap_path, "w", encoding="utf-8") as f:
|
||||
for bay in range(0, 16):
|
||||
for unit in range(0, 4):
|
||||
f.write("{ubridge_id}:{bay}/{unit}{iou_id:>5d}:{bay}/{unit}\n".format(ubridge_id=str(self.application_id + 512),
|
||||
bay=bay,
|
||||
unit=unit,
|
||||
iou_id=self.application_id))
|
||||
log.info("IOU {name} [id={id}]: NETMAP file created".format(name=self._name,
|
||||
id=self._id))
|
||||
f.write(
|
||||
"{ubridge_id}:{bay}/{unit}{iou_id:>5d}:{bay}/{unit}\n".format(
|
||||
ubridge_id=str(self.application_id + 512),
|
||||
bay=bay,
|
||||
unit=unit,
|
||||
iou_id=self.application_id,
|
||||
)
|
||||
)
|
||||
log.info("IOU {name} [id={id}]: NETMAP file created".format(name=self._name, id=self._id))
|
||||
except OSError as e:
|
||||
raise IOUError(f"Could not create {netmap_path}: {e}")
|
||||
|
||||
@ -813,7 +847,7 @@ class IOUVM(BaseNode):
|
||||
command.extend(["-m", str(self._ram)])
|
||||
|
||||
# do not let IOU create the NVRAM anymore
|
||||
#startup_config_file = self.startup_config_file
|
||||
# startup_config_file = self.startup_config_file
|
||||
# if startup_config_file:
|
||||
# command.extend(["-c", os.path.basename(startup_config_file)])
|
||||
|
||||
@ -863,9 +897,11 @@ class IOUVM(BaseNode):
|
||||
for _ in range(0, ethernet_adapters):
|
||||
self._ethernet_adapters.append(EthernetAdapter(interfaces=4))
|
||||
|
||||
log.info('IOU "{name}" [{id}]: number of Ethernet adapters changed to {adapters}'.format(name=self._name,
|
||||
id=self._id,
|
||||
adapters=len(self._ethernet_adapters)))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: number of Ethernet adapters changed to {adapters}'.format(
|
||||
name=self._name, id=self._id, adapters=len(self._ethernet_adapters)
|
||||
)
|
||||
)
|
||||
|
||||
self._adapters = self._ethernet_adapters + self._serial_adapters
|
||||
|
||||
@ -891,9 +927,11 @@ class IOUVM(BaseNode):
|
||||
for _ in range(0, serial_adapters):
|
||||
self._serial_adapters.append(SerialAdapter(interfaces=4))
|
||||
|
||||
log.info('IOU "{name}" [{id}]: number of Serial adapters changed to {adapters}'.format(name=self._name,
|
||||
id=self._id,
|
||||
adapters=len(self._serial_adapters)))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: number of Serial adapters changed to {adapters}'.format(
|
||||
name=self._name, id=self._id, adapters=len(self._serial_adapters)
|
||||
)
|
||||
)
|
||||
|
||||
self._adapters = self._ethernet_adapters + self._serial_adapters
|
||||
|
||||
@ -909,29 +947,39 @@ class IOUVM(BaseNode):
|
||||
try:
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError('Adapter {adapter_number} does not exist for IOU "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
raise IOUError(
|
||||
'Adapter {adapter_number} does not exist for IOU "{name}"'.format(
|
||||
name=self._name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
raise IOUError(
|
||||
"Port {port_number} does not exist on adapter {adapter}".format(
|
||||
adapter=adapter, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
adapter.add_nio(port_number, nio)
|
||||
log.info('IOU "{name}" [{id}]: {nio} added to {adapter_number}/{port_number}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: {nio} added to {adapter_number}/{port_number}'.format(
|
||||
name=self._name, id=self._id, nio=nio, adapter_number=adapter_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
if self.ubridge:
|
||||
bridge_name = f"IOL-BRIDGE-{self.application_id + 512}"
|
||||
await self._ubridge_send("iol_bridge add_nio_udp {name} {iol_id} {bay} {unit} {lport} {rhost} {rport}".format(name=bridge_name,
|
||||
iol_id=self.application_id,
|
||||
bay=adapter_number,
|
||||
unit=port_number,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport))
|
||||
await self._ubridge_send(
|
||||
"iol_bridge add_nio_udp {name} {iol_id} {bay} {unit} {lport} {rhost} {rport}".format(
|
||||
name=bridge_name,
|
||||
iol_id=self.application_id,
|
||||
bay=adapter_number,
|
||||
unit=port_number,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport,
|
||||
)
|
||||
)
|
||||
await self._ubridge_apply_filters(adapter_number, port_number, nio.filters)
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, port_number, nio):
|
||||
@ -955,15 +1003,10 @@ class IOUVM(BaseNode):
|
||||
:param filters: Array of filter dictionnary
|
||||
"""
|
||||
bridge_name = f"IOL-BRIDGE-{self.application_id + 512}"
|
||||
location = '{bridge_name} {bay} {unit}'.format(
|
||||
bridge_name=bridge_name,
|
||||
bay=adapter_number,
|
||||
unit=port_number)
|
||||
await self._ubridge_send('iol_bridge reset_packet_filters ' + location)
|
||||
location = "{bridge_name} {bay} {unit}".format(bridge_name=bridge_name, bay=adapter_number, unit=port_number)
|
||||
await self._ubridge_send("iol_bridge reset_packet_filters " + location)
|
||||
for filter in self._build_filter_list(filters):
|
||||
cmd = 'iol_bridge add_packet_filter {} {}'.format(
|
||||
location,
|
||||
filter)
|
||||
cmd = "iol_bridge add_packet_filter {} {}".format(location, filter)
|
||||
await self._ubridge_send(cmd)
|
||||
|
||||
async def adapter_remove_nio_binding(self, adapter_number, port_number):
|
||||
@ -979,28 +1022,36 @@ class IOUVM(BaseNode):
|
||||
try:
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError('Adapter {adapter_number} does not exist on IOU "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
raise IOUError(
|
||||
'Adapter {adapter_number} does not exist on IOU "{name}"'.format(
|
||||
name=self._name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
raise IOUError(
|
||||
"Port {port_number} does not exist on adapter {adapter}".format(
|
||||
adapter=adapter, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
if isinstance(nio, NIOUDP):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
adapter.remove_nio(port_number)
|
||||
log.info('IOU "{name}" [{id}]: {nio} removed from {adapter_number}/{port_number}'.format(name=self._name,
|
||||
id=self._id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: {nio} removed from {adapter_number}/{port_number}'.format(
|
||||
name=self._name, id=self._id, nio=nio, adapter_number=adapter_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
if self.ubridge:
|
||||
bridge_name = f"IOL-BRIDGE-{self.application_id + 512}"
|
||||
await self._ubridge_send("iol_bridge delete_nio_udp {name} {bay} {unit}".format(name=bridge_name,
|
||||
bay=adapter_number,
|
||||
unit=port_number))
|
||||
await self._ubridge_send(
|
||||
"iol_bridge delete_nio_udp {name} {bay} {unit}".format(
|
||||
name=bridge_name, bay=adapter_number, unit=port_number
|
||||
)
|
||||
)
|
||||
|
||||
return nio
|
||||
|
||||
@ -1017,18 +1068,25 @@ class IOUVM(BaseNode):
|
||||
try:
|
||||
adapter = self._adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise IOUError('Adapter {adapter_number} does not exist on IOU "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
raise IOUError(
|
||||
'Adapter {adapter_number} does not exist on IOU "{name}"'.format(
|
||||
name=self._name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
if not adapter.port_exists(port_number):
|
||||
raise IOUError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
raise IOUError(
|
||||
"Port {port_number} does not exist on adapter {adapter}".format(
|
||||
adapter=adapter, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
nio = adapter.get_nio(port_number)
|
||||
|
||||
if not nio:
|
||||
raise IOUError("NIO {port_number} does not exist on adapter {adapter}".format(adapter=adapter,
|
||||
port_number=port_number))
|
||||
raise IOUError(
|
||||
"NIO {port_number} does not exist on adapter {adapter}".format(adapter=adapter, port_number=port_number)
|
||||
)
|
||||
return nio
|
||||
|
||||
@property
|
||||
@ -1066,13 +1124,17 @@ class IOUVM(BaseNode):
|
||||
if "IOURC" not in os.environ:
|
||||
env["IOURC"] = self.iourc_path
|
||||
try:
|
||||
output = await gns3server.utils.asyncio.subprocess_check_output(self._path, "-h", cwd=self.working_dir, env=env, stderr=True)
|
||||
output = await gns3server.utils.asyncio.subprocess_check_output(
|
||||
self._path, "-h", cwd=self.working_dir, env=env, stderr=True
|
||||
)
|
||||
if re.search(r"-l\s+Enable Layer 1 keepalive messages", output):
|
||||
command.extend(["-l"])
|
||||
else:
|
||||
raise IOUError(f"layer 1 keepalive messages are not supported by {os.path.basename(self._path)}")
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
log.warning(f"could not determine if layer 1 keepalive messages are supported by {os.path.basename(self._path)}: {e}")
|
||||
log.warning(
|
||||
f"could not determine if layer 1 keepalive messages are supported by {os.path.basename(self._path)}: {e}"
|
||||
)
|
||||
|
||||
@property
|
||||
def startup_config_content(self):
|
||||
@ -1102,15 +1164,15 @@ class IOUVM(BaseNode):
|
||||
startup_config_path = os.path.join(self.working_dir, "startup-config.cfg")
|
||||
|
||||
if startup_config is None:
|
||||
startup_config = ''
|
||||
startup_config = ""
|
||||
|
||||
# We disallow erasing the startup config file
|
||||
if len(startup_config) == 0 and os.path.exists(startup_config_path):
|
||||
return
|
||||
|
||||
with open(startup_config_path, 'w+', encoding='utf-8') as f:
|
||||
with open(startup_config_path, "w+", encoding="utf-8") as f:
|
||||
if len(startup_config) == 0:
|
||||
f.write('')
|
||||
f.write("")
|
||||
else:
|
||||
startup_config = startup_config.replace("%h", self._name)
|
||||
f.write(startup_config)
|
||||
@ -1153,15 +1215,15 @@ class IOUVM(BaseNode):
|
||||
private_config_path = os.path.join(self.working_dir, "private-config.cfg")
|
||||
|
||||
if private_config is None:
|
||||
private_config = ''
|
||||
private_config = ""
|
||||
|
||||
# We disallow erasing the private config file
|
||||
if len(private_config) == 0 and os.path.exists(private_config_path):
|
||||
return
|
||||
|
||||
with open(private_config_path, 'w+', encoding='utf-8') as f:
|
||||
with open(private_config_path, "w+", encoding="utf-8") as f:
|
||||
if len(private_config) == 0:
|
||||
f.write('')
|
||||
f.write("")
|
||||
else:
|
||||
private_config = private_config.replace("%h", self._name)
|
||||
f.write(private_config)
|
||||
@ -1176,7 +1238,7 @@ class IOUVM(BaseNode):
|
||||
:returns: path to config file. None if the file doesn't exist
|
||||
"""
|
||||
|
||||
path = os.path.join(self.working_dir, 'startup-config.cfg')
|
||||
path = os.path.join(self.working_dir, "startup-config.cfg")
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
else:
|
||||
@ -1190,7 +1252,7 @@ class IOUVM(BaseNode):
|
||||
:returns: path to config file. None if the file doesn't exist
|
||||
"""
|
||||
|
||||
path = os.path.join(self.working_dir, 'private-config.cfg')
|
||||
path = os.path.join(self.working_dir, "private-config.cfg")
|
||||
if os.path.exists(path):
|
||||
return path
|
||||
else:
|
||||
@ -1205,9 +1267,9 @@ class IOUVM(BaseNode):
|
||||
:returns: path to startup-config file. None if the file doesn't exist
|
||||
"""
|
||||
|
||||
path = os.path.join(self.working_dir, 'startup-config.cfg')
|
||||
path = os.path.join(self.working_dir, "startup-config.cfg")
|
||||
if os.path.exists(path):
|
||||
return 'startup-config.cfg'
|
||||
return "startup-config.cfg"
|
||||
else:
|
||||
return None
|
||||
|
||||
@ -1219,9 +1281,9 @@ class IOUVM(BaseNode):
|
||||
:returns: path to private-config file. None if the file doesn't exist
|
||||
"""
|
||||
|
||||
path = os.path.join(self.working_dir, 'private-config.cfg')
|
||||
path = os.path.join(self.working_dir, "private-config.cfg")
|
||||
if os.path.exists(path):
|
||||
return 'private-config.cfg'
|
||||
return "private-config.cfg"
|
||||
else:
|
||||
return None
|
||||
|
||||
@ -1288,7 +1350,7 @@ class IOUVM(BaseNode):
|
||||
except (binascii.Error, OSError) as e:
|
||||
raise IOUError(f"Could not save the startup configuration {config_path}: {e}")
|
||||
|
||||
if private_config_content and private_config_content != b'\nend\n':
|
||||
if private_config_content and private_config_content != b"\nend\n":
|
||||
config_path = os.path.join(self.working_dir, "private-config.cfg")
|
||||
try:
|
||||
config = private_config_content.decode("utf-8", errors="replace")
|
||||
@ -1310,23 +1372,34 @@ class IOUVM(BaseNode):
|
||||
|
||||
nio = self.get_nio(adapter_number, port_number)
|
||||
if nio.capturing:
|
||||
raise IOUError("Packet capture is already activated on {adapter_number}/{port_number}".format(adapter_number=adapter_number,
|
||||
port_number=port_number))
|
||||
raise IOUError(
|
||||
"Packet capture is already activated on {adapter_number}/{port_number}".format(
|
||||
adapter_number=adapter_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
nio.start_packet_capture(output_file, data_link_type)
|
||||
log.info('IOU "{name}" [{id}]: starting packet capture on {adapter_number}/{port_number} to {output_file}'.format(name=self._name,
|
||||
id=self._id,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number,
|
||||
output_file=output_file))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: starting packet capture on {adapter_number}/{port_number} to {output_file}'.format(
|
||||
name=self._name,
|
||||
id=self._id,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number,
|
||||
output_file=output_file,
|
||||
)
|
||||
)
|
||||
|
||||
if self.ubridge:
|
||||
bridge_name = f"IOL-BRIDGE-{self.application_id + 512}"
|
||||
await self._ubridge_send('iol_bridge start_capture {name} {bay} {unit} "{output_file}" {data_link_type}'.format(name=bridge_name,
|
||||
bay=adapter_number,
|
||||
unit=port_number,
|
||||
output_file=output_file,
|
||||
data_link_type=re.sub(r"^DLT_", "", data_link_type)))
|
||||
await self._ubridge_send(
|
||||
'iol_bridge start_capture {name} {bay} {unit} "{output_file}" {data_link_type}'.format(
|
||||
name=bridge_name,
|
||||
bay=adapter_number,
|
||||
unit=port_number,
|
||||
output_file=output_file,
|
||||
data_link_type=re.sub(r"^DLT_", "", data_link_type),
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, adapter_number, port_number):
|
||||
"""
|
||||
@ -1340,12 +1413,15 @@ class IOUVM(BaseNode):
|
||||
if not nio.capturing:
|
||||
return
|
||||
nio.stop_packet_capture()
|
||||
log.info('IOU "{name}" [{id}]: stopping packet capture on {adapter_number}/{port_number}'.format(name=self._name,
|
||||
id=self._id,
|
||||
adapter_number=adapter_number,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
'IOU "{name}" [{id}]: stopping packet capture on {adapter_number}/{port_number}'.format(
|
||||
name=self._name, id=self._id, adapter_number=adapter_number, port_number=port_number
|
||||
)
|
||||
)
|
||||
if self.ubridge:
|
||||
bridge_name = f"IOL-BRIDGE-{self.application_id + 512}"
|
||||
await self._ubridge_send('iol_bridge stop_capture {name} {bay} {unit}'.format(name=bridge_name,
|
||||
bay=adapter_number,
|
||||
unit=port_number))
|
||||
await self._ubridge_send(
|
||||
"iol_bridge stop_capture {name} {bay} {unit}".format(
|
||||
name=bridge_name, bay=adapter_number, unit=port_number
|
||||
)
|
||||
)
|
||||
|
@ -47,28 +47,28 @@ def uncompress_LZC(data):
|
||||
LZC_NUM_BITS_MIN = 9
|
||||
LZC_NUM_BITS_MAX = 16
|
||||
|
||||
in_data = bytearray(data)
|
||||
in_len = len(in_data)
|
||||
in_data = bytearray(data)
|
||||
in_len = len(in_data)
|
||||
out_data = bytearray()
|
||||
|
||||
if in_len == 0:
|
||||
return out_data
|
||||
if in_len < 3:
|
||||
raise ValueError('invalid length')
|
||||
raise ValueError("invalid length")
|
||||
if in_data[0] != 0x1F or in_data[1] != 0x9D:
|
||||
raise ValueError('invalid header')
|
||||
raise ValueError("invalid header")
|
||||
|
||||
max_bits = in_data[2] & 0x1F
|
||||
if max_bits < LZC_NUM_BITS_MIN or max_bits > LZC_NUM_BITS_MAX:
|
||||
raise ValueError('not supported')
|
||||
raise ValueError("not supported")
|
||||
num_items = 1 << max_bits
|
||||
blockmode = (in_data[2] & 0x80) != 0
|
||||
|
||||
in_pos = 3
|
||||
in_pos = 3
|
||||
start_pos = in_pos
|
||||
num_bits = LZC_NUM_BITS_MIN
|
||||
num_bits = LZC_NUM_BITS_MIN
|
||||
dict_size = 1 << num_bits
|
||||
head = 256
|
||||
head = 256
|
||||
if blockmode:
|
||||
head += 1
|
||||
first_sym = True
|
||||
@ -89,7 +89,7 @@ def uncompress_LZC(data):
|
||||
buf, symbol = divmod(buf, dict_size)
|
||||
buf_bits -= num_bits
|
||||
except IndexError:
|
||||
raise ValueError('invalid data')
|
||||
raise ValueError("invalid data")
|
||||
|
||||
# re-initialize dictionary
|
||||
if blockmode and symbol == 256:
|
||||
@ -108,7 +108,7 @@ def uncompress_LZC(data):
|
||||
if first_sym:
|
||||
first_sym = False
|
||||
if symbol >= 256:
|
||||
raise ValueError('invalid data')
|
||||
raise ValueError("invalid data")
|
||||
prev = symbol
|
||||
out_data.extend(comp_dict[symbol])
|
||||
continue
|
||||
@ -124,7 +124,7 @@ def uncompress_LZC(data):
|
||||
elif symbol == head:
|
||||
comp_dict[head] = comp_dict[prev] + comp_dict[prev][0:1]
|
||||
else:
|
||||
raise ValueError('invalid data')
|
||||
raise ValueError("invalid data")
|
||||
prev = symbol
|
||||
|
||||
# output symbol
|
||||
@ -148,42 +148,42 @@ def nvram_export(nvram):
|
||||
offset = 0
|
||||
# extract startup config
|
||||
try:
|
||||
(magic, data_format, _, _, _, _, length, _, _, _, _, _) = \
|
||||
struct.unpack_from('>HHHHIIIIIHHI', nvram, offset=offset)
|
||||
(magic, data_format, _, _, _, _, length, _, _, _, _, _) = struct.unpack_from(
|
||||
">HHHHIIIIIHHI", nvram, offset=offset
|
||||
)
|
||||
offset += 36
|
||||
if magic != 0xABCD:
|
||||
raise ValueError('no startup config')
|
||||
if len(nvram) < offset+length:
|
||||
raise ValueError('invalid length')
|
||||
startup = nvram[offset:offset+length]
|
||||
raise ValueError("no startup config")
|
||||
if len(nvram) < offset + length:
|
||||
raise ValueError("invalid length")
|
||||
startup = nvram[offset : offset + length]
|
||||
except struct.error:
|
||||
raise ValueError('invalid length')
|
||||
raise ValueError("invalid length")
|
||||
|
||||
# uncompress startup config
|
||||
if data_format == 2:
|
||||
try:
|
||||
startup = uncompress_LZC(startup)
|
||||
except ValueError as err:
|
||||
raise ValueError('uncompress startup: ' + str(err))
|
||||
raise ValueError("uncompress startup: " + str(err))
|
||||
|
||||
private = None
|
||||
try:
|
||||
# calculate offset of private header
|
||||
length += (4 - length % 4) % 4 # alignment to multiple of 4
|
||||
length += (4 - length % 4) % 4 # alignment to multiple of 4
|
||||
offset += length
|
||||
# check for additonal offset of 4
|
||||
(magic, data_format) = struct.unpack_from('>HH', nvram, offset=offset+4)
|
||||
(magic, data_format) = struct.unpack_from(">HH", nvram, offset=offset + 4)
|
||||
if magic == 0xFEDC and data_format == 1:
|
||||
offset += 4
|
||||
|
||||
# extract private config
|
||||
(magic, data_format, _, _, length) = \
|
||||
struct.unpack_from('>HHIII', nvram, offset=offset)
|
||||
(magic, data_format, _, _, length) = struct.unpack_from(">HHIII", nvram, offset=offset)
|
||||
offset += 16
|
||||
if magic == 0xFEDC and data_format == 1:
|
||||
if len(nvram) < offset+length:
|
||||
raise ValueError('invalid length')
|
||||
private = nvram[offset:offset+length]
|
||||
if len(nvram) < offset + length:
|
||||
raise ValueError("invalid length")
|
||||
private = nvram[offset : offset + length]
|
||||
|
||||
# missing private header is not an error
|
||||
except struct.error:
|
||||
@ -192,22 +192,19 @@ def nvram_export(nvram):
|
||||
return (startup, private)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
# Main program
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
parser = argparse.ArgumentParser(description='%(prog)s exports startup/private configuration from IOU NVRAM file.')
|
||||
parser.add_argument('nvram', metavar='NVRAM',
|
||||
help='NVRAM file')
|
||||
parser.add_argument('startup', metavar='startup-config',
|
||||
help='startup configuration')
|
||||
parser.add_argument('private', metavar='private-config', nargs='?',
|
||||
help='private configuration')
|
||||
parser = argparse.ArgumentParser(description="%(prog)s exports startup/private configuration from IOU NVRAM file.")
|
||||
parser.add_argument("nvram", metavar="NVRAM", help="NVRAM file")
|
||||
parser.add_argument("startup", metavar="startup-config", help="startup configuration")
|
||||
parser.add_argument("private", metavar="private-config", nargs="?", help="private configuration")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
fd = open(args.nvram, 'rb')
|
||||
fd = open(args.nvram, "rb")
|
||||
nvram = fd.read()
|
||||
fd.close()
|
||||
except OSError as err:
|
||||
@ -221,14 +218,14 @@ if __name__ == '__main__':
|
||||
sys.exit(3)
|
||||
|
||||
try:
|
||||
fd = open(args.startup, 'wb')
|
||||
fd = open(args.startup, "wb")
|
||||
fd.write(startup)
|
||||
fd.close()
|
||||
if args.private is not None:
|
||||
if private is None:
|
||||
sys.stderr.write("Warning: No private config\n")
|
||||
else:
|
||||
fd = open(args.private, 'wb')
|
||||
fd = open(args.private, "wb")
|
||||
fd.write(private)
|
||||
fd.close()
|
||||
except OSError as err:
|
||||
|
@ -39,7 +39,7 @@ import struct
|
||||
|
||||
# calculate padding
|
||||
def padding(length, start_address):
|
||||
pad = -length % 4 # padding to alignment of 4
|
||||
pad = -length % 4 # padding to alignment of 4
|
||||
# extra padding if pad != 0 and big start_address
|
||||
if pad != 0 and (start_address & 0x80000000) != 0:
|
||||
pad += 4
|
||||
@ -50,66 +50,64 @@ def padding(length, start_address):
|
||||
def checksum(data, start, end):
|
||||
chk = 0
|
||||
# calculate checksum of first two words
|
||||
for word in struct.unpack_from('>2H', data, start):
|
||||
for word in struct.unpack_from(">2H", data, start):
|
||||
chk += word
|
||||
|
||||
# add remaining words, ignoring old checksum at offset 4
|
||||
struct_format = f'>{(end - start - 6) // 2:d}H'
|
||||
for word in struct.unpack_from(struct_format, data, start+6):
|
||||
struct_format = f">{(end - start - 6) // 2:d}H"
|
||||
for word in struct.unpack_from(struct_format, data, start + 6):
|
||||
chk += word
|
||||
|
||||
# handle 16 bit overflow
|
||||
while chk >> 16:
|
||||
chk = (chk & 0xffff) + (chk >> 16)
|
||||
chk = chk ^ 0xffff
|
||||
chk = (chk & 0xFFFF) + (chk >> 16)
|
||||
chk = chk ^ 0xFFFF
|
||||
|
||||
# save checksum
|
||||
struct.pack_into('>H', data, start+4, chk)
|
||||
struct.pack_into(">H", data, start + 4, chk)
|
||||
|
||||
|
||||
# import IOU NVRAM
|
||||
# NVRAM format: https://github.com/ehlers/IOUtools/blob/master/NVRAM.md
|
||||
def nvram_import(nvram, startup, private, size):
|
||||
DEFAULT_IOS = 0x0F04 # IOS 15.4
|
||||
DEFAULT_IOS = 0x0F04 # IOS 15.4
|
||||
base_address = 0x10000000
|
||||
|
||||
# check size parameter
|
||||
if size is not None and (size < 8 or size > 1024):
|
||||
raise ValueError('invalid size')
|
||||
raise ValueError("invalid size")
|
||||
|
||||
# create new nvram if nvram is empty or has wrong size
|
||||
if nvram is None or (size is not None and len(nvram) != size*1024):
|
||||
nvram = bytearray([0] * (size*1024))
|
||||
if nvram is None or (size is not None and len(nvram) != size * 1024):
|
||||
nvram = bytearray([0] * (size * 1024))
|
||||
else:
|
||||
nvram = bytearray(nvram)
|
||||
|
||||
# check nvram size
|
||||
nvram_len = len(nvram)
|
||||
if nvram_len < 8*1024 or nvram_len > 1024*1024 or nvram_len % 1024 != 0:
|
||||
raise ValueError('invalid NVRAM length')
|
||||
if nvram_len < 8 * 1024 or nvram_len > 1024 * 1024 or nvram_len % 1024 != 0:
|
||||
raise ValueError("invalid NVRAM length")
|
||||
nvram_len = nvram_len // 2
|
||||
|
||||
# get size of current config
|
||||
config_len = 0
|
||||
try:
|
||||
(magic, _, _, ios, start_addr, _, length, _, _, _, _, _) = \
|
||||
struct.unpack_from('>HHHHIIIIIHHI', nvram, offset=0)
|
||||
(magic, _, _, ios, start_addr, _, length, _, _, _, _, _) = struct.unpack_from(">HHHHIIIIIHHI", nvram, offset=0)
|
||||
if magic == 0xABCD:
|
||||
base_address = start_addr - 36
|
||||
config_len = 36 + length + padding(length, base_address)
|
||||
(magic, _, _, _, length) = \
|
||||
struct.unpack_from('>HHIII', nvram, offset=config_len)
|
||||
(magic, _, _, _, length) = struct.unpack_from(">HHIII", nvram, offset=config_len)
|
||||
if magic == 0xFEDC:
|
||||
config_len += 16 + length
|
||||
else:
|
||||
ios = None
|
||||
except struct.error:
|
||||
raise ValueError('unknown nvram format')
|
||||
raise ValueError("unknown nvram format")
|
||||
if config_len > nvram_len:
|
||||
raise ValueError('unknown nvram format')
|
||||
raise ValueError("unknown nvram format")
|
||||
|
||||
# calculate max. config size
|
||||
max_config = nvram_len - 2*1024 # reserve 2k for files
|
||||
max_config = nvram_len - 2 * 1024 # reserve 2k for files
|
||||
idx = max_config
|
||||
empty_sector = bytearray([0] * 1024)
|
||||
while True:
|
||||
@ -117,11 +115,10 @@ def nvram_import(nvram, startup, private, size):
|
||||
if idx < config_len:
|
||||
break
|
||||
# if valid file header:
|
||||
(magic, _, flags, length, _) = \
|
||||
struct.unpack_from('>HHHH24s', nvram, offset=idx)
|
||||
(magic, _, flags, length, _) = struct.unpack_from(">HHHH24s", nvram, offset=idx)
|
||||
if magic == 0xDCBA and flags < 8 and length <= 992:
|
||||
max_config = idx
|
||||
elif nvram[idx:idx+1024] != empty_sector:
|
||||
elif nvram[idx : idx + 1024] != empty_sector:
|
||||
break
|
||||
|
||||
# import startup config
|
||||
@ -131,34 +128,46 @@ def nvram_import(nvram, startup, private, size):
|
||||
# the padding of a different version, the startup config is padded
|
||||
# with '\n' to the alignment of 4.
|
||||
ios = DEFAULT_IOS
|
||||
startup += b'\n' * (-len(startup) % 4)
|
||||
new_nvram.extend(struct.pack('>HHHHIIIIIHHI',
|
||||
0xABCD, # magic
|
||||
1, # raw data
|
||||
0, # checksum, not yet calculated
|
||||
ios, # IOS version
|
||||
base_address + 36, # start address
|
||||
base_address + 36 + len(startup), # end address
|
||||
len(startup), # length
|
||||
0, 0, 0, 0, 0))
|
||||
startup += b"\n" * (-len(startup) % 4)
|
||||
new_nvram.extend(
|
||||
struct.pack(
|
||||
">HHHHIIIIIHHI",
|
||||
0xABCD, # magic
|
||||
1, # raw data
|
||||
0, # checksum, not yet calculated
|
||||
ios, # IOS version
|
||||
base_address + 36, # start address
|
||||
base_address + 36 + len(startup), # end address
|
||||
len(startup), # length
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
)
|
||||
)
|
||||
new_nvram.extend(startup)
|
||||
new_nvram.extend([0] * padding(len(new_nvram), base_address))
|
||||
|
||||
# import private config
|
||||
if private is None:
|
||||
private = b''
|
||||
private = b""
|
||||
offset = len(new_nvram)
|
||||
new_nvram.extend(struct.pack('>HHIII',
|
||||
0xFEDC, # magic
|
||||
1, # raw data
|
||||
base_address + offset + 16, # start address
|
||||
base_address + offset + 16 + len(private), # end address
|
||||
len(private) )) # length
|
||||
new_nvram.extend(
|
||||
struct.pack(
|
||||
">HHIII",
|
||||
0xFEDC, # magic
|
||||
1, # raw data
|
||||
base_address + offset + 16, # start address
|
||||
base_address + offset + 16 + len(private), # end address
|
||||
len(private),
|
||||
)
|
||||
) # length
|
||||
new_nvram.extend(private)
|
||||
|
||||
# add rest
|
||||
if len(new_nvram) > max_config:
|
||||
raise ValueError('NVRAM size too small')
|
||||
raise ValueError("NVRAM size too small")
|
||||
new_nvram.extend([0] * (max_config - len(new_nvram)))
|
||||
new_nvram.extend(nvram[max_config:])
|
||||
|
||||
@ -167,7 +176,7 @@ def nvram_import(nvram, startup, private, size):
|
||||
return new_nvram
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
# Main program
|
||||
import argparse
|
||||
import sys
|
||||
@ -176,36 +185,32 @@ if __name__ == '__main__':
|
||||
try:
|
||||
value = int(string)
|
||||
except ValueError:
|
||||
raise argparse.ArgumentTypeError('invalid int value: ' + string)
|
||||
raise argparse.ArgumentTypeError("invalid int value: " + string)
|
||||
if value < 8 or value > 1024:
|
||||
raise argparse.ArgumentTypeError('size must be 8..1024')
|
||||
raise argparse.ArgumentTypeError("size must be 8..1024")
|
||||
return value
|
||||
|
||||
parser = argparse.ArgumentParser(description='%(prog)s imports startup/private configuration into IOU NVRAM file.')
|
||||
parser.add_argument('-c', '--create', metavar='size', type=check_size,
|
||||
help='create NVRAM file, size in kByte')
|
||||
parser.add_argument('nvram', metavar='NVRAM',
|
||||
help='NVRAM file')
|
||||
parser.add_argument('startup', metavar='startup-config',
|
||||
help='startup configuration')
|
||||
parser.add_argument('private', metavar='private-config', nargs='?',
|
||||
help='private configuration')
|
||||
parser = argparse.ArgumentParser(description="%(prog)s imports startup/private configuration into IOU NVRAM file.")
|
||||
parser.add_argument("-c", "--create", metavar="size", type=check_size, help="create NVRAM file, size in kByte")
|
||||
parser.add_argument("nvram", metavar="NVRAM", help="NVRAM file")
|
||||
parser.add_argument("startup", metavar="startup-config", help="startup configuration")
|
||||
parser.add_argument("private", metavar="private-config", nargs="?", help="private configuration")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
if args.create is None:
|
||||
fd = open(args.nvram, 'rb')
|
||||
fd = open(args.nvram, "rb")
|
||||
nvram = fd.read()
|
||||
fd.close()
|
||||
else:
|
||||
nvram = None
|
||||
fd = open(args.startup, 'rb')
|
||||
fd = open(args.startup, "rb")
|
||||
startup = fd.read()
|
||||
fd.close()
|
||||
if args.private is None:
|
||||
private = None
|
||||
else:
|
||||
fd = open(args.private, 'rb')
|
||||
fd = open(args.private, "rb")
|
||||
private = fd.read()
|
||||
fd.close()
|
||||
except OSError as err:
|
||||
@ -219,7 +224,7 @@ if __name__ == '__main__':
|
||||
sys.exit(3)
|
||||
|
||||
try:
|
||||
fd = open(args.nvram, 'wb')
|
||||
fd = open(args.nvram, "wb")
|
||||
fd.write(nvram)
|
||||
fd.close()
|
||||
except OSError as err:
|
||||
|
@ -50,5 +50,4 @@ class NIOEthernet(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_ethernet",
|
||||
"ethernet_device": self._ethernet_device}
|
||||
return {"type": "nio_ethernet", "ethernet_device": self._ethernet_device}
|
||||
|
@ -50,5 +50,4 @@ class NIOTAP(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_tap",
|
||||
"tap_device": self._tap_device}
|
||||
return {"type": "nio_tap", "tap_device": self._tap_device}
|
||||
|
@ -74,7 +74,4 @@ class NIOUDP(NIO):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"type": "nio_udp",
|
||||
"lport": self._lport,
|
||||
"rport": self._rport,
|
||||
"rhost": self._rhost}
|
||||
return {"type": "nio_udp", "lport": self._lport, "rport": self._rport, "rhost": self._rhost}
|
||||
|
@ -68,6 +68,6 @@ class NotificationManager:
|
||||
:returns: instance of NotificationManager
|
||||
"""
|
||||
|
||||
if not hasattr(NotificationManager, '_instance') or NotificationManager._instance is None:
|
||||
if not hasattr(NotificationManager, "_instance") or NotificationManager._instance is None:
|
||||
NotificationManager._instance = NotificationManager()
|
||||
return NotificationManager._instance
|
||||
|
@ -19,14 +19,77 @@ from fastapi import HTTPException, status
|
||||
from gns3server.config import Config
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# These ports are disallowed by Chrome and Firefox to avoid issues, we skip them as well
|
||||
BANNED_PORTS = {1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 77, 79, 87, 95, 101, 102, 103,
|
||||
104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 139, 143, 179, 389, 465, 512, 513, 514, 515, 526,
|
||||
530, 531, 532, 540, 556, 563, 587, 601, 636, 993, 995, 2049, 3659, 4045, 6000, 6665, 6666, 6667,
|
||||
6668, 6669}
|
||||
BANNED_PORTS = {
|
||||
1,
|
||||
7,
|
||||
9,
|
||||
11,
|
||||
13,
|
||||
15,
|
||||
17,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
25,
|
||||
37,
|
||||
42,
|
||||
43,
|
||||
53,
|
||||
77,
|
||||
79,
|
||||
87,
|
||||
95,
|
||||
101,
|
||||
102,
|
||||
103,
|
||||
104,
|
||||
109,
|
||||
110,
|
||||
111,
|
||||
113,
|
||||
115,
|
||||
117,
|
||||
119,
|
||||
123,
|
||||
135,
|
||||
139,
|
||||
143,
|
||||
179,
|
||||
389,
|
||||
465,
|
||||
512,
|
||||
513,
|
||||
514,
|
||||
515,
|
||||
526,
|
||||
530,
|
||||
531,
|
||||
532,
|
||||
540,
|
||||
556,
|
||||
563,
|
||||
587,
|
||||
601,
|
||||
636,
|
||||
993,
|
||||
995,
|
||||
2049,
|
||||
3659,
|
||||
4045,
|
||||
6000,
|
||||
6665,
|
||||
6666,
|
||||
6667,
|
||||
6668,
|
||||
6669,
|
||||
}
|
||||
|
||||
|
||||
class PortManager:
|
||||
@ -66,10 +129,12 @@ class PortManager:
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"console_port_range": self._console_port_range,
|
||||
"console_ports": list(self._used_tcp_ports),
|
||||
"udp_port_range": self._udp_port_range,
|
||||
"udp_ports": list(self._used_udp_ports)}
|
||||
return {
|
||||
"console_port_range": self._console_port_range,
|
||||
"console_ports": list(self._used_tcp_ports),
|
||||
"udp_port_range": self._udp_port_range,
|
||||
"udp_ports": list(self._used_udp_ports),
|
||||
}
|
||||
|
||||
@property
|
||||
def console_host(self):
|
||||
@ -145,8 +210,9 @@ class PortManager:
|
||||
"""
|
||||
|
||||
if end_port < start_port:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"Invalid port range {start_port}-{end_port}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT, detail=f"Invalid port range {start_port}-{end_port}"
|
||||
)
|
||||
|
||||
last_exception = None
|
||||
for port in range(start_port, end_port + 1):
|
||||
@ -165,9 +231,11 @@ class PortManager:
|
||||
else:
|
||||
continue
|
||||
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"Could not find a free port between {start_port} and {end_port} on host {host},"
|
||||
f" last exception: {last_exception}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"Could not find a free port between {start_port} and {end_port} on host {host},"
|
||||
f" last exception: {last_exception}",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _check_port(host, port, socket_type):
|
||||
@ -200,11 +268,13 @@ class PortManager:
|
||||
port_range_start = self._console_port_range[0]
|
||||
port_range_end = self._console_port_range[1]
|
||||
|
||||
port = self.find_unused_port(port_range_start,
|
||||
port_range_end,
|
||||
host=self._console_host,
|
||||
socket_type="TCP",
|
||||
ignore_ports=self._used_tcp_ports)
|
||||
port = self.find_unused_port(
|
||||
port_range_start,
|
||||
port_range_end,
|
||||
host=self._console_host,
|
||||
socket_type="TCP",
|
||||
ignore_ports=self._used_tcp_ports,
|
||||
)
|
||||
|
||||
self._used_tcp_ports.add(port)
|
||||
project.record_tcp_port(port)
|
||||
@ -237,8 +307,10 @@ class PortManager:
|
||||
if port < port_range_start or port > port_range_end:
|
||||
old_port = port
|
||||
port = self.get_free_tcp_port(project, port_range_start=port_range_start, port_range_end=port_range_end)
|
||||
msg = f"TCP port {old_port} is outside the range {port_range_start}-{port_range_end} on host " \
|
||||
f"{self._console_host}. Port has been replaced by {port}"
|
||||
msg = (
|
||||
f"TCP port {old_port} is outside the range {port_range_start}-{port_range_end} on host "
|
||||
f"{self._console_host}. Port has been replaced by {port}"
|
||||
)
|
||||
log.debug(msg)
|
||||
return port
|
||||
try:
|
||||
@ -274,11 +346,13 @@ class PortManager:
|
||||
|
||||
:param project: Project instance
|
||||
"""
|
||||
port = self.find_unused_port(self._udp_port_range[0],
|
||||
self._udp_port_range[1],
|
||||
host=self._udp_host,
|
||||
socket_type="UDP",
|
||||
ignore_ports=self._used_udp_ports)
|
||||
port = self.find_unused_port(
|
||||
self._udp_port_range[0],
|
||||
self._udp_port_range[1],
|
||||
host=self._udp_host,
|
||||
socket_type="UDP",
|
||||
ignore_ports=self._used_udp_ports,
|
||||
)
|
||||
|
||||
self._used_udp_ports.add(port)
|
||||
project.record_udp_port(port)
|
||||
@ -294,12 +368,15 @@ class PortManager:
|
||||
"""
|
||||
|
||||
if port in self._used_udp_ports:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"UDP port {port} already in use on host {self._console_host}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"UDP port {port} already in use on host {self._console_host}",
|
||||
)
|
||||
if port < self._udp_port_range[0] or port > self._udp_port_range[1]:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"UDP port {port} is outside the range "
|
||||
f"{self._udp_port_range[0]}-{self._udp_port_range[1]}")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_409_CONFLICT,
|
||||
detail=f"UDP port {port} is outside the range " f"{self._udp_port_range[0]}-{self._udp_port_range[1]}",
|
||||
)
|
||||
self._used_udp_ports.add(port)
|
||||
project.record_udp_port(port)
|
||||
log.debug(f"UDP port {port} has been reserved")
|
||||
|
@ -29,6 +29,7 @@ from ..utils.asyncio import wait_run_in_executor
|
||||
from ..utils.path import check_path_allowed, get_default_project_directory
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -78,11 +79,7 @@ class Project:
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {
|
||||
"name": self._name,
|
||||
"project_id": self._id,
|
||||
"variables": self._variables
|
||||
}
|
||||
return {"name": self._name, "project_id": self._id, "variables": self._variables}
|
||||
|
||||
def is_local(self):
|
||||
|
||||
@ -225,7 +222,6 @@ class Project:
|
||||
"""
|
||||
return os.path.join(self._path, "project-files", node.manager.module_name.lower(), node.id)
|
||||
|
||||
|
||||
def tmp_working_directory(self):
|
||||
"""
|
||||
A temporary directory. Will be clean at project open and close
|
||||
@ -296,7 +292,7 @@ class Project:
|
||||
# we need to update docker nodes when variables changes
|
||||
if original_variables != variables:
|
||||
for node in self.nodes:
|
||||
if hasattr(node, 'update'):
|
||||
if hasattr(node, "update"):
|
||||
await node.update()
|
||||
|
||||
async def close(self):
|
||||
@ -385,6 +381,7 @@ class Project:
|
||||
|
||||
# We import it at the last time to avoid circular dependencies
|
||||
from ..compute import MODULES
|
||||
|
||||
return MODULES
|
||||
|
||||
def emit(self, action, event):
|
||||
@ -411,7 +408,9 @@ class Project:
|
||||
file_info = {"path": path}
|
||||
|
||||
try:
|
||||
file_info["md5sum"] = await wait_run_in_executor(self._hash_file, os.path.join(dirpath, filename))
|
||||
file_info["md5sum"] = await wait_run_in_executor(
|
||||
self._hash_file, os.path.join(dirpath, filename)
|
||||
)
|
||||
except OSError:
|
||||
continue
|
||||
files.append(file_info)
|
||||
|
@ -23,6 +23,7 @@ from uuid import UUID
|
||||
from gns3server.compute.compute_error import ComputeError, ComputeNotFoundError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -91,9 +92,7 @@ class ProjectManager:
|
||||
# send a warning if used disk space is >= 90%
|
||||
if used_disk_space >= 90:
|
||||
message = 'Only {:.2f}% or less of free disk space detected in "{}" on "{}"'.format(
|
||||
100 - used_disk_space,
|
||||
project.path,
|
||||
platform.node()
|
||||
100 - used_disk_space, project.path, platform.node()
|
||||
)
|
||||
log.warning(message)
|
||||
project.emit("log.warning", {"message": message})
|
||||
@ -106,8 +105,7 @@ class ProjectManager:
|
||||
"""
|
||||
if project_id is not None and project_id in self._projects:
|
||||
return self._projects[project_id]
|
||||
project = Project(name=name, project_id=project_id,
|
||||
path=path, variables=variables)
|
||||
project = Project(name=name, project_id=project_id, path=path, variables=variables)
|
||||
self._check_available_disk_space(project)
|
||||
self._projects[project.id] = project
|
||||
return project
|
||||
|
@ -35,6 +35,7 @@ from .utils.guest_cid import get_next_guest_cid
|
||||
from .utils.ziputils import unpack_zip
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -153,9 +154,11 @@ class Qemu(BaseManager):
|
||||
for f in os.listdir(path):
|
||||
if f.endswith("-spice"):
|
||||
continue
|
||||
if (f.startswith("qemu-system") or f.startswith("qemu-kvm") or f == "qemu" or f == "qemu.exe") and \
|
||||
os.access(os.path.join(path, f), os.X_OK) and \
|
||||
os.path.isfile(os.path.join(path, f)):
|
||||
if (
|
||||
(f.startswith("qemu-system") or f.startswith("qemu-kvm") or f == "qemu" or f == "qemu.exe")
|
||||
and os.access(os.path.join(path, f), os.X_OK)
|
||||
and os.path.isfile(os.path.join(path, f))
|
||||
):
|
||||
if archs is not None:
|
||||
for arch in archs:
|
||||
if f.endswith(arch) or f.endswith(f"{arch}.exe") or f.endswith(f"{arch}w.exe"):
|
||||
@ -183,9 +186,11 @@ class Qemu(BaseManager):
|
||||
for path in Qemu.paths_list():
|
||||
try:
|
||||
for f in os.listdir(path):
|
||||
if (f == "qemu-img" or f == "qemu-img.exe") and \
|
||||
os.access(os.path.join(path, f), os.X_OK) and \
|
||||
os.path.isfile(os.path.join(path, f)):
|
||||
if (
|
||||
(f == "qemu-img" or f == "qemu-img.exe")
|
||||
and os.access(os.path.join(path, f), os.X_OK)
|
||||
and os.path.isfile(os.path.join(path, f))
|
||||
):
|
||||
qemu_path = os.path.join(path, f)
|
||||
version = await Qemu._get_qemu_img_version(qemu_path)
|
||||
qemu_imgs.append({"path": qemu_path, "version": version})
|
||||
@ -255,17 +260,21 @@ class Qemu(BaseManager):
|
||||
:returns: HAXM version number. Returns None if HAXM is not installed.
|
||||
"""
|
||||
|
||||
assert(sys.platform.startswith("win"))
|
||||
assert sys.platform.startswith("win")
|
||||
import winreg
|
||||
|
||||
hkey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products")
|
||||
hkey = winreg.OpenKey(
|
||||
winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products"
|
||||
)
|
||||
version = None
|
||||
for index in range(winreg.QueryInfoKey(hkey)[0]):
|
||||
product_id = winreg.EnumKey(hkey, index)
|
||||
try:
|
||||
product_key = winreg.OpenKey(hkey, fr"{product_id}\InstallProperties")
|
||||
try:
|
||||
if winreg.QueryValueEx(product_key, "DisplayName")[0].endswith("Hardware Accelerated Execution Manager"):
|
||||
if winreg.QueryValueEx(product_key, "DisplayName")[0].endswith(
|
||||
"Hardware Accelerated Execution Manager"
|
||||
):
|
||||
version = winreg.QueryValueEx(product_key, "DisplayVersion")[0]
|
||||
break
|
||||
finally:
|
||||
@ -310,8 +319,10 @@ class Qemu(BaseManager):
|
||||
if os.path.exists(path):
|
||||
raise QemuError(f"Could not create disk image '{path}', file already exists")
|
||||
except UnicodeEncodeError:
|
||||
raise QemuError("Could not create disk image '{}', "
|
||||
"path contains characters not supported by filesystem".format(path))
|
||||
raise QemuError(
|
||||
"Could not create disk image '{}', "
|
||||
"path contains characters not supported by filesystem".format(path)
|
||||
)
|
||||
|
||||
command = [qemu_img, "create", "-f", img_format]
|
||||
for option in sorted(options.keys()):
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -17,6 +17,7 @@
|
||||
from ..qemu_error import QemuError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -59,10 +59,12 @@ class Qcow2:
|
||||
# } QCowHeader;
|
||||
|
||||
struct_format = ">IIQi"
|
||||
with open(self._path, 'rb') as f:
|
||||
with open(self._path, "rb") as f:
|
||||
content = f.read(struct.calcsize(struct_format))
|
||||
try:
|
||||
self.magic, self.version, self.backing_file_offset, self.backing_file_size = struct.unpack_from(struct_format, content)
|
||||
self.magic, self.version, self.backing_file_offset, self.backing_file_size = struct.unpack_from(
|
||||
struct_format, content
|
||||
)
|
||||
|
||||
except struct.error:
|
||||
raise Qcow2Error(f"Invalid file header for {self._path}")
|
||||
@ -78,7 +80,7 @@ class Qcow2:
|
||||
:returns: None if it's not a linked clone, the path otherwise
|
||||
"""
|
||||
|
||||
with open(self._path, 'rb') as f:
|
||||
with open(self._path, "rb") as f:
|
||||
f.seek(self.backing_file_offset)
|
||||
content = f.read(self.backing_file_size)
|
||||
|
||||
|
@ -20,12 +20,14 @@ import time
|
||||
import shutil
|
||||
import zipfile
|
||||
|
||||
|
||||
def pack_zip(filename, root_dir=None, base_dir=None):
|
||||
"""Create a zip archive"""
|
||||
|
||||
if filename[-4:].lower() == ".zip":
|
||||
filename = filename[:-4]
|
||||
shutil.make_archive(filename, 'zip', root_dir, base_dir)
|
||||
shutil.make_archive(filename, "zip", root_dir, base_dir)
|
||||
|
||||
|
||||
def unpack_zip(filename, extract_dir=None):
|
||||
"""Unpack a zip archive"""
|
||||
@ -35,7 +37,7 @@ def unpack_zip(filename, extract_dir=None):
|
||||
extract_dir = os.getcwd()
|
||||
|
||||
try:
|
||||
with zipfile.ZipFile(filename, 'r') as zfile:
|
||||
with zipfile.ZipFile(filename, "r") as zfile:
|
||||
for zinfo in zfile.infolist():
|
||||
fname = os.path.join(extract_dir, zinfo.filename)
|
||||
date_time = time.mktime(zinfo.date_time + (0, 0, -1))
|
||||
|
@ -40,4 +40,4 @@ class TraceNG(BaseManager):
|
||||
:returns: TraceNGVM instance
|
||||
"""
|
||||
|
||||
return (await super().create_node(*args, **kwargs))
|
||||
return await super().create_node(*args, **kwargs)
|
||||
|
@ -36,11 +36,12 @@ from ..base_node import BaseNode
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TraceNGVM(BaseNode):
|
||||
module_name = 'traceng'
|
||||
module_name = "traceng"
|
||||
|
||||
"""
|
||||
TraceNG VM implementation.
|
||||
@ -111,16 +112,18 @@ class TraceNGVM(BaseNode):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
return {"name": self.name,
|
||||
"ip_address": self.ip_address,
|
||||
"default_destination": self._default_destination,
|
||||
"node_id": self.id,
|
||||
"node_directory": self.working_path,
|
||||
"status": self.status,
|
||||
"console": self._console,
|
||||
"console_type": "none",
|
||||
"project_id": self.project.id,
|
||||
"command_line": self.command_line}
|
||||
return {
|
||||
"name": self.name,
|
||||
"ip_address": self.ip_address,
|
||||
"default_destination": self._default_destination,
|
||||
"node_id": self.id,
|
||||
"node_directory": self.working_path,
|
||||
"status": self.status,
|
||||
"console": self._console,
|
||||
"console_type": "none",
|
||||
"project_id": self.project.id,
|
||||
"command_line": self.command_line,
|
||||
}
|
||||
|
||||
def _traceng_path(self):
|
||||
"""
|
||||
@ -161,10 +164,11 @@ class TraceNGVM(BaseNode):
|
||||
raise TraceNGError(f"Invalid IP address: {ip_address}\n")
|
||||
|
||||
self._ip_address = ip_address
|
||||
log.info("{module}: {name} [{id}] set IP address to {ip_address}".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
ip_address=ip_address))
|
||||
log.info(
|
||||
"{module}: {name} [{id}] set IP address to {ip_address}".format(
|
||||
module=self.manager.module_name, name=self.name, id=self.id, ip_address=ip_address
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def default_destination(self):
|
||||
@ -185,10 +189,11 @@ class TraceNGVM(BaseNode):
|
||||
"""
|
||||
|
||||
self._default_destination = destination
|
||||
log.info("{module}: {name} [{id}] set default destination to {destination}".format(module=self.manager.module_name,
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
destination=destination))
|
||||
log.info(
|
||||
"{module}: {name} [{id}] set default destination to {destination}".format(
|
||||
module=self.manager.module_name, name=self.name, id=self.id, destination=destination
|
||||
)
|
||||
)
|
||||
|
||||
async def start(self, destination=None):
|
||||
"""
|
||||
@ -207,10 +212,10 @@ class TraceNGVM(BaseNode):
|
||||
flags = 0
|
||||
if hasattr(subprocess, "CREATE_NEW_CONSOLE"):
|
||||
flags = subprocess.CREATE_NEW_CONSOLE
|
||||
self.command_line = ' '.join(command)
|
||||
self._process = await asyncio.create_subprocess_exec(*command,
|
||||
cwd=self.working_dir,
|
||||
creationflags=flags)
|
||||
self.command_line = " ".join(command)
|
||||
self._process = await asyncio.create_subprocess_exec(
|
||||
*command, cwd=self.working_dir, creationflags=flags
|
||||
)
|
||||
monitor_process(self._process, self._termination_callback)
|
||||
|
||||
await self._start_ubridge()
|
||||
@ -277,9 +282,9 @@ class TraceNGVM(BaseNode):
|
||||
"""
|
||||
|
||||
log.info(f"Stopping TraceNG instance {self.name} PID={self._process.pid}")
|
||||
#if sys.platform.startswith("win32"):
|
||||
# if sys.platform.startswith("win32"):
|
||||
# self._process.send_signal(signal.CTRL_BREAK_EVENT)
|
||||
#else:
|
||||
# else:
|
||||
try:
|
||||
self._process.terminate()
|
||||
# Sometime the process may already be dead when we garbage collect
|
||||
@ -306,17 +311,21 @@ class TraceNGVM(BaseNode):
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
raise TraceNGError(
|
||||
"Port {port_number} doesn't exist in adapter {adapter}".format(
|
||||
adapter=self._ethernet_adapter, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
if self.is_running():
|
||||
await self.add_ubridge_udp_connection(f"TraceNG-{self._id}", self._local_udp_tunnel[1], nio)
|
||||
|
||||
self._ethernet_adapter.add_nio(port_number, nio)
|
||||
log.info('TraceNG "{name}" [{id}]: {nio} added to port {port_number}'.format(name=self._name,
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
'TraceNG "{name}" [{id}]: {nio} added to port {port_number}'.format(
|
||||
name=self._name, id=self.id, nio=nio, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
return nio
|
||||
|
||||
@ -329,8 +338,11 @@ class TraceNGVM(BaseNode):
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise TraceNGError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
raise TraceNGError(
|
||||
"Port {port_number} doesn't exist on adapter {adapter}".format(
|
||||
adapter=self._ethernet_adapter, port_number=port_number
|
||||
)
|
||||
)
|
||||
if self.is_running():
|
||||
await self.update_ubridge_udp_connection(f"TraceNG-{self._id}", self._local_udp_tunnel[1], nio)
|
||||
|
||||
@ -344,8 +356,11 @@ class TraceNGVM(BaseNode):
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise TraceNGError("Port {port_number} doesn't exist in adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
raise TraceNGError(
|
||||
"Port {port_number} doesn't exist in adapter {adapter}".format(
|
||||
adapter=self._ethernet_adapter, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
await self.stop_capture(port_number)
|
||||
if self.is_running():
|
||||
@ -356,10 +371,11 @@ class TraceNGVM(BaseNode):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
self._ethernet_adapter.remove_nio(port_number)
|
||||
|
||||
log.info('TraceNG "{name}" [{id}]: {nio} removed from port {port_number}'.format(name=self._name,
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
'TraceNG "{name}" [{id}]: {nio} removed from port {port_number}'.format(
|
||||
name=self._name, id=self.id, nio=nio, port_number=port_number
|
||||
)
|
||||
)
|
||||
return nio
|
||||
|
||||
def get_nio(self, port_number):
|
||||
@ -372,8 +388,11 @@ class TraceNGVM(BaseNode):
|
||||
"""
|
||||
|
||||
if not self._ethernet_adapter.port_exists(port_number):
|
||||
raise TraceNGError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter,
|
||||
port_number=port_number))
|
||||
raise TraceNGError(
|
||||
"Port {port_number} doesn't exist on adapter {adapter}".format(
|
||||
adapter=self._ethernet_adapter, port_number=port_number
|
||||
)
|
||||
)
|
||||
nio = self._ethernet_adapter.get_nio(port_number)
|
||||
if not nio:
|
||||
raise TraceNGError(f"Port {port_number} is not connected")
|
||||
@ -393,12 +412,17 @@ class TraceNGVM(BaseNode):
|
||||
|
||||
nio.start_packet_capture(output_file)
|
||||
if self.ubridge:
|
||||
await self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(name=f"TraceNG-{self._id}",
|
||||
output_file=output_file))
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {name} "{output_file}"'.format(
|
||||
name=f"TraceNG-{self._id}", output_file=output_file
|
||||
)
|
||||
)
|
||||
|
||||
log.info("TraceNG '{name}' [{id}]: starting packet capture on port {port_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
"TraceNG '{name}' [{id}]: starting packet capture on port {port_number}".format(
|
||||
name=self.name, id=self.id, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, port_number):
|
||||
"""
|
||||
@ -413,11 +437,13 @@ class TraceNGVM(BaseNode):
|
||||
|
||||
nio.stop_packet_capture()
|
||||
if self.ubridge:
|
||||
await self._ubridge_send('bridge stop_capture {name}'.format(name=f"TraceNG-{self._id}"))
|
||||
await self._ubridge_send("bridge stop_capture {name}".format(name=f"TraceNG-{self._id}"))
|
||||
|
||||
log.info("TraceNG '{name}' [{id}]: stopping packet capture on port {port_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
port_number=port_number))
|
||||
log.info(
|
||||
"TraceNG '{name}' [{id}]: stopping packet capture on port {port_number}".format(
|
||||
name=self.name, id=self.id, port_number=port_number
|
||||
)
|
||||
)
|
||||
|
||||
def _build_command(self, destination):
|
||||
"""
|
||||
@ -447,7 +473,9 @@ class TraceNGVM(BaseNode):
|
||||
command.extend(["-c", str(nio.lport)]) # source UDP port
|
||||
command.extend(["-v", str(nio.rport)]) # destination UDP port
|
||||
try:
|
||||
command.extend(["-b", socket.gethostbyname(nio.rhost)]) # destination host, we need to resolve the hostname because TraceNG doesn't support it
|
||||
command.extend(
|
||||
["-b", socket.gethostbyname(nio.rhost)]
|
||||
) # destination host, we need to resolve the hostname because TraceNG doesn't support it
|
||||
except socket.gaierror as e:
|
||||
raise TraceNGError(f"Can't resolve hostname {nio.rhost}: {e}")
|
||||
|
||||
|
@ -33,6 +33,7 @@ from .ubridge_hypervisor import UBridgeHypervisor
|
||||
from .ubridge_error import UbridgeError
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -141,7 +142,7 @@ class Hypervisor(UBridgeHypervisor):
|
||||
if sys.platform.startswith("win") or sys.platform.startswith("darwin"):
|
||||
minimum_required_version = "0.9.12"
|
||||
else:
|
||||
# uBridge version 0.9.14 is required for packet filters
|
||||
# uBridge version 0.9.14 is required for packet filters
|
||||
# to work for IOU nodes.
|
||||
minimum_required_version = "0.9.14"
|
||||
if parse_version(self._version) < parse_version(minimum_required_version):
|
||||
@ -161,7 +162,7 @@ class Hypervisor(UBridgeHypervisor):
|
||||
# add the Npcap directory to $PATH to force uBridge to use npcap DLL instead of Winpcap (if installed)
|
||||
system_root = os.path.join(os.path.expandvars("%SystemRoot%"), "System32", "Npcap")
|
||||
if os.path.isdir(system_root):
|
||||
env["PATH"] = system_root + ';' + env["PATH"]
|
||||
env["PATH"] = system_root + ";" + env["PATH"]
|
||||
await self._check_ubridge_version(env)
|
||||
try:
|
||||
command = self._build_command()
|
||||
@ -169,16 +170,14 @@ class Hypervisor(UBridgeHypervisor):
|
||||
self._stdout_file = os.path.join(self._working_dir, "ubridge.log")
|
||||
log.info(f"logging to {self._stdout_file}")
|
||||
with open(self._stdout_file, "w", encoding="utf-8") as fd:
|
||||
self._process = await asyncio.create_subprocess_exec(*command,
|
||||
stdout=fd,
|
||||
stderr=subprocess.STDOUT,
|
||||
cwd=self._working_dir,
|
||||
env=env)
|
||||
self._process = await asyncio.create_subprocess_exec(
|
||||
*command, stdout=fd, stderr=subprocess.STDOUT, cwd=self._working_dir, env=env
|
||||
)
|
||||
|
||||
log.info(f"ubridge started PID={self._process.pid}")
|
||||
# recv: Bad address is received by uBridge when a docker image stops by itself
|
||||
# see https://github.com/GNS3/gns3-gui/issues/2957
|
||||
#monitor_process(self._process, self._termination_callback)
|
||||
# monitor_process(self._process, self._termination_callback)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
ubridge_stdout = self.read_stdout()
|
||||
log.error(f"Could not start ubridge: {e}\n{ubridge_stdout}")
|
||||
|
@ -20,7 +20,6 @@ Custom exceptions for the ubridge.
|
||||
|
||||
|
||||
class UbridgeError(Exception):
|
||||
|
||||
def __init__(self, message):
|
||||
Exception.__init__(self, message)
|
||||
|
||||
@ -29,4 +28,5 @@ class UbridgeNamespaceError(Exception):
|
||||
"""
|
||||
Raised if ubridge can not move a container to a namespace
|
||||
"""
|
||||
|
||||
pass
|
||||
|
@ -199,17 +199,20 @@ class UBridgeHypervisor:
|
||||
raise UbridgeError("Not connected")
|
||||
|
||||
try:
|
||||
command = command.strip() + '\n'
|
||||
command = command.strip() + "\n"
|
||||
log.debug(f"sending {command}")
|
||||
self._writer.write(command.encode())
|
||||
await self._writer.drain()
|
||||
except OSError as e:
|
||||
raise UbridgeError("Lost communication with {host}:{port} when sending command '{command}': {error}, uBridge process running: {run}"
|
||||
.format(host=self._host, port=self._port, command=command, error=e, run=self.is_running()))
|
||||
raise UbridgeError(
|
||||
"Lost communication with {host}:{port} when sending command '{command}': {error}, uBridge process running: {run}".format(
|
||||
host=self._host, port=self._port, command=command, error=e, run=self.is_running()
|
||||
)
|
||||
)
|
||||
|
||||
# Now retrieve the result
|
||||
data = []
|
||||
buf = ''
|
||||
buf = ""
|
||||
retries = 0
|
||||
max_retries = 10
|
||||
while True:
|
||||
@ -228,8 +231,11 @@ class UBridgeHypervisor:
|
||||
continue
|
||||
if not chunk:
|
||||
if retries > max_retries:
|
||||
raise UbridgeError("No data returned from {host}:{port} after sending command '{command}', uBridge process running: {run}"
|
||||
.format(host=self._host, port=self._port, command=command, run=self.is_running()))
|
||||
raise UbridgeError(
|
||||
"No data returned from {host}:{port} after sending command '{command}', uBridge process running: {run}".format(
|
||||
host=self._host, port=self._port, command=command, run=self.is_running()
|
||||
)
|
||||
)
|
||||
else:
|
||||
retries += 1
|
||||
await asyncio.sleep(0.5)
|
||||
@ -237,30 +243,36 @@ class UBridgeHypervisor:
|
||||
retries = 0
|
||||
buf += chunk.decode("utf-8")
|
||||
except OSError as e:
|
||||
raise UbridgeError("Lost communication with {host}:{port} after sending command '{command}': {error}, uBridge process running: {run}"
|
||||
.format(host=self._host, port=self._port, command=command, error=e, run=self.is_running()))
|
||||
raise UbridgeError(
|
||||
"Lost communication with {host}:{port} after sending command '{command}': {error}, uBridge process running: {run}".format(
|
||||
host=self._host, port=self._port, command=command, error=e, run=self.is_running()
|
||||
)
|
||||
)
|
||||
|
||||
# If the buffer doesn't end in '\n' then we can't be done
|
||||
try:
|
||||
if buf[-1] != '\n':
|
||||
if buf[-1] != "\n":
|
||||
continue
|
||||
except IndexError:
|
||||
raise UbridgeError("Could not communicate with {host}:{port} after sending command '{command}', uBridge process running: {run}"
|
||||
.format(host=self._host, port=self._port, command=command, run=self.is_running()))
|
||||
raise UbridgeError(
|
||||
"Could not communicate with {host}:{port} after sending command '{command}', uBridge process running: {run}".format(
|
||||
host=self._host, port=self._port, command=command, run=self.is_running()
|
||||
)
|
||||
)
|
||||
|
||||
data += buf.split('\r\n')
|
||||
if data[-1] == '':
|
||||
data += buf.split("\r\n")
|
||||
if data[-1] == "":
|
||||
data.pop()
|
||||
buf = ''
|
||||
buf = ""
|
||||
|
||||
# Does it contain an error code?
|
||||
if self.error_re.search(data[-1]):
|
||||
raise UbridgeError(data[-1][4:])
|
||||
|
||||
# Or does the last line begin with '100-'? Then we are done!
|
||||
if data[-1][:4] == '100-':
|
||||
if data[-1][:4] == "100-":
|
||||
data[-1] = data[-1][4:]
|
||||
if data[-1] == 'OK':
|
||||
if data[-1] == "OK":
|
||||
data.pop()
|
||||
break
|
||||
|
||||
|
@ -110,7 +110,9 @@ class VirtualBox(BaseManager):
|
||||
command_string = " ".join(command)
|
||||
log.info(f"Executing VBoxManage with command: {command_string}")
|
||||
try:
|
||||
process = await asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
raise VirtualBoxError(f"Could not execute VBoxManage: {e}")
|
||||
|
||||
@ -140,7 +142,7 @@ class VirtualBox(BaseManager):
|
||||
flag_inaccessible = False
|
||||
for prop in properties:
|
||||
try:
|
||||
name, value = prop.split(':', 1)
|
||||
name, value = prop.split(":", 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if name.strip() == "State" and value.strip() == "inaccessible":
|
||||
@ -191,7 +193,7 @@ class VirtualBox(BaseManager):
|
||||
ram = 0
|
||||
for info in info_results:
|
||||
try:
|
||||
name, value = info.split('=', 1)
|
||||
name, value = info.split("=", 1)
|
||||
if name.strip() == "memory":
|
||||
ram = int(value.strip())
|
||||
break
|
||||
|
@ -38,11 +38,12 @@ from gns3server.compute.nios.nio_udp import NIOUDP
|
||||
from gns3server.compute.adapters.ethernet_adapter import EthernetAdapter
|
||||
from gns3server.compute.base_node import BaseNode
|
||||
|
||||
if sys.platform.startswith('win'):
|
||||
if sys.platform.startswith("win"):
|
||||
import msvcrt
|
||||
import win32file
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -52,9 +53,22 @@ class VirtualBoxVM(BaseNode):
|
||||
VirtualBox VM implementation.
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, vmname, linked_clone=False, console=None, console_type="telnet", adapters=0):
|
||||
def __init__(
|
||||
self,
|
||||
name,
|
||||
node_id,
|
||||
project,
|
||||
manager,
|
||||
vmname,
|
||||
linked_clone=False,
|
||||
console=None,
|
||||
console_type="telnet",
|
||||
adapters=0,
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, console=console, linked_clone=linked_clone, console_type=console_type)
|
||||
super().__init__(
|
||||
name, node_id, project, manager, console=console, linked_clone=linked_clone, console_type=console_type
|
||||
)
|
||||
|
||||
self._uuid = None # UUID in VirtualBox
|
||||
self._maximum_adapters = 8
|
||||
@ -74,21 +88,23 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
json = {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"console": self.console,
|
||||
"console_type": self.console_type,
|
||||
"project_id": self.project.id,
|
||||
"vmname": self.vmname,
|
||||
"headless": self.headless,
|
||||
"on_close": self.on_close,
|
||||
"adapters": self._adapters,
|
||||
"adapter_type": self.adapter_type,
|
||||
"ram": self.ram,
|
||||
"status": self.status,
|
||||
"use_any_adapter": self.use_any_adapter,
|
||||
"linked_clone": self.linked_clone}
|
||||
json = {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"console": self.console,
|
||||
"console_type": self.console_type,
|
||||
"project_id": self.project.id,
|
||||
"vmname": self.vmname,
|
||||
"headless": self.headless,
|
||||
"on_close": self.on_close,
|
||||
"adapters": self._adapters,
|
||||
"adapter_type": self.adapter_type,
|
||||
"ram": self.ram,
|
||||
"status": self.status,
|
||||
"use_any_adapter": self.use_any_adapter,
|
||||
"linked_clone": self.linked_clone,
|
||||
}
|
||||
if self.linked_clone:
|
||||
json["node_directory"] = self.working_path
|
||||
else:
|
||||
@ -104,7 +120,7 @@ class VirtualBoxVM(BaseNode):
|
||||
properties = await self.manager.execute("list", ["systemproperties"])
|
||||
for prop in properties:
|
||||
try:
|
||||
name, value = prop.split(':', 1)
|
||||
name, value = prop.split(":", 1)
|
||||
except ValueError:
|
||||
continue
|
||||
self._system_properties[name.strip()] = value.strip()
|
||||
@ -118,8 +134,8 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
results = await self.manager.execute("showvminfo", [self._uuid, "--machinereadable"])
|
||||
for info in results:
|
||||
if '=' in info:
|
||||
name, value = info.split('=', 1)
|
||||
if "=" in info:
|
||||
name, value = info.split("=", 1)
|
||||
if name == "VMState":
|
||||
return value.strip('"')
|
||||
raise VirtualBoxError(f"Could not get VM state for {self._vmname}")
|
||||
@ -164,10 +180,14 @@ class VirtualBoxVM(BaseNode):
|
||||
found = True
|
||||
if node.project != self.project:
|
||||
if trial >= 30:
|
||||
raise VirtualBoxError(f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmname} is already used by {node.name} in project {self.project.name}")
|
||||
raise VirtualBoxError(
|
||||
f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmname} is already used by {node.name} in project {self.project.name}"
|
||||
)
|
||||
else:
|
||||
if trial >= 5:
|
||||
raise VirtualBoxError(f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmname} is already used by {node.name} in this project")
|
||||
raise VirtualBoxError(
|
||||
f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmname} is already used by {node.name} in this project"
|
||||
)
|
||||
if not found:
|
||||
return
|
||||
trial += 1
|
||||
@ -221,8 +241,10 @@ class VirtualBoxVM(BaseNode):
|
||||
try:
|
||||
tree = ET.parse(self._linked_vbox_file())
|
||||
except ET.ParseError:
|
||||
raise VirtualBoxError("Cannot modify VirtualBox linked nodes file. "
|
||||
"File {} is corrupted.".format(self._linked_vbox_file()))
|
||||
raise VirtualBoxError(
|
||||
"Cannot modify VirtualBox linked nodes file. "
|
||||
"File {} is corrupted.".format(self._linked_vbox_file())
|
||||
)
|
||||
except OSError as e:
|
||||
raise VirtualBoxError(f"Cannot modify VirtualBox linked nodes file '{self._linked_vbox_file()}': {e}")
|
||||
|
||||
@ -233,8 +255,10 @@ class VirtualBoxVM(BaseNode):
|
||||
currentSnapshot = machine.get("currentSnapshot")
|
||||
if currentSnapshot:
|
||||
newSnapshot = re.sub(r"\{.*\}", "{" + str(uuid.uuid4()) + "}", currentSnapshot)
|
||||
shutil.move(os.path.join(self.working_dir, self._vmname, "Snapshots", currentSnapshot) + ".vdi",
|
||||
os.path.join(self.working_dir, self._vmname, "Snapshots", newSnapshot) + ".vdi")
|
||||
shutil.move(
|
||||
os.path.join(self.working_dir, self._vmname, "Snapshots", currentSnapshot) + ".vdi",
|
||||
os.path.join(self.working_dir, self._vmname, "Snapshots", newSnapshot) + ".vdi",
|
||||
)
|
||||
image.set("uuid", newSnapshot)
|
||||
|
||||
machine.set("uuid", "{" + self.id + "}")
|
||||
@ -270,7 +294,7 @@ class VirtualBoxVM(BaseNode):
|
||||
# VM must be powered off to start it
|
||||
if vm_state == "saved":
|
||||
result = await self.manager.execute("guestproperty", ["get", self._uuid, "SavedByGNS3"])
|
||||
if result == ['No value set!']:
|
||||
if result == ["No value set!"]:
|
||||
raise VirtualBoxError("VirtualBox VM was not saved from GNS3")
|
||||
else:
|
||||
await self.manager.execute("guestproperty", ["delete", self._uuid, "SavedByGNS3"])
|
||||
@ -300,13 +324,13 @@ class VirtualBoxVM(BaseNode):
|
||||
for adapter_number in range(0, self._adapters):
|
||||
nio = self._ethernet_adapters[adapter_number].get_nio(0)
|
||||
if nio:
|
||||
await self.add_ubridge_udp_connection(f"VBOX-{self._id}-{adapter_number}",
|
||||
self._local_udp_tunnels[adapter_number][1],
|
||||
nio)
|
||||
await self.add_ubridge_udp_connection(
|
||||
f"VBOX-{self._id}-{adapter_number}", self._local_udp_tunnels[adapter_number][1], nio
|
||||
)
|
||||
|
||||
await self._start_console()
|
||||
|
||||
if (await self.check_hw_virtualization()):
|
||||
if await self.check_hw_virtualization():
|
||||
self._hw_virtualization = True
|
||||
|
||||
@locking
|
||||
@ -381,9 +405,11 @@ class VirtualBoxVM(BaseNode):
|
||||
self.status = "suspended"
|
||||
log.info(f"VirtualBox VM '{self.name}' [{self.id}] suspended")
|
||||
else:
|
||||
log.warning("VirtualBox VM '{name}' [{id}] cannot be suspended, current state: {state}".format(name=self.name,
|
||||
id=self.id,
|
||||
state=vm_state))
|
||||
log.warning(
|
||||
"VirtualBox VM '{name}' [{id}] cannot be suspended, current state: {state}".format(
|
||||
name=self.name, id=self.id, state=vm_state
|
||||
)
|
||||
)
|
||||
|
||||
async def resume(self):
|
||||
"""
|
||||
@ -409,7 +435,7 @@ class VirtualBoxVM(BaseNode):
|
||||
properties = await self.manager.execute("list", ["hdds"])
|
||||
for prop in properties:
|
||||
try:
|
||||
name, value = prop.split(':', 1)
|
||||
name, value = prop.split(":", 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if name.strip() == "Location":
|
||||
@ -432,27 +458,36 @@ class VirtualBoxVM(BaseNode):
|
||||
for hdd_info in hdd_table:
|
||||
hdd_file = os.path.join(self.working_dir, self._vmname, "Snapshots", hdd_info["hdd"])
|
||||
if os.path.exists(hdd_file):
|
||||
log.info("VirtualBox VM '{name}' [{id}] attaching HDD {controller} {port} {device} {medium}".format(name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd_info["controller"],
|
||||
port=hdd_info["port"],
|
||||
device=hdd_info["device"],
|
||||
medium=hdd_file))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}] attaching HDD {controller} {port} {device} {medium}".format(
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd_info["controller"],
|
||||
port=hdd_info["port"],
|
||||
device=hdd_info["device"],
|
||||
medium=hdd_file,
|
||||
)
|
||||
)
|
||||
|
||||
try:
|
||||
await self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium "{}"'.format(hdd_info["controller"],
|
||||
hdd_info["port"],
|
||||
hdd_info["device"],
|
||||
hdd_file))
|
||||
await self._storage_attach(
|
||||
'--storagectl "{}" --port {} --device {} --type hdd --medium "{}"'.format(
|
||||
hdd_info["controller"], hdd_info["port"], hdd_info["device"], hdd_file
|
||||
)
|
||||
)
|
||||
|
||||
except VirtualBoxError as e:
|
||||
log.warning("VirtualBox VM '{name}' [{id}] error reattaching HDD {controller} {port} {device} {medium}: {error}".format(name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd_info["controller"],
|
||||
port=hdd_info["port"],
|
||||
device=hdd_info["device"],
|
||||
medium=hdd_file,
|
||||
error=e))
|
||||
log.warning(
|
||||
"VirtualBox VM '{name}' [{id}] error reattaching HDD {controller} {port} {device} {medium}: {error}".format(
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd_info["controller"],
|
||||
port=hdd_info["port"],
|
||||
device=hdd_info["device"],
|
||||
medium=hdd_file,
|
||||
error=e,
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
async def save_linked_hdds_info(self):
|
||||
@ -468,17 +503,21 @@ class VirtualBoxVM(BaseNode):
|
||||
hdd_files = await self._get_all_hdd_files()
|
||||
vm_info = await self._get_vm_info()
|
||||
for entry, value in vm_info.items():
|
||||
match = re.search(r"^([\s\w]+)\-(\d)\-(\d)$", entry) # match Controller-PortNumber-DeviceNumber entry
|
||||
match = re.search(
|
||||
r"^([\s\w]+)\-(\d)\-(\d)$", entry
|
||||
) # match Controller-PortNumber-DeviceNumber entry
|
||||
if match:
|
||||
controller = match.group(1)
|
||||
port = match.group(2)
|
||||
device = match.group(3)
|
||||
if value in hdd_files and os.path.exists(os.path.join(self.working_dir, self._vmname, "Snapshots", os.path.basename(value))):
|
||||
log.info("VirtualBox VM '{name}' [{id}] detaching HDD {controller} {port} {device}".format(name=self.name,
|
||||
id=self.id,
|
||||
controller=controller,
|
||||
port=port,
|
||||
device=device))
|
||||
if value in hdd_files and os.path.exists(
|
||||
os.path.join(self.working_dir, self._vmname, "Snapshots", os.path.basename(value))
|
||||
):
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}] detaching HDD {controller} {port} {device}".format(
|
||||
name=self.name, id=self.id, controller=controller, port=port, device=device
|
||||
)
|
||||
)
|
||||
hdd_table.append(
|
||||
{
|
||||
"hdd": os.path.basename(value),
|
||||
@ -494,9 +533,11 @@ class VirtualBoxVM(BaseNode):
|
||||
with open(hdd_info_file, "w", encoding="utf-8") as f:
|
||||
json.dump(hdd_table, f, indent=4)
|
||||
except OSError as e:
|
||||
log.warning("VirtualBox VM '{name}' [{id}] could not write HHD info file: {error}".format(name=self.name,
|
||||
id=self.id,
|
||||
error=e.strerror))
|
||||
log.warning(
|
||||
"VirtualBox VM '{name}' [{id}] could not write HHD info file: {error}".format(
|
||||
name=self.name, id=self.id, error=e.strerror
|
||||
)
|
||||
)
|
||||
|
||||
return hdd_table
|
||||
|
||||
@ -534,22 +575,28 @@ class VirtualBoxVM(BaseNode):
|
||||
if self.linked_clone:
|
||||
hdd_table = await self.save_linked_hdds_info()
|
||||
for hdd in hdd_table.copy():
|
||||
log.info("VirtualBox VM '{name}' [{id}] detaching HDD {controller} {port} {device}".format(name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd["controller"],
|
||||
port=hdd["port"],
|
||||
device=hdd["device"]))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}] detaching HDD {controller} {port} {device}".format(
|
||||
name=self.name, id=self.id, controller=hdd["controller"], port=hdd["port"], device=hdd["device"]
|
||||
)
|
||||
)
|
||||
try:
|
||||
await self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium none'.format(hdd["controller"],
|
||||
hdd["port"],
|
||||
hdd["device"]))
|
||||
await self._storage_attach(
|
||||
'--storagectl "{}" --port {} --device {} --type hdd --medium none'.format(
|
||||
hdd["controller"], hdd["port"], hdd["device"]
|
||||
)
|
||||
)
|
||||
except VirtualBoxError as e:
|
||||
log.warning("VirtualBox VM '{name}' [{id}] error detaching HDD {controller} {port} {device}: {error}".format(name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd["controller"],
|
||||
port=hdd["port"],
|
||||
device=hdd["device"],
|
||||
error=e))
|
||||
log.warning(
|
||||
"VirtualBox VM '{name}' [{id}] error detaching HDD {controller} {port} {device}: {error}".format(
|
||||
name=self.name,
|
||||
id=self.id,
|
||||
controller=hdd["controller"],
|
||||
port=hdd["port"],
|
||||
device=hdd["device"],
|
||||
error=e,
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
log.info(f"VirtualBox VM '{self.name}' [{self.id}] unregistering")
|
||||
@ -623,7 +670,7 @@ class VirtualBoxVM(BaseNode):
|
||||
if ram == 0:
|
||||
return
|
||||
|
||||
await self._modify_vm(f'--memory {ram}')
|
||||
await self._modify_vm(f"--memory {ram}")
|
||||
|
||||
log.info(f"VirtualBox VM '{self.name}' [{self.id}] has set amount of RAM to {ram}")
|
||||
self._ram = ram
|
||||
@ -688,26 +735,34 @@ class VirtualBoxVM(BaseNode):
|
||||
try:
|
||||
self._maximum_adapters = int(self._system_properties[max_adapter_string])
|
||||
except ValueError:
|
||||
log.error(f"Could not convert system property to integer: {max_adapter_string} = {self._system_properties[max_adapter_string]}")
|
||||
log.error(
|
||||
f"Could not convert system property to integer: {max_adapter_string} = {self._system_properties[max_adapter_string]}"
|
||||
)
|
||||
else:
|
||||
log.warning(f"Could not find system property '{max_adapter_string}' for chipset {chipset}")
|
||||
|
||||
log.info("VirtualBox VM '{name}' [{id}] can have a maximum of {max} network adapters for chipset {chipset}".format(name=self.name,
|
||||
id=self.id,
|
||||
max=self._maximum_adapters,
|
||||
chipset=chipset.upper()))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}] can have a maximum of {max} network adapters for chipset {chipset}".format(
|
||||
name=self.name, id=self.id, max=self._maximum_adapters, chipset=chipset.upper()
|
||||
)
|
||||
)
|
||||
if adapters > self._maximum_adapters:
|
||||
raise VirtualBoxError("The configured {} chipset limits the VM to {} network adapters. The chipset can be changed outside GNS3 in the VirtualBox VM settings.".format(chipset.upper(),
|
||||
self._maximum_adapters))
|
||||
raise VirtualBoxError(
|
||||
"The configured {} chipset limits the VM to {} network adapters. The chipset can be changed outside GNS3 in the VirtualBox VM settings.".format(
|
||||
chipset.upper(), self._maximum_adapters
|
||||
)
|
||||
)
|
||||
|
||||
self._ethernet_adapters.clear()
|
||||
for adapter_number in range(0, adapters):
|
||||
self._ethernet_adapters[adapter_number] = EthernetAdapter()
|
||||
|
||||
self._adapters = len(self._ethernet_adapters)
|
||||
log.info("VirtualBox VM '{name}' [{id}] has changed the number of Ethernet adapters to {adapters}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapters=adapters))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}] has changed the number of Ethernet adapters to {adapters}".format(
|
||||
name=self.name, id=self.id, adapters=adapters
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def use_any_adapter(self):
|
||||
@ -752,9 +807,11 @@ class VirtualBoxVM(BaseNode):
|
||||
"""
|
||||
|
||||
self._adapter_type = adapter_type
|
||||
log.info("VirtualBox VM '{name}' [{id}]: adapter type changed to {adapter_type}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_type=adapter_type))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}]: adapter type changed to {adapter_type}".format(
|
||||
name=self.name, id=self.id, adapter_type=adapter_type
|
||||
)
|
||||
)
|
||||
|
||||
async def _get_vm_info(self):
|
||||
"""
|
||||
@ -764,10 +821,12 @@ class VirtualBoxVM(BaseNode):
|
||||
"""
|
||||
|
||||
vm_info = {}
|
||||
results = await self.manager.execute("showvminfo", ["--machinereadable", "--", self._vmname]) # "--" is to protect against vm names containing the "-" character
|
||||
results = await self.manager.execute(
|
||||
"showvminfo", ["--machinereadable", "--", self._vmname]
|
||||
) # "--" is to protect against vm names containing the "-" character
|
||||
for info in results:
|
||||
try:
|
||||
name, value = info.split('=', 1)
|
||||
name, value = info.split("=", 1)
|
||||
except ValueError:
|
||||
continue
|
||||
vm_info[name.strip('"')] = value.strip('"')
|
||||
@ -916,16 +975,18 @@ class VirtualBoxVM(BaseNode):
|
||||
result = await self.manager.execute("snapshot", [self._uuid, "take", "GNS3 Linked Base for clones"])
|
||||
log.debug(f"GNS3 snapshot created: {result}")
|
||||
|
||||
args = [self._uuid,
|
||||
"--snapshot",
|
||||
"GNS3 Linked Base for clones",
|
||||
"--options",
|
||||
"link",
|
||||
"--name",
|
||||
self.name,
|
||||
"--basefolder",
|
||||
self.working_dir,
|
||||
"--register"]
|
||||
args = [
|
||||
self._uuid,
|
||||
"--snapshot",
|
||||
"GNS3 Linked Base for clones",
|
||||
"--options",
|
||||
"link",
|
||||
"--name",
|
||||
self.name,
|
||||
"--basefolder",
|
||||
self.working_dir,
|
||||
"--register",
|
||||
]
|
||||
|
||||
result = await self.manager.execute("clonevm", args)
|
||||
log.debug(f"VirtualBox VM: {result} cloned")
|
||||
@ -959,14 +1020,18 @@ class VirtualBoxVM(BaseNode):
|
||||
self._remote_pipe = await asyncio_open_serial(pipe_name)
|
||||
except OSError as e:
|
||||
raise VirtualBoxError(f"Could not open serial pipe '{pipe_name}': {e}")
|
||||
server = AsyncioTelnetServer(reader=self._remote_pipe,
|
||||
writer=self._remote_pipe,
|
||||
binary=True,
|
||||
echo=True)
|
||||
server = AsyncioTelnetServer(reader=self._remote_pipe, writer=self._remote_pipe, binary=True, echo=True)
|
||||
try:
|
||||
self._telnet_server = await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)
|
||||
self._telnet_server = await asyncio.start_server(
|
||||
server.run, self._manager.port_manager.console_host, self.console
|
||||
)
|
||||
except OSError as e:
|
||||
self.project.emit("log.warning", {"message": f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.console}: {e}"})
|
||||
self.project.emit(
|
||||
"log.warning",
|
||||
{
|
||||
"message": f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.console}: {e}"
|
||||
},
|
||||
)
|
||||
|
||||
async def _stop_remote_console(self):
|
||||
"""
|
||||
@ -1010,18 +1075,23 @@ class VirtualBoxVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VirtualBoxError(
|
||||
"Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
# check if trying to connect to a nat, bridged, host-only or any other special adapter
|
||||
nic_attachments = await self._get_nic_attachements(self._maximum_adapters)
|
||||
attachment = nic_attachments[adapter_number]
|
||||
if attachment in ("nat", "bridged", "intnet", "hostonly", "natnetwork"):
|
||||
if not self._use_any_adapter:
|
||||
raise VirtualBoxError("Attachment '{attachment}' is already configured on adapter {adapter_number}. "
|
||||
"Please remove it or allow VirtualBox VM '{name}' to use any adapter.".format(attachment=attachment,
|
||||
adapter_number=adapter_number,
|
||||
name=self.name))
|
||||
raise VirtualBoxError(
|
||||
"Attachment '{attachment}' is already configured on adapter {adapter_number}. "
|
||||
"Please remove it or allow VirtualBox VM '{name}' to use any adapter.".format(
|
||||
attachment=attachment, adapter_number=adapter_number, name=self.name
|
||||
)
|
||||
)
|
||||
elif self.is_running():
|
||||
# dynamically configure an UDP tunnel attachment if the VM is already running
|
||||
local_nio = self._local_udp_tunnels[adapter_number][0]
|
||||
@ -1034,19 +1104,23 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
if self.is_running():
|
||||
try:
|
||||
await self.add_ubridge_udp_connection(f"VBOX-{self._id}-{adapter_number}",
|
||||
self._local_udp_tunnels[adapter_number][1],
|
||||
nio)
|
||||
await self.add_ubridge_udp_connection(
|
||||
f"VBOX-{self._id}-{adapter_number}", self._local_udp_tunnels[adapter_number][1], nio
|
||||
)
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VirtualBoxError(
|
||||
"Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
await self._control_vm(f"setlinkstate{adapter_number + 1} on")
|
||||
|
||||
adapter.add_nio(0, nio)
|
||||
log.info("VirtualBox VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, nio=nio, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
@ -1058,16 +1132,19 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
if self.is_running():
|
||||
try:
|
||||
await self.update_ubridge_udp_connection(f"VBOX-{self._id}-{adapter_number}",
|
||||
self._local_udp_tunnels[adapter_number][1],
|
||||
nio)
|
||||
await self.update_ubridge_udp_connection(
|
||||
f"VBOX-{self._id}-{adapter_number}", self._local_udp_tunnels[adapter_number][1], nio
|
||||
)
|
||||
if nio.suspend:
|
||||
await self._control_vm(f"setlinkstate{adapter_number + 1} off")
|
||||
else:
|
||||
await self._control_vm(f"setlinkstate{adapter_number + 1} on")
|
||||
except IndexError:
|
||||
raise VirtualBoxError('Adapter {adapter_number} does not exist on VirtualBox VM "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
raise VirtualBoxError(
|
||||
'Adapter {adapter_number} does not exist on VirtualBox VM "{name}"'.format(
|
||||
name=self._name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def adapter_remove_nio_binding(self, adapter_number):
|
||||
"""
|
||||
@ -1081,8 +1158,11 @@ class VirtualBoxVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VirtualBoxError(
|
||||
"Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
await self.stop_capture(adapter_number)
|
||||
if self.is_running():
|
||||
@ -1096,10 +1176,11 @@ class VirtualBoxVM(BaseNode):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
adapter.remove_nio(0)
|
||||
|
||||
log.info("VirtualBox VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, nio=nio, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
return nio
|
||||
|
||||
def get_nio(self, adapter_number):
|
||||
@ -1114,8 +1195,11 @@ class VirtualBoxVM(BaseNode):
|
||||
try:
|
||||
adapter = self.ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VirtualBoxError("Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VirtualBoxError(
|
||||
"Adapter {adapter_number} doesn't exist on VirtualBox VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
|
||||
@ -1144,12 +1228,17 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
nio.start_packet_capture(output_file)
|
||||
if self.ubridge:
|
||||
await self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(name=f"VBOX-{self._id}-{adapter_number}",
|
||||
output_file=output_file))
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {name} "{output_file}"'.format(
|
||||
name=f"VBOX-{self._id}-{adapter_number}", output_file=output_file
|
||||
)
|
||||
)
|
||||
|
||||
log.info("VirtualBox VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, adapter_number):
|
||||
"""
|
||||
@ -1164,8 +1253,10 @@ class VirtualBoxVM(BaseNode):
|
||||
|
||||
nio.stop_packet_capture()
|
||||
if self.ubridge:
|
||||
await self._ubridge_send('bridge stop_capture {name}'.format(name=f"VBOX-{self._id}-{adapter_number}"))
|
||||
await self._ubridge_send("bridge stop_capture {name}".format(name=f"VBOX-{self._id}-{adapter_number}"))
|
||||
|
||||
log.info("VirtualBox VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VirtualBox VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
@ -70,6 +70,7 @@ class VMware(BaseManager):
|
||||
def _find_vmrun_registry(regkey):
|
||||
|
||||
import winreg
|
||||
|
||||
try:
|
||||
# default path not used, let's look in the registry
|
||||
hkey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, regkey)
|
||||
@ -124,6 +125,7 @@ class VMware(BaseManager):
|
||||
def _find_vmware_version_registry(regkey):
|
||||
|
||||
import winreg
|
||||
|
||||
version = None
|
||||
try:
|
||||
# default path not used, let's look in the registry
|
||||
@ -210,7 +212,9 @@ class VMware(BaseManager):
|
||||
else:
|
||||
if sys.platform.startswith("darwin"):
|
||||
if not os.path.isdir("/Applications/VMware Fusion.app"):
|
||||
raise VMwareError("VMware Fusion is not installed in the standard location /Applications/VMware Fusion.app")
|
||||
raise VMwareError(
|
||||
"VMware Fusion is not installed in the standard location /Applications/VMware Fusion.app"
|
||||
)
|
||||
self._host_type = "fusion"
|
||||
return # FIXME: no version checking on Mac OS X but we support all versions of fusion
|
||||
|
||||
@ -244,6 +248,7 @@ class VMware(BaseManager):
|
||||
def _get_vmnet_interfaces_registry():
|
||||
|
||||
import winreg
|
||||
|
||||
vmnet_interfaces = []
|
||||
regkey = r"SOFTWARE\Wow6432Node\VMware, Inc.\VMnetLib\VMnetConfig"
|
||||
try:
|
||||
@ -320,7 +325,9 @@ class VMware(BaseManager):
|
||||
def allocate_vmnet(self):
|
||||
|
||||
if not self._vmnets:
|
||||
raise VMwareError(f"No VMnet interface available between vmnet{self._vmnet_start_range} and vmnet{self._vmnet_end_range}. Go to preferences VMware / Network / Configure to add more interfaces.")
|
||||
raise VMwareError(
|
||||
f"No VMnet interface available between vmnet{self._vmnet_start_range} and vmnet{self._vmnet_end_range}. Go to preferences VMware / Network / Configure to add more interfaces."
|
||||
)
|
||||
return self._vmnets.pop(0)
|
||||
|
||||
def refresh_vmnet_list(self, ubridge=True):
|
||||
@ -363,12 +370,12 @@ class VMware(BaseManager):
|
||||
|
||||
while True:
|
||||
try:
|
||||
return (await self._execute(subcommand, args, timeout=timeout, log_level=log_level))
|
||||
return await self._execute(subcommand, args, timeout=timeout, log_level=log_level)
|
||||
except VMwareError as e:
|
||||
# We can fail to detect that it's VMware player instead of Workstation (due to marketing change Player is now Player Workstation)
|
||||
if self.host_type == "ws" and "VIX_SERVICEPROVIDER_VMWARE_WORKSTATION" in str(e):
|
||||
self._host_type = "player"
|
||||
return (await self._execute(subcommand, args, timeout=timeout, log_level=log_level))
|
||||
return await self._execute(subcommand, args, timeout=timeout, log_level=log_level)
|
||||
else:
|
||||
if trial <= 0:
|
||||
raise e
|
||||
@ -388,19 +395,25 @@ class VMware(BaseManager):
|
||||
command_string = " ".join([shlex_quote(c) for c in command])
|
||||
log.log(log_level, f"Executing vmrun with command: {command_string}")
|
||||
try:
|
||||
process = await asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
||||
process = await asyncio.create_subprocess_exec(
|
||||
*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
|
||||
)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
raise VMwareError(f"Could not execute vmrun: {e}")
|
||||
|
||||
try:
|
||||
stdout_data, _ = await asyncio.wait_for(process.communicate(), timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
raise VMwareError(f"vmrun has timed out after {timeout} seconds!\nTry to run {command_string} in a terminal to see more details.\n\nMake sure GNS3 and VMware run under the same user and whitelist vmrun.exe in your antivirus.")
|
||||
raise VMwareError(
|
||||
f"vmrun has timed out after {timeout} seconds!\nTry to run {command_string} in a terminal to see more details.\n\nMake sure GNS3 and VMware run under the same user and whitelist vmrun.exe in your antivirus."
|
||||
)
|
||||
|
||||
if process.returncode:
|
||||
# vmrun print errors on stdout
|
||||
vmrun_error = stdout_data.decode("utf-8", errors="ignore")
|
||||
raise VMwareError(f"vmrun has returned an error: {vmrun_error}\nTry to run {command_string} in a terminal to see more details.\nAnd make sure GNS3 and VMware run under the same user.")
|
||||
raise VMwareError(
|
||||
f"vmrun has returned an error: {vmrun_error}\nTry to run {command_string} in a terminal to see more details.\nAnd make sure GNS3 and VMware run under the same user."
|
||||
)
|
||||
|
||||
return stdout_data.decode("utf-8", errors="ignore").splitlines()
|
||||
|
||||
@ -487,7 +500,7 @@ class VMware(BaseManager):
|
||||
# skip the shebang
|
||||
line = f.readline().decode(encoding, errors="ignore")
|
||||
try:
|
||||
key, value = line.split('=', 1)
|
||||
key, value = line.split("=", 1)
|
||||
if key.strip().lower() == ".encoding":
|
||||
file_encoding = value.strip('" ')
|
||||
try:
|
||||
@ -502,7 +515,7 @@ class VMware(BaseManager):
|
||||
with open(path, encoding=encoding, errors="ignore") as f:
|
||||
for line in f.read().splitlines():
|
||||
try:
|
||||
key, value = line.split('=', 1)
|
||||
key, value = line.split("=", 1)
|
||||
pairs[key.strip().lower()] = value.strip('" ')
|
||||
except ValueError:
|
||||
continue
|
||||
@ -574,7 +587,7 @@ class VMware(BaseManager):
|
||||
for key, value in pairs.items():
|
||||
if key.startswith("vmlist"):
|
||||
try:
|
||||
vm_entry, variable_name = key.split('.', 1)
|
||||
vm_entry, variable_name = key.split(".", 1)
|
||||
except ValueError:
|
||||
continue
|
||||
if vm_entry not in vm_entries:
|
||||
@ -586,7 +599,11 @@ class VMware(BaseManager):
|
||||
for vm_settings in vm_entries.values():
|
||||
if "displayname" in vm_settings and "config" in vm_settings:
|
||||
if os.path.exists(vm_settings["config"]):
|
||||
log.debug('Found VM named "{}" with VMX file "{}"'.format(vm_settings["displayname"], vm_settings["config"]))
|
||||
log.debug(
|
||||
'Found VM named "{}" with VMX file "{}"'.format(
|
||||
vm_settings["displayname"], vm_settings["config"]
|
||||
)
|
||||
)
|
||||
vmware_vms.append({"vmname": vm_settings["displayname"], "vmx_path": vm_settings["config"]})
|
||||
return vmware_vms
|
||||
|
||||
@ -657,10 +674,11 @@ class VMware(BaseManager):
|
||||
if sys.platform.startswith("win"):
|
||||
import ctypes
|
||||
import ctypes.wintypes
|
||||
|
||||
path = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
|
||||
ctypes.windll.shell32.SHGetFolderPathW(None, 5, None, 0, path)
|
||||
documents_folder = path.value
|
||||
return [fr'{documents_folder}\My Virtual Machines', fr'{documents_folder}\Virtual Machines']
|
||||
return [fr"{documents_folder}\My Virtual Machines", fr"{documents_folder}\Virtual Machines"]
|
||||
elif sys.platform.startswith("darwin"):
|
||||
return [os.path.expanduser("~/Documents/Virtual Machines.localized")]
|
||||
else:
|
||||
@ -691,8 +709,11 @@ class VMware(BaseManager):
|
||||
if "prefvmx.defaultvmpath" in pairs:
|
||||
default_vm_path = pairs["prefvmx.defaultvmpath"]
|
||||
if not os.path.isdir(default_vm_path):
|
||||
raise VMwareError('Could not find or access the default VM directory: "{default_vm_path}". Please change "prefvmx.defaultvmpath={default_vm_path}" in "{vmware_preferences_path}"'.format(default_vm_path=default_vm_path,
|
||||
vmware_preferences_path=vmware_preferences_path))
|
||||
raise VMwareError(
|
||||
'Could not find or access the default VM directory: "{default_vm_path}". Please change "prefvmx.defaultvmpath={default_vm_path}" in "{vmware_preferences_path}"'.format(
|
||||
default_vm_path=default_vm_path, vmware_preferences_path=vmware_preferences_path
|
||||
)
|
||||
)
|
||||
vmware_vms = self._get_vms_from_directory(default_vm_path)
|
||||
|
||||
if not vmware_vms:
|
||||
@ -707,7 +728,7 @@ class VMware(BaseManager):
|
||||
|
||||
# look for VMX paths in the preferences file in case not all VMs are in a default directory
|
||||
for key, value in pairs.items():
|
||||
m = re.match(r'pref.mruVM(\d+)\.filename', key)
|
||||
m = re.match(r"pref.mruVM(\d+)\.filename", key)
|
||||
if m:
|
||||
display_name = f"pref.mruVM{m.group(1)}.displayName"
|
||||
if display_name in pairs:
|
||||
@ -730,7 +751,7 @@ class VMware(BaseManager):
|
||||
return path
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
loop = asyncio.get_event_loop()
|
||||
vmware = VMware.instance()
|
||||
print("=> Check version")
|
||||
|
@ -34,6 +34,7 @@ from ..base_node import BaseNode
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -43,9 +44,13 @@ class VMwareVM(BaseNode):
|
||||
VMware VM implementation.
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, vmx_path, linked_clone=False, console=None, console_type="telnet"):
|
||||
def __init__(
|
||||
self, name, node_id, project, manager, vmx_path, linked_clone=False, console=None, console_type="telnet"
|
||||
):
|
||||
|
||||
super().__init__(name, node_id, project, manager, console=console, console_type=console_type, linked_clone=linked_clone)
|
||||
super().__init__(
|
||||
name, node_id, project, manager, console=console, console_type=console_type, linked_clone=linked_clone
|
||||
)
|
||||
|
||||
self._vmx_pairs = OrderedDict()
|
||||
self._telnet_server = None
|
||||
@ -72,21 +77,23 @@ class VMwareVM(BaseNode):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
json = {"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"console": self.console,
|
||||
"console_type": self.console_type,
|
||||
"project_id": self.project.id,
|
||||
"vmx_path": self.vmx_path,
|
||||
"headless": self.headless,
|
||||
"on_close": self.on_close,
|
||||
"adapters": self._adapters,
|
||||
"adapter_type": self.adapter_type,
|
||||
"use_any_adapter": self.use_any_adapter,
|
||||
"status": self.status,
|
||||
"node_directory": self.working_path,
|
||||
"linked_clone": self.linked_clone}
|
||||
json = {
|
||||
"name": self.name,
|
||||
"usage": self.usage,
|
||||
"node_id": self.id,
|
||||
"console": self.console,
|
||||
"console_type": self.console_type,
|
||||
"project_id": self.project.id,
|
||||
"vmx_path": self.vmx_path,
|
||||
"headless": self.headless,
|
||||
"on_close": self.on_close,
|
||||
"adapters": self._adapters,
|
||||
"adapter_type": self.adapter_type,
|
||||
"use_any_adapter": self.use_any_adapter,
|
||||
"status": self.status,
|
||||
"node_directory": self.working_path,
|
||||
"linked_clone": self.linked_clone,
|
||||
}
|
||||
return json
|
||||
|
||||
@property
|
||||
@ -147,10 +154,14 @@ class VMwareVM(BaseNode):
|
||||
found = True
|
||||
if node.project != self.project:
|
||||
if trial >= 30:
|
||||
raise VMwareError(f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmx_path} is already used by {node.name} in project {self.project.name}")
|
||||
raise VMwareError(
|
||||
f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmx_path} is already used by {node.name} in project {self.project.name}"
|
||||
)
|
||||
else:
|
||||
if trial >= 5:
|
||||
raise VMwareError(f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmx_path} is already used by {node.name} in this project")
|
||||
raise VMwareError(
|
||||
f"Sorry a node without the linked clone setting enabled can only be used once on your server.\n{self.vmx_path} is already used by {node.name} in this project"
|
||||
)
|
||||
if not found:
|
||||
return
|
||||
trial += 1
|
||||
@ -187,11 +198,9 @@ class VMwareVM(BaseNode):
|
||||
|
||||
# create the linked clone based on the base snapshot
|
||||
new_vmx_path = os.path.join(self.working_dir, self.name + ".vmx")
|
||||
await self._control_vm("clone",
|
||||
new_vmx_path,
|
||||
"linked",
|
||||
f"-snapshot={base_snapshot_name}",
|
||||
f"-cloneName={self.name}")
|
||||
await self._control_vm(
|
||||
"clone", new_vmx_path, "linked", f"-snapshot={base_snapshot_name}", f"-cloneName={self.name}"
|
||||
)
|
||||
|
||||
try:
|
||||
vmsd_pairs = self.manager.parse_vmware_file(vmsd_path)
|
||||
@ -265,14 +274,20 @@ class VMwareVM(BaseNode):
|
||||
vmware_adapter_type = "e1000"
|
||||
else:
|
||||
vmware_adapter_type = adapter_type
|
||||
ethernet_adapter = {f"ethernet{adapter_number}.present": "TRUE",
|
||||
f"ethernet{adapter_number}.addresstype": "generated",
|
||||
f"ethernet{adapter_number}.generatedaddressoffset": "0",
|
||||
f"ethernet{adapter_number}.virtualdev": vmware_adapter_type}
|
||||
ethernet_adapter = {
|
||||
f"ethernet{adapter_number}.present": "TRUE",
|
||||
f"ethernet{adapter_number}.addresstype": "generated",
|
||||
f"ethernet{adapter_number}.generatedaddressoffset": "0",
|
||||
f"ethernet{adapter_number}.virtualdev": vmware_adapter_type,
|
||||
}
|
||||
self._vmx_pairs.update(ethernet_adapter)
|
||||
|
||||
connection_type = f"ethernet{adapter_number}.connectiontype"
|
||||
if not self._use_any_adapter and connection_type in self._vmx_pairs and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly"):
|
||||
if (
|
||||
not self._use_any_adapter
|
||||
and connection_type in self._vmx_pairs
|
||||
and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly")
|
||||
):
|
||||
continue
|
||||
|
||||
self._vmx_pairs[f"ethernet{adapter_number}.connectiontype"] = "custom"
|
||||
@ -339,15 +354,16 @@ class VMwareVM(BaseNode):
|
||||
await self._add_ubridge_ethernet_connection(vnet, vmnet_interface, block_host_traffic)
|
||||
|
||||
if isinstance(nio, NIOUDP):
|
||||
await self._ubridge_send('bridge add_nio_udp {name} {lport} {rhost} {rport}'.format(name=vnet,
|
||||
lport=nio.lport,
|
||||
rhost=nio.rhost,
|
||||
rport=nio.rport))
|
||||
await self._ubridge_send(
|
||||
"bridge add_nio_udp {name} {lport} {rhost} {rport}".format(
|
||||
name=vnet, lport=nio.lport, rhost=nio.rhost, rport=nio.rport
|
||||
)
|
||||
)
|
||||
|
||||
if nio.capturing:
|
||||
await self._ubridge_send(f'bridge start_capture {vnet} "{nio.pcap_output_file}"')
|
||||
|
||||
await self._ubridge_send(f'bridge start {vnet}')
|
||||
await self._ubridge_send(f"bridge start {vnet}")
|
||||
await self._ubridge_apply_filters(vnet, nio.filters)
|
||||
|
||||
async def _update_ubridge_connection(self, adapter_number, nio):
|
||||
@ -388,8 +404,9 @@ class VMwareVM(BaseNode):
|
||||
raise VMwareError(f"vnet {vnet} not in VMX file")
|
||||
if not self._ubridge_hypervisor:
|
||||
raise VMwareError("Cannot start the packet capture: uBridge is not running")
|
||||
await self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(name=vnet,
|
||||
output_file=output_file))
|
||||
await self._ubridge_send(
|
||||
'bridge start_capture {name} "{output_file}"'.format(name=vnet, output_file=output_file)
|
||||
)
|
||||
|
||||
async def _stop_ubridge_capture(self, adapter_number):
|
||||
"""
|
||||
@ -425,7 +442,7 @@ class VMwareVM(BaseNode):
|
||||
if self.status == "started":
|
||||
return
|
||||
|
||||
if (await self.is_running()):
|
||||
if await self.is_running():
|
||||
raise VMwareError("The VM is already running in VMware")
|
||||
|
||||
ubridge_path = self.ubridge_path
|
||||
@ -475,7 +492,7 @@ class VMwareVM(BaseNode):
|
||||
await self._stop_ubridge()
|
||||
|
||||
try:
|
||||
if (await self.is_running()):
|
||||
if await self.is_running():
|
||||
if self.on_close == "save_vm_state":
|
||||
await self._control_vm("suspend")
|
||||
elif self.on_close == "shutdown_signal":
|
||||
@ -492,7 +509,10 @@ class VMwareVM(BaseNode):
|
||||
# remove the adapters managed by GNS3
|
||||
for adapter_number in range(0, self._adapters):
|
||||
vnet = f"ethernet{adapter_number}.vnet"
|
||||
if self._get_vmx_setting(vnet) or self._get_vmx_setting(f"ethernet{adapter_number}.connectiontype") is None:
|
||||
if (
|
||||
self._get_vmx_setting(vnet)
|
||||
or self._get_vmx_setting(f"ethernet{adapter_number}.connectiontype") is None
|
||||
):
|
||||
if vnet in self._vmx_pairs:
|
||||
vmnet = os.path.basename(self._vmx_pairs[vnet])
|
||||
if not self.manager.is_managed_vmnet(vmnet):
|
||||
@ -656,9 +676,11 @@ class VMwareVM(BaseNode):
|
||||
self._ethernet_adapters[adapter_number] = EthernetAdapter()
|
||||
|
||||
self._adapters = len(self._ethernet_adapters)
|
||||
log.info("VMware VM '{name}' [{id}] has changed the number of Ethernet adapters to {adapters}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapters=adapters))
|
||||
log.info(
|
||||
"VMware VM '{name}' [{id}] has changed the number of Ethernet adapters to {adapters}".format(
|
||||
name=self.name, id=self.id, adapters=adapters
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def adapter_type(self):
|
||||
@ -679,9 +701,11 @@ class VMwareVM(BaseNode):
|
||||
"""
|
||||
|
||||
self._adapter_type = adapter_type
|
||||
log.info("VMware VM '{name}' [{id}]: adapter type changed to {adapter_type}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_type=adapter_type))
|
||||
log.info(
|
||||
"VMware VM '{name}' [{id}]: adapter type changed to {adapter_type}".format(
|
||||
name=self.name, id=self.id, adapter_type=adapter_type
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def use_any_adapter(self):
|
||||
@ -718,35 +742,46 @@ class VMwareVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VMwareError(
|
||||
"Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
self._read_vmx_file()
|
||||
# check if trying to connect to a nat, bridged or host-only adapter
|
||||
if self._get_vmx_setting(f"ethernet{adapter_number}.present", "TRUE"):
|
||||
# check for the connection type
|
||||
connection_type = f"ethernet{adapter_number}.connectiontype"
|
||||
if not self._use_any_adapter and connection_type in self._vmx_pairs and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly"):
|
||||
if (await self.is_running()):
|
||||
raise VMwareError("Attachment '{attachment}' is configured on network adapter {adapter_number}. "
|
||||
"Please stop VMware VM '{name}' to link to this adapter and allow GNS3 to change the attachment type.".format(attachment=self._vmx_pairs[connection_type],
|
||||
adapter_number=adapter_number,
|
||||
name=self.name))
|
||||
if (
|
||||
not self._use_any_adapter
|
||||
and connection_type in self._vmx_pairs
|
||||
and self._vmx_pairs[connection_type] in ("nat", "bridged", "hostonly")
|
||||
):
|
||||
if await self.is_running():
|
||||
raise VMwareError(
|
||||
"Attachment '{attachment}' is configured on network adapter {adapter_number}. "
|
||||
"Please stop VMware VM '{name}' to link to this adapter and allow GNS3 to change the attachment type.".format(
|
||||
attachment=self._vmx_pairs[connection_type], adapter_number=adapter_number, name=self.name
|
||||
)
|
||||
)
|
||||
else:
|
||||
raise VMwareError("Attachment '{attachment}' is already configured on network adapter {adapter_number}. "
|
||||
"Please remove it or allow VMware VM '{name}' to use any adapter.".format(attachment=self._vmx_pairs[connection_type],
|
||||
adapter_number=adapter_number,
|
||||
name=self.name))
|
||||
|
||||
raise VMwareError(
|
||||
"Attachment '{attachment}' is already configured on network adapter {adapter_number}. "
|
||||
"Please remove it or allow VMware VM '{name}' to use any adapter.".format(
|
||||
attachment=self._vmx_pairs[connection_type], adapter_number=adapter_number, name=self.name
|
||||
)
|
||||
)
|
||||
|
||||
adapter.add_nio(0, nio)
|
||||
if self._started and self._ubridge_hypervisor:
|
||||
await self._add_ubridge_connection(nio, adapter_number)
|
||||
|
||||
log.info("VMware VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VMware VM '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, nio=nio, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def adapter_update_nio_binding(self, adapter_number, nio):
|
||||
"""
|
||||
@ -760,8 +795,11 @@ class VMwareVM(BaseNode):
|
||||
try:
|
||||
await self._update_ubridge_connection(adapter_number, nio)
|
||||
except IndexError:
|
||||
raise VMwareError('Adapter {adapter_number} does not exist on VMware VM "{name}"'.format(name=self._name,
|
||||
adapter_number=adapter_number))
|
||||
raise VMwareError(
|
||||
'Adapter {adapter_number} does not exist on VMware VM "{name}"'.format(
|
||||
name=self._name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def adapter_remove_nio_binding(self, adapter_number):
|
||||
"""
|
||||
@ -775,8 +813,11 @@ class VMwareVM(BaseNode):
|
||||
try:
|
||||
adapter = self._ethernet_adapters[adapter_number]
|
||||
except IndexError:
|
||||
raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VMwareError(
|
||||
"Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
await self.stop_capture(adapter_number)
|
||||
nio = adapter.get_nio(0)
|
||||
@ -786,10 +827,11 @@ class VMwareVM(BaseNode):
|
||||
if self._started and self._ubridge_hypervisor:
|
||||
await self._delete_ubridge_connection(adapter_number)
|
||||
|
||||
log.info("VMware VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
nio=nio,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VMware VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, nio=nio, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
return nio
|
||||
|
||||
@ -805,8 +847,11 @@ class VMwareVM(BaseNode):
|
||||
try:
|
||||
adapter = self.ethernet_adapters[adapter_number]
|
||||
except KeyError:
|
||||
raise VMwareError("Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(name=self.name,
|
||||
adapter_number=adapter_number))
|
||||
raise VMwareError(
|
||||
"Adapter {adapter_number} doesn't exist on VMware VM '{name}'".format(
|
||||
name=self.name, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
nio = adapter.get_nio(0)
|
||||
if not nio:
|
||||
@ -837,11 +882,13 @@ class VMwareVM(BaseNode):
|
||||
"""
|
||||
|
||||
pipe_name = self._get_pipe_name()
|
||||
serial_port = {"serial0.present": "TRUE",
|
||||
"serial0.filetype": "pipe",
|
||||
"serial0.filename": pipe_name,
|
||||
"serial0.pipe.endpoint": "server",
|
||||
"serial0.startconnected": "TRUE"}
|
||||
serial_port = {
|
||||
"serial0.present": "TRUE",
|
||||
"serial0.filetype": "pipe",
|
||||
"serial0.filename": pipe_name,
|
||||
"serial0.pipe.endpoint": "server",
|
||||
"serial0.startconnected": "TRUE",
|
||||
}
|
||||
self._vmx_pairs.update(serial_port)
|
||||
|
||||
async def _start_console(self):
|
||||
@ -855,14 +902,18 @@ class VMwareVM(BaseNode):
|
||||
self._remote_pipe = await asyncio_open_serial(self._get_pipe_name())
|
||||
except OSError as e:
|
||||
raise VMwareError(f"Could not open serial pipe '{pipe_name}': {e}")
|
||||
server = AsyncioTelnetServer(reader=self._remote_pipe,
|
||||
writer=self._remote_pipe,
|
||||
binary=True,
|
||||
echo=True)
|
||||
server = AsyncioTelnetServer(reader=self._remote_pipe, writer=self._remote_pipe, binary=True, echo=True)
|
||||
try:
|
||||
self._telnet_server = await asyncio.start_server(server.run, self._manager.port_manager.console_host, self.console)
|
||||
self._telnet_server = await asyncio.start_server(
|
||||
server.run, self._manager.port_manager.console_host, self.console
|
||||
)
|
||||
except OSError as e:
|
||||
self.project.emit("log.warning", {"message": f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.console}: {e}"})
|
||||
self.project.emit(
|
||||
"log.warning",
|
||||
{
|
||||
"message": f"Could not start Telnet server on socket {self._manager.port_manager.console_host}:{self.console}: {e}"
|
||||
},
|
||||
)
|
||||
|
||||
async def _stop_remote_console(self):
|
||||
"""
|
||||
@ -911,9 +962,11 @@ class VMwareVM(BaseNode):
|
||||
if self._started:
|
||||
await self._start_ubridge_capture(adapter_number, output_file)
|
||||
|
||||
log.info("VMware VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VMware VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
||||
async def stop_capture(self, adapter_number):
|
||||
"""
|
||||
@ -930,6 +983,8 @@ class VMwareVM(BaseNode):
|
||||
if self._started:
|
||||
await self._stop_ubridge_capture(adapter_number)
|
||||
|
||||
log.info("VMware VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(name=self.name,
|
||||
id=self.id,
|
||||
adapter_number=adapter_number))
|
||||
log.info(
|
||||
"VMware VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(
|
||||
name=self.name, id=self.id, adapter_number=adapter_number
|
||||
)
|
||||
)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user