1
0
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:
grossmj 2021-04-13 18:46:50 +09:30
parent f928738bd5
commit c021e21309
194 changed files with 6034 additions and 4564 deletions

View File

@ -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

View File

@ -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"])

View File

@ -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.

View File

@ -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,
}

View File

@ -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.

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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()

View File

@ -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()

View File

@ -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.

View File

@ -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

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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",

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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():

View File

@ -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)

View File

@ -25,6 +25,7 @@ from websockets.exceptions import ConnectionClosed, WebSocketException
from gns3server.controller import Controller
import logging
log = logging.getLogger(__name__)
router = APIRouter()

View File

@ -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:

View File

@ -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.

View File

@ -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.

View 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__()

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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)

View File

@ -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)

View File

@ -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})

View File

@ -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

View File

@ -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:

View File

@ -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
)
)

View File

@ -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):
"""

View File

@ -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):
"""

View File

@ -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,
}

View File

@ -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)

View File

@ -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"])

View File

@ -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):
"""

View File

@ -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

View File

@ -18,7 +18,6 @@ from .adapter import Adapter
class GT96100_FE(Adapter):
def __init__(self):
super().__init__(interfaces=2, wics=3)

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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}

View File

@ -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}

View File

@ -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):

View File

@ -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}

View File

@ -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}

View File

@ -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}

View 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}

View 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
)
)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
)
)

View File

@ -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
)
)

View File

@ -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

View File

@ -16,7 +16,6 @@
class NodeError(Exception):
def __init__(self, message, original_exception=None):
super().__init__(message)
if isinstance(message, Exception):

View File

@ -26,6 +26,7 @@ from .iou_error import IOUError
from .iou_vm import IOUVM
import logging
log = logging.getLogger(__name__)

View File

@ -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
)
)

View File

@ -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:

View File

@ -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:

View File

@ -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}

View File

@ -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}

View File

@ -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}

View File

@ -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

View File

@ -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")

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -17,6 +17,7 @@
from ..qemu_error import QemuError
import logging
log = logging.getLogger(__name__)

View File

@ -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)

View File

@ -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))

View File

@ -40,4 +40,4 @@ class TraceNG(BaseManager):
:returns: TraceNGVM instance
"""
return (await super().create_node(*args, **kwargs))
return await super().create_node(*args, **kwargs)

View File

@ -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}")

View File

@ -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}")

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
)
)

View File

@ -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")

View File

@ -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