From c021e21309a374caf40fcfb0470494ae985470df Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 13 Apr 2021 18:46:50 +0930 Subject: [PATCH] Use black with -l 120 param. --- README.rst | 3 + gns3server/api/routes/compute/__init__.py | 36 +- .../api/routes/compute/atm_switch_nodes.py | 79 +- gns3server/api/routes/compute/capabilities.py | 18 +- gns3server/api/routes/compute/cloud_nodes.py | 95 +-- gns3server/api/routes/compute/compute.py | 56 +- gns3server/api/routes/compute/docker_nodes.py | 149 ++-- .../api/routes/compute/dynamips_nodes.py | 114 ++- .../api/routes/compute/ethernet_hub_nodes.py | 83 +-- .../routes/compute/ethernet_switch_nodes.py | 92 ++- .../compute/frame_relay_switch_nodes.py | 91 ++- gns3server/api/routes/compute/images.py | 9 +- gns3server/api/routes/compute/iou_nodes.py | 109 ++- gns3server/api/routes/compute/nat_nodes.py | 95 +-- .../api/routes/compute/notifications.py | 3 +- gns3server/api/routes/compute/projects.py | 34 +- gns3server/api/routes/compute/qemu_nodes.py | 123 ++-- .../api/routes/compute/virtualbox_nodes.py | 109 ++- gns3server/api/routes/compute/vmware_nodes.py | 108 ++- gns3server/api/routes/compute/vpcs_nodes.py | 105 ++- .../api/routes/controller/appliances.py | 3 +- gns3server/api/routes/controller/computes.py | 58 +- .../api/routes/controller/controller.py | 30 +- .../controller/dependencies/authentication.py | 7 +- .../controller/dependencies/database.py | 2 +- gns3server/api/routes/controller/drawings.py | 23 +- gns3server/api/routes/controller/links.py | 64 +- gns3server/api/routes/controller/nodes.py | 113 ++- .../api/routes/controller/notifications.py | 1 + gns3server/api/routes/controller/projects.py | 128 ++-- gns3server/api/routes/controller/snapshots.py | 20 +- gns3server/api/routes/controller/symbols.py | 17 +- gns3server/api/routes/controller/templates.py | 78 +- gns3server/api/routes/controller/users.py | 33 +- gns3server/api/routes/index.py | 20 +- gns3server/api/server.py | 21 +- gns3server/compute/__init__.py | 8 +- gns3server/compute/base_manager.py | 14 +- gns3server/compute/base_node.py | 235 +++--- gns3server/compute/builtin/__init__.py | 7 +- .../compute/builtin/builtin_node_factory.py | 6 +- gns3server/compute/builtin/nodes/cloud.py | 171 +++-- .../compute/builtin/nodes/ethernet_hub.py | 6 +- .../compute/builtin/nodes/ethernet_switch.py | 6 +- gns3server/compute/builtin/nodes/nat.py | 26 +- gns3server/compute/compute_error.py | 5 - gns3server/compute/docker/__init__.py | 48 +- gns3server/compute/docker/docker_vm.py | 416 +++++++---- gns3server/compute/dynamips/__init__.py | 83 ++- .../compute/dynamips/adapters/gt96100_fe.py | 1 - .../compute/dynamips/dynamips_factory.py | 27 +- .../compute/dynamips/dynamips_hypervisor.py | 48 +- gns3server/compute/dynamips/hypervisor.py | 13 +- gns3server/compute/dynamips/nios/nio.py | 22 +- .../dynamips/nios/nio_generic_ethernet.py | 18 +- .../dynamips/nios/nio_linux_ethernet.py | 18 +- gns3server/compute/dynamips/nios/nio_null.py | 3 +- gns3server/compute/dynamips/nios/nio_tap.py | 6 +- gns3server/compute/dynamips/nios/nio_udp.py | 62 +- gns3server/compute/dynamips/nios/nio_unix.py | 23 +- gns3server/compute/dynamips/nios/nio_vde.py | 23 +- .../compute/dynamips/nodes/atm_switch.py | 265 ++++--- gns3server/compute/dynamips/nodes/bridge.py | 5 +- gns3server/compute/dynamips/nodes/c1700.py | 40 +- gns3server/compute/dynamips/nodes/c2600.py | 60 +- gns3server/compute/dynamips/nodes/c2691.py | 28 +- gns3server/compute/dynamips/nodes/c3600.py | 37 +- gns3server/compute/dynamips/nodes/c3725.py | 28 +- gns3server/compute/dynamips/nodes/c3745.py | 28 +- gns3server/compute/dynamips/nodes/c7200.py | 89 ++- .../compute/dynamips/nodes/ethernet_hub.py | 54 +- .../compute/dynamips/nodes/ethernet_switch.py | 159 ++-- .../dynamips/nodes/frame_relay_switch.py | 140 ++-- gns3server/compute/dynamips/nodes/router.py | 693 +++++++++++------- gns3server/compute/error.py | 1 - gns3server/compute/iou/__init__.py | 1 + gns3server/compute/iou/iou_vm.py | 384 ++++++---- gns3server/compute/iou/utils/iou_export.py | 71 +- gns3server/compute/iou/utils/iou_import.py | 119 +-- gns3server/compute/nios/nio_ethernet.py | 3 +- gns3server/compute/nios/nio_tap.py | 3 +- gns3server/compute/nios/nio_udp.py | 5 +- gns3server/compute/notification_manager.py | 2 +- gns3server/compute/port_manager.py | 137 +++- gns3server/compute/project.py | 15 +- gns3server/compute/project_manager.py | 8 +- gns3server/compute/qemu/__init__.py | 33 +- gns3server/compute/qemu/qemu_vm.py | 611 ++++++++++----- gns3server/compute/qemu/utils/guest_cid.py | 1 + gns3server/compute/qemu/utils/qcow2.py | 8 +- gns3server/compute/qemu/utils/ziputils.py | 6 +- gns3server/compute/traceng/__init__.py | 2 +- gns3server/compute/traceng/traceng_vm.py | 130 ++-- gns3server/compute/ubridge/hypervisor.py | 15 +- gns3server/compute/ubridge/ubridge_error.py | 2 +- .../compute/ubridge/ubridge_hypervisor.py | 44 +- gns3server/compute/virtualbox/__init__.py | 8 +- .../compute/virtualbox/virtualbox_vm.py | 383 ++++++---- gns3server/compute/vmware/__init__.py | 53 +- gns3server/compute/vmware/vmware_vm.py | 233 +++--- gns3server/compute/vpcs/vpcs_vm.py | 123 ++-- gns3server/config.py | 27 +- gns3server/controller/__init__.py | 59 +- gns3server/controller/appliance.py | 4 +- gns3server/controller/appliance_manager.py | 49 +- gns3server/controller/compute.py | 98 ++- gns3server/controller/controller_error.py | 6 - gns3server/controller/drawing.py | 13 +- gns3server/controller/export_project.py | 78 +- gns3server/controller/gns3vm/__init__.py | 68 +- gns3server/controller/gns3vm/base_gns3_vm.py | 6 +- gns3server/controller/gns3vm/gns3_vm_error.py | 1 - .../controller/gns3vm/hyperv_gns3_vm.py | 53 +- .../controller/gns3vm/remote_gns3_vm.py | 2 +- .../controller/gns3vm/virtualbox_gns3_vm.py | 82 ++- .../controller/gns3vm/vmware_gns3_vm.py | 23 +- gns3server/controller/import_project.py | 31 +- gns3server/controller/link.py | 163 ++-- gns3server/controller/node.py | 133 +++- gns3server/controller/notification.py | 10 +- gns3server/controller/ports/atm_port.py | 1 - .../controller/ports/fastethernet_port.py | 1 - .../controller/ports/frame_relay_port.py | 1 - .../controller/ports/gigabitethernet_port.py | 1 - gns3server/controller/ports/port.py | 4 +- gns3server/controller/ports/port_factory.py | 124 ++-- gns3server/controller/ports/pos_port.py | 1 - gns3server/controller/ports/serial_port.py | 5 +- gns3server/controller/project.py | 146 ++-- gns3server/controller/snapshot.py | 24 +- gns3server/controller/symbol_themes.py | 226 +++--- gns3server/controller/symbols.py | 29 +- gns3server/controller/topology.py | 127 ++-- gns3server/controller/udp_link.py | 71 +- gns3server/core/tasks.py | 4 +- gns3server/crash_report.py | 29 +- gns3server/db/models/__init__.py | 2 +- gns3server/db/models/base.py | 12 +- gns3server/db/models/templates.py | 50 +- gns3server/db/repositories/base.py | 1 - gns3server/db/repositories/computes.py | 7 +- gns3server/db/repositories/templates.py | 12 +- gns3server/db/repositories/users.py | 10 +- gns3server/db/tasks.py | 6 +- gns3server/logger.py | 31 +- gns3server/main.py | 6 +- gns3server/schemas/__init__.py | 2 +- gns3server/schemas/cloud_nodes.py | 4 +- gns3server/schemas/computes.py | 16 +- gns3server/schemas/config.py | 6 +- gns3server/schemas/docker_nodes.py | 4 +- gns3server/schemas/docker_templates.py | 17 +- gns3server/schemas/dynamips_nodes.py | 6 +- gns3server/schemas/dynamips_templates.py | 10 +- gns3server/schemas/ethernet_hub_templates.py | 2 +- .../schemas/ethernet_switch_templates.py | 2 +- gns3server/schemas/iou_templates.py | 5 +- gns3server/schemas/links.py | 12 +- gns3server/schemas/nat_nodes.py | 4 +- gns3server/schemas/nios.py | 1 - gns3server/schemas/nodes.py | 17 +- gns3server/schemas/qemu_nodes.py | 14 +- gns3server/schemas/qemu_templates.py | 27 +- gns3server/schemas/topology.py | 7 +- gns3server/schemas/virtualbox_nodes.py | 10 +- gns3server/schemas/virtualbox_templates.py | 32 +- gns3server/schemas/vmware_templates.py | 24 +- gns3server/schemas/vpcs_templates.py | 4 +- gns3server/server.py | 58 +- gns3server/services/__init__.py | 1 + gns3server/services/authentication.py | 9 +- gns3server/services/computes.py | 15 +- gns3server/services/templates.py | 21 +- gns3server/utils/__init__.py | 10 +- gns3server/utils/application_id.py | 5 +- gns3server/utils/asyncio/__init__.py | 5 +- gns3server/utils/asyncio/aiozipstream.py | 189 +++-- gns3server/utils/asyncio/embed_shell.py | 63 +- gns3server/utils/asyncio/input_stream.py | 283 ++++--- gns3server/utils/asyncio/pool.py | 4 +- .../utils/asyncio/raw_command_server.py | 49 +- gns3server/utils/asyncio/serial.py | 5 +- gns3server/utils/asyncio/telnet_server.py | 151 ++-- gns3server/utils/cpu_percent.py | 7 +- gns3server/utils/file_watcher.py | 10 +- gns3server/utils/get_resource.py | 1 + gns3server/utils/http_client.py | 1 + gns3server/utils/images.py | 35 +- gns3server/utils/interfaces.py | 92 ++- gns3server/utils/notification_queue.py | 5 +- gns3server/utils/picture.py | 29 +- gns3server/utils/vmnet.py | 21 +- gns3server/utils/windows_loopback.py | 14 +- gns3server/version.py | 1 + 194 files changed, 6034 insertions(+), 4564 deletions(-) diff --git a/README.rst b/README.rst index 4988f54a..81b11da3 100644 --- a/README.rst +++ b/README.rst @@ -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 diff --git a/gns3server/api/routes/compute/__init__.py b/gns3server/api/routes/compute/__init__.py index c9734739..b60dfb7c 100644 --- a/gns3server/api/routes/compute/__init__.py +++ b/gns3server/api/routes/compute/__init__.py @@ -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"]) diff --git a/gns3server/api/routes/compute/atm_switch_nodes.py b/gns3server/api/routes/compute/atm_switch_nodes.py index 5847ca90..23a45ef1 100644 --- a/gns3server/api/routes/compute/atm_switch_nodes.py +++ b/gns3server/api/routes/compute/atm_switch_nodes.py @@ -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. diff --git a/gns3server/api/routes/compute/capabilities.py b/gns3server/api/routes/compute/capabilities.py index 4acbf39b..0931aa5c 100644 --- a/gns3server/api/routes/compute/capabilities.py +++ b/gns3server/api/routes/compute/capabilities.py @@ -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, + } diff --git a/gns3server/api/routes/compute/cloud_nodes.py b/gns3server/api/routes/compute/cloud_nodes.py index bf7977fe..0c710a2b 100644 --- a/gns3server/api/routes/compute/cloud_nodes.py +++ b/gns3server/api/routes/compute/cloud_nodes.py @@ -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. diff --git a/gns3server/api/routes/compute/compute.py b/gns3server/api/routes/compute/compute.py index 2fae74f6..81d8e8ec 100644 --- a/gns3server/api/routes/compute/compute.py +++ b/gns3server/api/routes/compute/compute.py @@ -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() - diff --git a/gns3server/api/routes/compute/docker_nodes.py b/gns3server/api/routes/compute/docker_nodes.py index e23a7e47..8663d82b 100644 --- a/gns3server/api/routes/compute/docker_nodes.py +++ b/gns3server/api/routes/compute/docker_nodes.py @@ -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() diff --git a/gns3server/api/routes/compute/dynamips_nodes.py b/gns3server/api/routes/compute/dynamips_nodes.py index 98e14c20..63e0bde9 100644 --- a/gns3server/api/routes/compute/dynamips_nodes.py +++ b/gns3server/api/routes/compute/dynamips_nodes.py @@ -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() diff --git a/gns3server/api/routes/compute/ethernet_hub_nodes.py b/gns3server/api/routes/compute/ethernet_hub_nodes.py index 3bb00d94..316bf229 100644 --- a/gns3server/api/routes/compute/ethernet_hub_nodes.py +++ b/gns3server/api/routes/compute/ethernet_hub_nodes.py @@ -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. diff --git a/gns3server/api/routes/compute/ethernet_switch_nodes.py b/gns3server/api/routes/compute/ethernet_switch_nodes.py index 20719ebc..277dfcbb 100644 --- a/gns3server/api/routes/compute/ethernet_switch_nodes.py +++ b/gns3server/api/routes/compute/ethernet_switch_nodes.py @@ -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. diff --git a/gns3server/api/routes/compute/frame_relay_switch_nodes.py b/gns3server/api/routes/compute/frame_relay_switch_nodes.py index 2a5d2997..14dd490b 100644 --- a/gns3server/api/routes/compute/frame_relay_switch_nodes.py +++ b/gns3server/api/routes/compute/frame_relay_switch_nodes.py @@ -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. diff --git a/gns3server/api/routes/compute/images.py b/gns3server/api/routes/compute/images.py index cdb1fb9b..6f5fe522 100644 --- a/gns3server/api/routes/compute/images.py +++ b/gns3server/api/routes/compute/images.py @@ -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() diff --git a/gns3server/api/routes/compute/iou_nodes.py b/gns3server/api/routes/compute/iou_nodes.py index 59962654..399bdc55 100644 --- a/gns3server/api/routes/compute/iou_nodes.py +++ b/gns3server/api/routes/compute/iou_nodes.py @@ -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() diff --git a/gns3server/api/routes/compute/nat_nodes.py b/gns3server/api/routes/compute/nat_nodes.py index b2f3b7cb..3af08309 100644 --- a/gns3server/api/routes/compute/nat_nodes.py +++ b/gns3server/api/routes/compute/nat_nodes.py @@ -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. diff --git a/gns3server/api/routes/compute/notifications.py b/gns3server/api/routes/compute/notifications.py index 5807a0c4..830488e5 100644 --- a/gns3server/api/routes/compute/notifications.py +++ b/gns3server/api/routes/compute/notifications.py @@ -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 diff --git a/gns3server/api/routes/compute/projects.py b/gns3server/api/routes/compute/projects.py index e0162a16..4637f5e1 100644 --- a/gns3server/api/routes/compute/projects.py +++ b/gns3server/api/routes/compute/projects.py @@ -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) diff --git a/gns3server/api/routes/compute/qemu_nodes.py b/gns3server/api/routes/compute/qemu_nodes.py index 5e91b959..ed8d4aec 100644 --- a/gns3server/api/routes/compute/qemu_nodes.py +++ b/gns3server/api/routes/compute/qemu_nodes.py @@ -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() diff --git a/gns3server/api/routes/compute/virtualbox_nodes.py b/gns3server/api/routes/compute/virtualbox_nodes.py index a39eec6f..f558a02e 100644 --- a/gns3server/api/routes/compute/virtualbox_nodes.py +++ b/gns3server/api/routes/compute/virtualbox_nodes.py @@ -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() diff --git a/gns3server/api/routes/compute/vmware_nodes.py b/gns3server/api/routes/compute/vmware_nodes.py index 9a20c717..1d857f1e 100644 --- a/gns3server/api/routes/compute/vmware_nodes.py +++ b/gns3server/api/routes/compute/vmware_nodes.py @@ -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() diff --git a/gns3server/api/routes/compute/vpcs_nodes.py b/gns3server/api/routes/compute/vpcs_nodes.py index d15140ee..0ebfe721 100644 --- a/gns3server/api/routes/compute/vpcs_nodes.py +++ b/gns3server/api/routes/compute/vpcs_nodes.py @@ -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() diff --git a/gns3server/api/routes/controller/appliances.py b/gns3server/api/routes/controller/appliances.py index eef6f361..f307e67c 100644 --- a/gns3server/api/routes/controller/appliances.py +++ b/gns3server/api/routes/controller/appliances.py @@ -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() diff --git a/gns3server/api/routes/controller/computes.py b/gns3server/api/routes/controller/computes.py index 77ee10a9..ff0c13e3 100644 --- a/gns3server/api/routes/controller/computes.py +++ b/gns3server/api/routes/controller/computes.py @@ -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) diff --git a/gns3server/api/routes/controller/controller.py b/gns3server/api/routes/controller/controller.py index ce5a5a9d..8dfa2998 100644 --- a/gns3server/api/routes/controller/controller.py +++ b/gns3server/api/routes/controller/controller.py @@ -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", diff --git a/gns3server/api/routes/controller/dependencies/authentication.py b/gns3server/api/routes/controller/dependencies/authentication.py index 851be2bc..c66b4b62 100644 --- a/gns3server/api/routes/controller/dependencies/authentication.py +++ b/gns3server/api/routes/controller/dependencies/authentication.py @@ -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 diff --git a/gns3server/api/routes/controller/dependencies/database.py b/gns3server/api/routes/controller/dependencies/database.py index 89860de9..b003cd02 100644 --- a/gns3server/api/routes/controller/dependencies/database.py +++ b/gns3server/api/routes/controller/dependencies/database.py @@ -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 diff --git a/gns3server/api/routes/controller/drawings.py b/gns3server/api/routes/controller/drawings.py index e6924fc6..ca6e2b95 100644 --- a/gns3server/api/routes/controller/drawings.py +++ b/gns3server/api/routes/controller/drawings.py @@ -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. diff --git a/gns3server/api/routes/controller/links.py b/gns3server/api/routes/controller/links.py index a1cd9ee9..7b91460d 100644 --- a/gns3server/api/routes/controller/links.py +++ b/gns3server/api/routes/controller/links.py @@ -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(): diff --git a/gns3server/api/routes/controller/nodes.py b/gns3server/api/routes/controller/nodes.py index e1e71272..e14dd334 100644 --- a/gns3server/api/routes/controller/nodes.py +++ b/gns3server/api/routes/controller/nodes.py @@ -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) diff --git a/gns3server/api/routes/controller/notifications.py b/gns3server/api/routes/controller/notifications.py index 10bb6d4a..bb64fbb9 100644 --- a/gns3server/api/routes/controller/notifications.py +++ b/gns3server/api/routes/controller/notifications.py @@ -25,6 +25,7 @@ from websockets.exceptions import ConnectionClosed, WebSocketException from gns3server.controller import Controller import logging + log = logging.getLogger(__name__) router = APIRouter() diff --git a/gns3server/api/routes/controller/projects.py b/gns3server/api/routes/controller/projects.py index 707eee46..60168412 100644 --- a/gns3server/api/routes/controller/projects.py +++ b/gns3server/api/routes/controller/projects.py @@ -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: diff --git a/gns3server/api/routes/controller/snapshots.py b/gns3server/api/routes/controller/snapshots.py index d5ecf2fc..54284f66 100644 --- a/gns3server/api/routes/controller/snapshots.py +++ b/gns3server/api/routes/controller/snapshots.py @@ -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. diff --git a/gns3server/api/routes/controller/symbols.py b/gns3server/api/routes/controller/symbols.py index e2f21b1a..c4c1b392 100644 --- a/gns3server/api/routes/controller/symbols.py +++ b/gns3server/api/routes/controller/symbols.py @@ -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. diff --git a/gns3server/api/routes/controller/templates.py b/gns3server/api/routes/controller/templates.py index d22a8333..725f3776 100644 --- a/gns3server/api/routes/controller/templates.py +++ b/gns3server/api/routes/controller/templates.py @@ -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__() diff --git a/gns3server/api/routes/controller/users.py b/gns3server/api/routes/controller/users.py index 2a02c324..89d14275 100644 --- a/gns3server/api/routes/controller/users.py +++ b/gns3server/api/routes/controller/users.py @@ -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 diff --git a/gns3server/api/routes/index.py b/gns3server/api/routes/index.py index df932017..571ee2fd 100644 --- a/gns3server/api/routes/index.py +++ b/gns3server/api/routes/index.py @@ -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) diff --git a/gns3server/api/server.py b/gns3server/api/server.py index 5cbdf8c4..de2310eb 100644 --- a/gns3server/api/server.py +++ b/gns3server/api/server.py @@ -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)) diff --git a/gns3server/compute/__init__.py b/gns3server/compute/__init__.py index 04758a5f..e618b768 100644 --- a/gns3server/compute/__init__.py +++ b/gns3server/compute/__init__.py @@ -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) diff --git a/gns3server/compute/base_manager.py b/gns3server/compute/base_manager.py index e148419e..b578706c 100644 --- a/gns3server/compute/base_manager.py +++ b/gns3server/compute/base_manager.py @@ -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) diff --git a/gns3server/compute/base_node.py b/gns3server/compute/base_node.py index 3d4fe099..71ad1a2c 100644 --- a/gns3server/compute/base_node.py +++ b/gns3server/compute/base_node.py @@ -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}) diff --git a/gns3server/compute/builtin/__init__.py b/gns3server/compute/builtin/__init__.py index 4b7a35fb..84e09395 100644 --- a/gns3server/compute/builtin/__init__.py +++ b/gns3server/compute/builtin/__init__.py @@ -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 diff --git a/gns3server/compute/builtin/builtin_node_factory.py b/gns3server/compute/builtin/builtin_node_factory.py index 78815dba..a078274c 100644 --- a/gns3server/compute/builtin/builtin_node_factory.py +++ b/gns3server/compute/builtin/builtin_node_factory.py @@ -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: diff --git a/gns3server/compute/builtin/nodes/cloud.py b/gns3server/compute/builtin/nodes/cloud.py index 8e1705ab..dcfdd5c7 100644 --- a/gns3server/compute/builtin/nodes/cloud.py +++ b/gns3server/compute/builtin/nodes/cloud.py @@ -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 + ) ) diff --git a/gns3server/compute/builtin/nodes/ethernet_hub.py b/gns3server/compute/builtin/nodes/ethernet_hub.py index 3c6a2e5d..ca6d316a 100644 --- a/gns3server/compute/builtin/nodes/ethernet_hub.py +++ b/gns3server/compute/builtin/nodes/ethernet_hub.py @@ -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): """ diff --git a/gns3server/compute/builtin/nodes/ethernet_switch.py b/gns3server/compute/builtin/nodes/ethernet_switch.py index 623efa22..a16c64b3 100644 --- a/gns3server/compute/builtin/nodes/ethernet_switch.py +++ b/gns3server/compute/builtin/nodes/ethernet_switch.py @@ -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): """ diff --git a/gns3server/compute/builtin/nodes/nat.py b/gns3server/compute/builtin/nodes/nat.py index 2b97cd4c..a3da907f 100644 --- a/gns3server/compute/builtin/nodes/nat.py +++ b/gns3server/compute/builtin/nodes/nat.py @@ -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, } diff --git a/gns3server/compute/compute_error.py b/gns3server/compute/compute_error.py index 93f74c73..157a2cc2 100644 --- a/gns3server/compute/compute_error.py +++ b/gns3server/compute/compute_error.py @@ -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) diff --git a/gns3server/compute/docker/__init__.py b/gns3server/compute/docker/__init__.py index 7c76a8c8..ffd1d656 100644 --- a/gns3server/compute/docker/__init__.py +++ b/gns3server/compute/docker/__init__.py @@ -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 != ":": - images.append({'image': tag}) - return sorted(images, key=lambda i: i['image']) + images.append({"image": tag}) + return sorted(images, key=lambda i: i["image"]) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index 1cb52e56..a10d3ec2 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -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): """ diff --git a/gns3server/compute/dynamips/__init__.py b/gns3server/compute/dynamips/__init__.py index b174fdb8..b9670b62 100644 --- a/gns3server/compute/dynamips/__init__.py +++ b/gns3server/compute/dynamips/__init__.py @@ -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 diff --git a/gns3server/compute/dynamips/adapters/gt96100_fe.py b/gns3server/compute/dynamips/adapters/gt96100_fe.py index 6759319b..cae2900b 100644 --- a/gns3server/compute/dynamips/adapters/gt96100_fe.py +++ b/gns3server/compute/dynamips/adapters/gt96100_fe.py @@ -18,7 +18,6 @@ from .adapter import Adapter class GT96100_FE(Adapter): - def __init__(self): super().__init__(interfaces=2, wics=3) diff --git a/gns3server/compute/dynamips/dynamips_factory.py b/gns3server/compute/dynamips/dynamips_factory.py index 7a47deca..35dbd2e8 100644 --- a/gns3server/compute/dynamips/dynamips_factory.py +++ b/gns3server/compute/dynamips/dynamips_factory.py @@ -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: diff --git a/gns3server/compute/dynamips/dynamips_hypervisor.py b/gns3server/compute/dynamips/dynamips_hypervisor.py index 714a7354..e7465a55 100644 --- a/gns3server/compute/dynamips/dynamips_hypervisor.py +++ b/gns3server/compute/dynamips/dynamips_hypervisor.py @@ -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 diff --git a/gns3server/compute/dynamips/hypervisor.py b/gns3server/compute/dynamips/hypervisor.py index b08d5e88..9a1aa041 100644 --- a/gns3server/compute/dynamips/hypervisor.py +++ b/gns3server/compute/dynamips/hypervisor.py @@ -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: diff --git a/gns3server/compute/dynamips/nios/nio.py b/gns3server/compute/dynamips/nios/nio.py index ae50a7ef..2872b89e 100644 --- a/gns3server/compute/dynamips/nios/nio.py +++ b/gns3server/compute/dynamips/nios/nio.py @@ -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 diff --git a/gns3server/compute/dynamips/nios/nio_generic_ethernet.py b/gns3server/compute/dynamips/nios/nio_generic_ethernet.py index 14cc9a2a..519ac7dc 100644 --- a/gns3server/compute/dynamips/nios/nio_generic_ethernet.py +++ b/gns3server/compute/dynamips/nios/nio_generic_ethernet.py @@ -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} diff --git a/gns3server/compute/dynamips/nios/nio_linux_ethernet.py b/gns3server/compute/dynamips/nios/nio_linux_ethernet.py index a9a9b9ae..2ced9d83 100644 --- a/gns3server/compute/dynamips/nios/nio_linux_ethernet.py +++ b/gns3server/compute/dynamips/nios/nio_linux_ethernet.py @@ -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} diff --git a/gns3server/compute/dynamips/nios/nio_null.py b/gns3server/compute/dynamips/nios/nio_null.py index 792665e9..6f83f11d 100644 --- a/gns3server/compute/dynamips/nios/nio_null.py +++ b/gns3server/compute/dynamips/nios/nio_null.py @@ -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): diff --git a/gns3server/compute/dynamips/nios/nio_tap.py b/gns3server/compute/dynamips/nios/nio_tap.py index ba851856..4e27dcce 100644 --- a/gns3server/compute/dynamips/nios/nio_tap.py +++ b/gns3server/compute/dynamips/nios/nio_tap.py @@ -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} diff --git a/gns3server/compute/dynamips/nios/nio_udp.py b/gns3server/compute/dynamips/nios/nio_udp.py index 321f1a3f..2988e858 100644 --- a/gns3server/compute/dynamips/nios/nio_udp.py +++ b/gns3server/compute/dynamips/nios/nio_udp.py @@ -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} diff --git a/gns3server/compute/dynamips/nios/nio_unix.py b/gns3server/compute/dynamips/nios/nio_unix.py index b52e60b0..dfc9065b 100644 --- a/gns3server/compute/dynamips/nios/nio_unix.py +++ b/gns3server/compute/dynamips/nios/nio_unix.py @@ -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} diff --git a/gns3server/compute/dynamips/nios/nio_vde.py b/gns3server/compute/dynamips/nios/nio_vde.py index 0da87846..ef2d921f 100644 --- a/gns3server/compute/dynamips/nios/nio_vde.py +++ b/gns3server/compute/dynamips/nios/nio_vde.py @@ -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} diff --git a/gns3server/compute/dynamips/nodes/atm_switch.py b/gns3server/compute/dynamips/nodes/atm_switch.py index 8c902efc..e47b8599 100644 --- a/gns3server/compute/dynamips/nodes/atm_switch.py +++ b/gns3server/compute/dynamips/nodes/atm_switch.py @@ -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 + ) + ) diff --git a/gns3server/compute/dynamips/nodes/bridge.py b/gns3server/compute/dynamips/nodes/bridge.py index 2b87117a..4b8bfcaf 100644 --- a/gns3server/compute/dynamips/nodes/bridge.py +++ b/gns3server/compute/dynamips/nodes/bridge.py @@ -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 diff --git a/gns3server/compute/dynamips/nodes/c1700.py b/gns3server/compute/dynamips/nodes/c1700.py index 200bc07f..641a5fad 100644 --- a/gns3server/compute/dynamips/nodes/c1700.py +++ b/gns3server/compute/dynamips/nodes/c1700.py @@ -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 diff --git a/gns3server/compute/dynamips/nodes/c2600.py b/gns3server/compute/dynamips/nodes/c2600.py index fee6b264..65240202 100644 --- a/gns3server/compute/dynamips/nodes/c2600.py +++ b/gns3server/compute/dynamips/nodes/c2600.py @@ -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 diff --git a/gns3server/compute/dynamips/nodes/c2691.py b/gns3server/compute/dynamips/nodes/c2691.py index fdeb8d87..cc4255ab 100644 --- a/gns3server/compute/dynamips/nodes/c2691.py +++ b/gns3server/compute/dynamips/nodes/c2691.py @@ -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 diff --git a/gns3server/compute/dynamips/nodes/c3600.py b/gns3server/compute/dynamips/nodes/c3600.py index 7fed94f7..14f875da 100644 --- a/gns3server/compute/dynamips/nodes/c3600.py +++ b/gns3server/compute/dynamips/nodes/c3600.py @@ -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 diff --git a/gns3server/compute/dynamips/nodes/c3725.py b/gns3server/compute/dynamips/nodes/c3725.py index 1cf0a399..443ac1be 100644 --- a/gns3server/compute/dynamips/nodes/c3725.py +++ b/gns3server/compute/dynamips/nodes/c3725.py @@ -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 diff --git a/gns3server/compute/dynamips/nodes/c3745.py b/gns3server/compute/dynamips/nodes/c3745.py index e680fee4..98e8efc3 100644 --- a/gns3server/compute/dynamips/nodes/c3745.py +++ b/gns3server/compute/dynamips/nodes/c3745.py @@ -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 diff --git a/gns3server/compute/dynamips/nodes/c7200.py b/gns3server/compute/dynamips/nodes/c7200.py index 764915c3..991c87b7 100644 --- a/gns3server/compute/dynamips/nodes/c7200.py +++ b/gns3server/compute/dynamips/nodes/c7200.py @@ -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 diff --git a/gns3server/compute/dynamips/nodes/ethernet_hub.py b/gns3server/compute/dynamips/nodes/ethernet_hub.py index 0253f28e..bad1d20f 100644 --- a/gns3server/compute/dynamips/nodes/ethernet_hub.py +++ b/gns3server/compute/dynamips/nodes/ethernet_hub.py @@ -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 + ) + ) diff --git a/gns3server/compute/dynamips/nodes/ethernet_switch.py b/gns3server/compute/dynamips/nodes/ethernet_switch.py index 0ccf7094..906df019 100644 --- a/gns3server/compute/dynamips/nodes/ethernet_switch.py +++ b/gns3server/compute/dynamips/nodes/ethernet_switch.py @@ -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 + ) + ) diff --git a/gns3server/compute/dynamips/nodes/frame_relay_switch.py b/gns3server/compute/dynamips/nodes/frame_relay_switch.py index 679e005a..91058c1c 100644 --- a/gns3server/compute/dynamips/nodes/frame_relay_switch.py +++ b/gns3server/compute/dynamips/nodes/frame_relay_switch.py @@ -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 + ) + ) diff --git a/gns3server/compute/dynamips/nodes/router.py b/gns3server/compute/dynamips/nodes/router.py index 49ac207b..f144d673 100644 --- a/gns3server/compute/dynamips/nodes/router.py +++ b/gns3server/compute/dynamips/nodes/router.py @@ -57,16 +57,31 @@ class Router(BaseNode): :param platform: Platform of this router """ - _status = {0: "inactive", - 1: "shutting down", - 2: "running", - 3: "suspended"} + _status = {0: "inactive", 1: "shutting down", 2: "running", 3: "suspended"} - def __init__(self, name, node_id, project, manager, dynamips_id=None, console=None, console_type="telnet", aux=None, aux_type="none", platform="c7200", hypervisor=None, ghost_flag=False): + def __init__( + self, + name, + node_id, + project, + manager, + dynamips_id=None, + console=None, + console_type="telnet", + aux=None, + aux_type="none", + platform="c7200", + hypervisor=None, + ghost_flag=False, + ): - 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 + ) - self._working_directory = os.path.join(self.project.module_working_directory(self.manager.module_name.lower()), self.id) + self._working_directory = os.path.join( + self.project.module_working_directory(self.manager.module_name.lower()), self.id + ) try: os.makedirs(os.path.join(self._working_directory, "configs"), exist_ok=True) except OSError as e: @@ -142,34 +157,36 @@ class Router(BaseNode): def __json__(self): - router_info = {"name": self.name, - "usage": self.usage, - "node_id": self.id, - "node_directory": os.path.join(self._working_directory), - "project_id": self.project.id, - "dynamips_id": self._dynamips_id, - "platform": self._platform, - "image": self._image, - "image_md5sum": md5sum(self._image), - "ram": self._ram, - "nvram": self._nvram, - "mmap": self._mmap, - "sparsemem": self._sparsemem, - "clock_divisor": self._clock_divisor, - "idlepc": self._idlepc, - "idlemax": self._idlemax, - "idlesleep": self._idlesleep, - "exec_area": self._exec_area, - "disk0": self._disk0, - "disk1": self._disk1, - "auto_delete_disks": self._auto_delete_disks, - "status": self.status, - "console": self.console, - "console_type": self.console_type, - "aux": self.aux, - "aux_type": self.aux_type, - "mac_addr": self._mac_addr, - "system_id": self._system_id} + router_info = { + "name": self.name, + "usage": self.usage, + "node_id": self.id, + "node_directory": os.path.join(self._working_directory), + "project_id": self.project.id, + "dynamips_id": self._dynamips_id, + "platform": self._platform, + "image": self._image, + "image_md5sum": md5sum(self._image), + "ram": self._ram, + "nvram": self._nvram, + "mmap": self._mmap, + "sparsemem": self._sparsemem, + "clock_divisor": self._clock_divisor, + "idlepc": self._idlepc, + "idlemax": self._idlemax, + "idlesleep": self._idlesleep, + "exec_area": self._exec_area, + "disk0": self._disk0, + "disk1": self._disk1, + "auto_delete_disks": self._auto_delete_disks, + "status": self.status, + "console": self.console, + "console_type": self.console_type, + "aux": self.aux, + "aux_type": self.aux_type, + "mac_addr": self._mac_addr, + "system_id": self._system_id, + } router_info["image"] = self.manager.get_relative_image_path(self._image, self.project.path) @@ -212,18 +229,22 @@ class Router(BaseNode): if not self._hypervisor: # We start the hypervisor is the dynamips folder and next we change to node dir # this allow the creation of common files in the dynamips folder - self._hypervisor = await self.manager.start_new_hypervisor(working_dir=self.project.module_working_directory(self.manager.module_name.lower())) + self._hypervisor = await self.manager.start_new_hypervisor( + working_dir=self.project.module_working_directory(self.manager.module_name.lower()) + ) await self._hypervisor.set_working_dir(self._working_directory) - await self._hypervisor.send('vm create "{name}" {id} {platform}'.format(name=self._name, - id=self._dynamips_id, - platform=self._platform)) + await self._hypervisor.send( + 'vm create "{name}" {id} {platform}'.format(name=self._name, id=self._dynamips_id, platform=self._platform) + ) if not self._ghost_flag: - log.info('Router {platform} "{name}" [{id}] has been created'.format(name=self._name, - platform=self._platform, - id=self._id)) + log.info( + 'Router {platform} "{name}" [{id}] has been created'.format( + name=self._name, platform=self._platform, id=self._id + ) + ) if self._console is not None: await self._hypervisor.send(f'vm set_con_tcp_port "{self._name}" {self._console}') @@ -262,7 +283,9 @@ class Router(BaseNode): if not os.path.isfile(self._image) or not os.path.exists(self._image): if os.path.islink(self._image): - raise DynamipsError(f'IOS image "{self._image}" linked to "{os.path.realpath(self._image)}" is not accessible') + raise DynamipsError( + f'IOS image "{self._image}" linked to "{os.path.realpath(self._image)}" is not accessible' + ) else: raise DynamipsError(f'IOS image "{self._image}" is not accessible') @@ -274,7 +297,7 @@ class Router(BaseNode): raise DynamipsError(f'Cannot read ELF header for IOS image "{self._image}": {e}') # IOS images must start with the ELF magic number, be 32-bit, big endian and have an ELF version of 1 - if elf_header_start != b'\x7fELF\x01\x02\x01': + if elf_header_start != b"\x7fELF\x01\x02\x01": raise DynamipsError(f'"{self._image}" is not a valid IOS image') # check if there is enough RAM to run @@ -285,20 +308,22 @@ class Router(BaseNode): startup_config_path = os.path.join("configs", f"i{self._dynamips_id}_startup-config.cfg") private_config_path = os.path.join("configs", f"i{self._dynamips_id}_private-config.cfg") - if not os.path.exists(os.path.join(self._working_directory, private_config_path)) or \ - not os.path.getsize(os.path.join(self._working_directory, private_config_path)): + if not os.path.exists(os.path.join(self._working_directory, private_config_path)) or not os.path.getsize( + os.path.join(self._working_directory, private_config_path) + ): # an empty private-config can prevent a router to boot. - private_config_path = '' + private_config_path = "" - await self._hypervisor.send('vm set_config "{name}" "{startup}" "{private}"'.format( - name=self._name, - startup=startup_config_path, - private=private_config_path)) + await self._hypervisor.send( + 'vm set_config "{name}" "{startup}" "{private}"'.format( + name=self._name, startup=startup_config_path, private=private_config_path + ) + ) await self._hypervisor.send(f'vm start "{self._name}"') self.status = "started" log.info(f'router "{self._name}" [{self._id}] has been started') - self._memory_watcher = FileWatcher(self._memory_files(), self._memory_changed, strategy='hash', delay=30) + self._memory_watcher = FileWatcher(self._memory_files(), self._memory_changed, strategy="hash", delay=30) monitor_process(self._hypervisor.process, self._termination_callback) async def _termination_callback(self, returncode): @@ -312,7 +337,12 @@ class Router(BaseNode): self.status = "stopped" log.info("Dynamips hypervisor process has stopped, return code: %d", returncode) if returncode != 0: - self.project.emit("log.error", {"message": f"Dynamips hypervisor process has stopped, return code: {returncode}\n{self._hypervisor.read_stdout()}"}) + self.project.emit( + "log.error", + { + "message": f"Dynamips hypervisor process has stopped, return code: {returncode}\n{self._hypervisor.read_stdout()}" + }, + ) async def stop(self): """ @@ -399,13 +429,27 @@ class Router(BaseNode): if self._auto_delete_disks: # delete nvram and disk files - files = glob.glob(os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_disk[0-1]")) - files += glob.glob(os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_slot[0-1]")) - files += glob.glob(os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_nvram")) - files += glob.glob(os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_flash[0-1]")) - files += glob.glob(os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_rom")) - files += glob.glob(os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_bootflash")) - files += glob.glob(os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_ssa")) + files = glob.glob( + os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_disk[0-1]") + ) + files += glob.glob( + os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_slot[0-1]") + ) + files += glob.glob( + os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_nvram") + ) + files += glob.glob( + os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_flash[0-1]") + ) + files += glob.glob( + os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_rom") + ) + files += glob.glob( + os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_bootflash") + ) + files += glob.glob( + os.path.join(glob.escape(self._working_directory), f"{self.platform}_i{self.dynamips_id}_ssa") + ) for file in files: try: log.debug(f"Deleting file {file}") @@ -487,9 +531,11 @@ class Router(BaseNode): await self._hypervisor.send(f'vm set_ios "{self._name}" "{image}"') - log.info('Router "{name}" [{id}]: has a new IOS image set: "{image}"'.format(name=self._name, - id=self._id, - image=image)) + log.info( + 'Router "{name}" [{id}]: has a new IOS image set: "{image}"'.format( + name=self._name, id=self._id, image=image + ) + ) self._image = image @@ -514,10 +560,11 @@ class Router(BaseNode): return await self._hypervisor.send(f'vm set_ram "{self._name}" {ram}') - log.info('Router "{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( + 'Router "{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 @property @@ -541,10 +588,11 @@ class Router(BaseNode): return await self._hypervisor.send(f'vm set_nvram "{self._name}" {nvram}') - log.info('Router "{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( + 'Router "{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 @property @@ -626,10 +674,11 @@ class Router(BaseNode): """ await self._hypervisor.send(f'vm set_clock_divisor "{self._name}" {clock_divisor}') - log.info('Router "{name}" [{id}]: clock divisor updated from {old_clock} to {new_clock}'.format(name=self._name, - id=self._id, - old_clock=self._clock_divisor, - new_clock=clock_divisor)) + log.info( + 'Router "{name}" [{id}]: clock divisor updated from {old_clock} to {new_clock}'.format( + name=self._name, id=self._id, old_clock=self._clock_divisor, new_clock=clock_divisor + ) + ) self._clock_divisor = clock_divisor @property @@ -709,9 +758,11 @@ class Router(BaseNode): idlepcs = await self._hypervisor.send(f'vm get_idle_pc_prop "{self._name}" 0') if old_priority is not None: self.set_process_priority_windows(self._hypervisor.process.pid, old_priority) - log.info('Router "{name}" [{id}] has finished calculating Idle-PC values after {time:.4f} seconds'.format(name=self._name, - id=self._id, - time=time.time() - begin)) + log.info( + 'Router "{name}" [{id}] has finished calculating Idle-PC values after {time:.4f} seconds'.format( + name=self._name, id=self._id, time=time.time() - begin + ) + ) if was_auto_started: await self.stop() return idlepcs @@ -752,10 +803,11 @@ class Router(BaseNode): if is_running: # router is running await self._hypervisor.send(f'vm set_idle_max "{self._name}" 0 {idlemax}') - log.info('Router "{name}" [{id}]: idlemax updated from {old_idlemax} to {new_idlemax}'.format(name=self._name, - id=self._id, - old_idlemax=self._idlemax, - new_idlemax=idlemax)) + log.info( + 'Router "{name}" [{id}]: idlemax updated from {old_idlemax} to {new_idlemax}'.format( + name=self._name, id=self._id, old_idlemax=self._idlemax, new_idlemax=idlemax + ) + ) self._idlemax = idlemax @@ -778,13 +830,15 @@ class Router(BaseNode): is_running = await self.is_running() if is_running: # router is running - await self._hypervisor.send('vm set_idle_sleep_time "{name}" 0 {idlesleep}'.format(name=self._name, - idlesleep=idlesleep)) + await self._hypervisor.send( + 'vm set_idle_sleep_time "{name}" 0 {idlesleep}'.format(name=self._name, idlesleep=idlesleep) + ) - log.info('Router "{name}" [{id}]: idlesleep updated from {old_idlesleep} to {new_idlesleep}'.format(name=self._name, - id=self._id, - old_idlesleep=self._idlesleep, - new_idlesleep=idlesleep)) + log.info( + 'Router "{name}" [{id}]: idlesleep updated from {old_idlesleep} to {new_idlesleep}'.format( + name=self._name, id=self._id, old_idlesleep=self._idlesleep, new_idlesleep=idlesleep + ) + ) self._idlesleep = idlesleep @@ -805,12 +859,15 @@ class Router(BaseNode): :ghost_file: path to ghost file """ - await self._hypervisor.send('vm set_ghost_file "{name}" "{ghost_file}"'.format(name=self._name, - ghost_file=ghost_file)) + await self._hypervisor.send( + 'vm set_ghost_file "{name}" "{ghost_file}"'.format(name=self._name, ghost_file=ghost_file) + ) - log.info('Router "{name}" [{id}]: ghost file set to "{ghost_file}"'.format(name=self._name, - id=self._id, - ghost_file=ghost_file)) + log.info( + 'Router "{name}" [{id}]: ghost file set to "{ghost_file}"'.format( + name=self._name, id=self._id, ghost_file=ghost_file + ) + ) self._ghost_file = ghost_file @@ -823,7 +880,7 @@ class Router(BaseNode): # replace specials characters in 'drive:\filename' in Linux and Dynamips in MS Windows or viceversa. ghost_file = f"{os.path.basename(self._image)}-{self._ram}.ghost" - ghost_file = ghost_file.replace('\\', '-').replace('/', '-').replace(':', '-') + ghost_file = ghost_file.replace("\\", "-").replace("/", "-").replace(":", "-") return ghost_file @property @@ -845,12 +902,15 @@ class Router(BaseNode): 2 => Use an existing ghost instance """ - await self._hypervisor.send('vm set_ghost_status "{name}" {ghost_status}'.format(name=self._name, - ghost_status=ghost_status)) + await self._hypervisor.send( + 'vm set_ghost_status "{name}" {ghost_status}'.format(name=self._name, ghost_status=ghost_status) + ) - log.info('Router "{name}" [{id}]: ghost status set to {ghost_status}'.format(name=self._name, - id=self._id, - ghost_status=ghost_status)) + log.info( + 'Router "{name}" [{id}]: ghost status set to {ghost_status}'.format( + name=self._name, id=self._id, ghost_status=ghost_status + ) + ) self._ghost_status = ghost_status @property @@ -873,13 +933,15 @@ class Router(BaseNode): :param exec_area: exec area value (integer) """ - await self._hypervisor.send('vm set_exec_area "{name}" {exec_area}'.format(name=self._name, - exec_area=exec_area)) + await self._hypervisor.send( + 'vm set_exec_area "{name}" {exec_area}'.format(name=self._name, exec_area=exec_area) + ) - log.info('Router "{name}" [{id}]: exec area updated from {old_exec}MB to {new_exec}MB'.format(name=self._name, - id=self._id, - old_exec=self._exec_area, - new_exec=exec_area)) + log.info( + 'Router "{name}" [{id}]: exec area updated from {old_exec}MB to {new_exec}MB'.format( + name=self._name, id=self._id, old_exec=self._exec_area, new_exec=exec_area + ) + ) self._exec_area = exec_area @property @@ -901,10 +963,11 @@ class Router(BaseNode): await self._hypervisor.send(f'vm set_disk0 "{self._name}" {disk0}') - log.info('Router "{name}" [{id}]: disk0 updated from {old_disk0}MB to {new_disk0}MB'.format(name=self._name, - id=self._id, - old_disk0=self._disk0, - new_disk0=disk0)) + log.info( + 'Router "{name}" [{id}]: disk0 updated from {old_disk0}MB to {new_disk0}MB'.format( + name=self._name, id=self._id, old_disk0=self._disk0, new_disk0=disk0 + ) + ) self._disk0 = disk0 @property @@ -926,10 +989,11 @@ class Router(BaseNode): await self._hypervisor.send(f'vm set_disk1 "{self._name}" {disk1}') - log.info('Router "{name}" [{id}]: disk1 updated from {old_disk1}MB to {new_disk1}MB'.format(name=self._name, - id=self._id, - old_disk1=self._disk1, - new_disk1=disk1)) + log.info( + 'Router "{name}" [{id}]: disk1 updated from {old_disk1}MB to {new_disk1}MB'.format( + name=self._name, id=self._id, old_disk1=self._disk1, new_disk1=disk1 + ) + ) self._disk1 = disk1 @property @@ -975,9 +1039,11 @@ class Router(BaseNode): if self.console_type != console_type: status = await self.get_status() if status == "running": - raise DynamipsError('"{name}" must be stopped to change the console type to {console_type}'.format(name=self._name, - console_type=console_type)) - + raise DynamipsError( + '"{name}" must be stopped to change the console type to {console_type}'.format( + name=self._name, console_type=console_type + ) + ) self.console_type = console_type @@ -1021,14 +1087,17 @@ class Router(BaseNode): :param mac_addr: a MAC address (hexadecimal format: hh:hh:hh:hh:hh:hh) """ - await self._hypervisor.send('{platform} set_mac_addr "{name}" {mac_addr}'.format(platform=self._platform, - name=self._name, - mac_addr=mac_addr)) + await self._hypervisor.send( + '{platform} set_mac_addr "{name}" {mac_addr}'.format( + platform=self._platform, name=self._name, mac_addr=mac_addr + ) + ) - log.info('Router "{name}" [{id}]: MAC address updated from {old_mac} to {new_mac}'.format(name=self._name, - id=self._id, - old_mac=self._mac_addr, - new_mac=mac_addr)) + log.info( + 'Router "{name}" [{id}]: MAC address updated from {old_mac} to {new_mac}'.format( + name=self._name, id=self._id, old_mac=self._mac_addr, new_mac=mac_addr + ) + ) self._mac_addr = mac_addr @property @@ -1048,14 +1117,17 @@ class Router(BaseNode): :param system_id: a system ID (also called board processor ID) """ - await self._hypervisor.send('{platform} set_system_id "{name}" {system_id}'.format(platform=self._platform, - name=self._name, - system_id=system_id)) + await self._hypervisor.send( + '{platform} set_system_id "{name}" {system_id}'.format( + platform=self._platform, name=self._name, system_id=system_id + ) + ) - log.info('Router "{name}" [{id}]: system ID updated from {old_id} to {new_id}'.format(name=self._name, - id=self._id, - old_id=self._system_id, - new_id=system_id)) + log.info( + 'Router "{name}" [{id}]: system ID updated from {old_id} to {new_id}'.format( + name=self._name, id=self._id, old_id=self._system_id, new_id=system_id + ) + ) self._system_id = system_id async def get_slot_bindings(self): @@ -1083,39 +1155,52 @@ class Router(BaseNode): if slot is not None: current_adapter = slot - raise DynamipsError('Slot {slot_number} is already occupied by adapter {adapter} on router "{name}"'.format(name=self._name, - slot_number=slot_number, - adapter=current_adapter)) + raise DynamipsError( + 'Slot {slot_number} is already occupied by adapter {adapter} on router "{name}"'.format( + name=self._name, slot_number=slot_number, adapter=current_adapter + ) + ) is_running = await self.is_running() # Only c7200, c3600 and c3745 (NM-4T only) support new adapter while running - if is_running and not ((self._platform == 'c7200' and not str(adapter).startswith('C7200')) - and not (self._platform == 'c3600' and self.chassis == '3660') - and not (self._platform == 'c3745' and adapter == 'NM-4T')): - raise DynamipsError('Adapter {adapter} cannot be added while router "{name}" is running'.format(adapter=adapter, - name=self._name)) + if is_running and not ( + (self._platform == "c7200" and not str(adapter).startswith("C7200")) + and not (self._platform == "c3600" and self.chassis == "3660") + and not (self._platform == "c3745" and adapter == "NM-4T") + ): + raise DynamipsError( + 'Adapter {adapter} cannot be added while router "{name}" is running'.format( + adapter=adapter, name=self._name + ) + ) - await self._hypervisor.send('vm slot_add_binding "{name}" {slot_number} 0 {adapter}'.format(name=self._name, - slot_number=slot_number, - adapter=adapter)) + await self._hypervisor.send( + 'vm slot_add_binding "{name}" {slot_number} 0 {adapter}'.format( + name=self._name, slot_number=slot_number, adapter=adapter + ) + ) - log.info('Router "{name}" [{id}]: adapter {adapter} inserted into slot {slot_number}'.format(name=self._name, - id=self._id, - adapter=adapter, - slot_number=slot_number)) + log.info( + 'Router "{name}" [{id}]: adapter {adapter} inserted into slot {slot_number}'.format( + name=self._name, id=self._id, adapter=adapter, slot_number=slot_number + ) + ) self._slots[slot_number] = adapter # Generate an OIR event if the router is running if is_running: - await self._hypervisor.send('vm slot_oir_start "{name}" {slot_number} 0'.format(name=self._name, - slot_number=slot_number)) + await self._hypervisor.send( + 'vm slot_oir_start "{name}" {slot_number} 0'.format(name=self._name, slot_number=slot_number) + ) - log.info('Router "{name}" [{id}]: OIR start event sent to slot {slot_number}'.format(name=self._name, - id=self._id, - slot_number=slot_number)) + log.info( + 'Router "{name}" [{id}]: OIR start event sent to slot {slot_number}'.format( + name=self._name, id=self._id, slot_number=slot_number + ) + ) async def slot_remove_binding(self, slot_number): """ @@ -1127,39 +1212,51 @@ class Router(BaseNode): try: adapter = self._slots[slot_number] except IndexError: - raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, - slot_number=slot_number)) + raise DynamipsError( + 'Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, slot_number=slot_number) + ) if adapter is None: - raise DynamipsError('No adapter in slot {slot_number} on router "{name}"'.format(name=self._name, - slot_number=slot_number)) + raise DynamipsError( + 'No adapter in slot {slot_number} on router "{name}"'.format(name=self._name, slot_number=slot_number) + ) is_running = await self.is_running() # Only c7200, c3600 and c3745 (NM-4T only) support to remove adapter while running - if is_running and not ((self._platform == 'c7200' and not str(adapter).startswith('C7200')) - and not (self._platform == 'c3600' and self.chassis == '3660') - and not (self._platform == 'c3745' and adapter == 'NM-4T')): - raise DynamipsError('Adapter {adapter} cannot be removed while router "{name}" is running'.format(adapter=adapter, - name=self._name)) + if is_running and not ( + (self._platform == "c7200" and not str(adapter).startswith("C7200")) + and not (self._platform == "c3600" and self.chassis == "3660") + and not (self._platform == "c3745" and adapter == "NM-4T") + ): + raise DynamipsError( + 'Adapter {adapter} cannot be removed while router "{name}" is running'.format( + adapter=adapter, name=self._name + ) + ) # Generate an OIR event if the router is running if is_running: - await self._hypervisor.send('vm slot_oir_stop "{name}" {slot_number} 0'.format(name=self._name, - slot_number=slot_number)) + await self._hypervisor.send( + 'vm slot_oir_stop "{name}" {slot_number} 0'.format(name=self._name, slot_number=slot_number) + ) - log.info('Router "{name}" [{id}]: OIR stop event sent to slot {slot_number}'.format(name=self._name, - id=self._id, - slot_number=slot_number)) + log.info( + 'Router "{name}" [{id}]: OIR stop event sent to slot {slot_number}'.format( + name=self._name, id=self._id, slot_number=slot_number + ) + ) - await self._hypervisor.send('vm slot_remove_binding "{name}" {slot_number} 0'.format(name=self._name, - slot_number=slot_number)) + await self._hypervisor.send( + 'vm slot_remove_binding "{name}" {slot_number} 0'.format(name=self._name, slot_number=slot_number) + ) - log.info('Router "{name}" [{id}]: adapter {adapter} removed from slot {slot_number}'.format(name=self._name, - id=self._id, - adapter=adapter, - slot_number=slot_number)) + log.info( + 'Router "{name}" [{id}]: adapter {adapter} removed from slot {slot_number}'.format( + name=self._name, id=self._id, adapter=adapter, slot_number=slot_number + ) + ) self._slots[slot_number] = None async def install_wic(self, wic_slot_number, wic): @@ -1184,21 +1281,24 @@ class Router(BaseNode): raise DynamipsError(f"WIC slot {wic_slot_number} is already occupied by another WIC") if await self.is_running(): - raise DynamipsError('WIC "{wic}" cannot be added while router "{name}" is running'.format(wic=wic, - name=self._name)) + raise DynamipsError( + 'WIC "{wic}" cannot be added while router "{name}" is running'.format(wic=wic, name=self._name) + ) # Dynamips WICs slot IDs start on a multiple of 16 # WIC1 = 16, WIC2 = 32 and WIC3 = 48 internal_wic_slot_number = 16 * (wic_slot_number + 1) - await self._hypervisor.send('vm slot_add_binding "{name}" {slot_number} {wic_slot_number} {wic}'.format(name=self._name, - slot_number=slot_number, - wic_slot_number=internal_wic_slot_number, - wic=wic)) + await self._hypervisor.send( + 'vm slot_add_binding "{name}" {slot_number} {wic_slot_number} {wic}'.format( + name=self._name, slot_number=slot_number, wic_slot_number=internal_wic_slot_number, wic=wic + ) + ) - log.info('Router "{name}" [{id}]: {wic} inserted into WIC slot {wic_slot_number}'.format(name=self._name, - id=self._id, - wic=wic, - wic_slot_number=wic_slot_number)) + log.info( + 'Router "{name}" [{id}]: {wic} inserted into WIC slot {wic_slot_number}'.format( + name=self._name, id=self._id, wic=wic, wic_slot_number=wic_slot_number + ) + ) adapter.install_wic(wic_slot_number, wic) @@ -1223,20 +1323,26 @@ class Router(BaseNode): raise DynamipsError(f"No WIC is installed in WIC slot {wic_slot_number}") if await self.is_running(): - raise DynamipsError('WIC cannot be removed from slot {wic_slot_number} while router "{name}" is running'.format(wic_slot_number=wic_slot_number, - name=self._name)) + raise DynamipsError( + 'WIC cannot be removed from slot {wic_slot_number} while router "{name}" is running'.format( + wic_slot_number=wic_slot_number, name=self._name + ) + ) # Dynamips WICs slot IDs start on a multiple of 16 # WIC1 = 16, WIC2 = 32 and WIC3 = 48 internal_wic_slot_number = 16 * (wic_slot_number + 1) - await self._hypervisor.send('vm slot_remove_binding "{name}" {slot_number} {wic_slot_number}'.format(name=self._name, - slot_number=slot_number, - wic_slot_number=internal_wic_slot_number)) + await self._hypervisor.send( + 'vm slot_remove_binding "{name}" {slot_number} {wic_slot_number}'.format( + name=self._name, slot_number=slot_number, wic_slot_number=internal_wic_slot_number + ) + ) - log.info('Router "{name}" [{id}]: {wic} removed from WIC slot {wic_slot_number}'.format(name=self._name, - id=self._id, - wic=adapter.wics[wic_slot_number], - wic_slot_number=wic_slot_number)) + log.info( + 'Router "{name}" [{id}]: {wic} removed from WIC slot {wic_slot_number}'.format( + name=self._name, id=self._id, wic=adapter.wics[wic_slot_number], wic_slot_number=wic_slot_number + ) + ) adapter.uninstall_wic(wic_slot_number) async def get_slot_nio_bindings(self, slot_number): @@ -1248,8 +1354,9 @@ class Router(BaseNode): :returns: list of NIO bindings """ - nio_bindings = await self._hypervisor.send('vm slot_nio_bindings "{name}" {slot_number}'.format(name=self._name, - slot_number=slot_number)) + nio_bindings = await self._hypervisor.send( + 'vm slot_nio_bindings "{name}" {slot_number}'.format(name=self._name, slot_number=slot_number) + ) return nio_bindings async def slot_add_nio_binding(self, slot_number, port_number, nio): @@ -1264,36 +1371,44 @@ class Router(BaseNode): try: adapter = self._slots[slot_number] except IndexError: - raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, - slot_number=slot_number)) + raise DynamipsError( + 'Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, slot_number=slot_number) + ) if adapter is None: raise DynamipsError(f"Adapter is missing in slot {slot_number}") if not adapter.port_exists(port_number): - raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter, - port_number=port_number)) + raise DynamipsError( + "Port {port_number} does not exist on adapter {adapter}".format( + adapter=adapter, port_number=port_number + ) + ) try: - await self._hypervisor.send('vm slot_add_nio_binding "{name}" {slot_number} {port_number} {nio}'.format(name=self._name, - slot_number=slot_number, - port_number=port_number, - nio=nio)) + await self._hypervisor.send( + 'vm slot_add_nio_binding "{name}" {slot_number} {port_number} {nio}'.format( + name=self._name, slot_number=slot_number, port_number=port_number, nio=nio + ) + ) except DynamipsError: # in case of error try to remove and add the nio binding - await self._hypervisor.send('vm slot_remove_nio_binding "{name}" {slot_number} {port_number}'.format(name=self._name, - slot_number=slot_number, - port_number=port_number)) - await self._hypervisor.send('vm slot_add_nio_binding "{name}" {slot_number} {port_number} {nio}'.format(name=self._name, - slot_number=slot_number, - port_number=port_number, - nio=nio)) + await self._hypervisor.send( + 'vm slot_remove_nio_binding "{name}" {slot_number} {port_number}'.format( + name=self._name, slot_number=slot_number, port_number=port_number + ) + ) + await self._hypervisor.send( + 'vm slot_add_nio_binding "{name}" {slot_number} {port_number} {nio}'.format( + name=self._name, slot_number=slot_number, port_number=port_number, nio=nio + ) + ) - log.info('Router "{name}" [{id}]: NIO {nio_name} bound to port {slot_number}/{port_number}'.format(name=self._name, - id=self._id, - nio_name=nio.name, - slot_number=slot_number, - port_number=port_number)) + log.info( + 'Router "{name}" [{id}]: NIO {nio_name} bound to port {slot_number}/{port_number}'.format( + name=self._name, id=self._id, nio_name=nio.name, slot_number=slot_number, port_number=port_number + ) + ) await self.slot_enable_nio(slot_number, port_number) adapter.add_nio(port_number, nio) @@ -1322,21 +1437,27 @@ class Router(BaseNode): try: adapter = self._slots[slot_number] except IndexError: - raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, - slot_number=slot_number)) + raise DynamipsError( + 'Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, slot_number=slot_number) + ) if adapter is None: raise DynamipsError(f"Adapter is missing in slot {slot_number}") if not adapter.port_exists(port_number): - raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter, - port_number=port_number)) + raise DynamipsError( + "Port {port_number} does not exist on adapter {adapter}".format( + adapter=adapter, port_number=port_number + ) + ) await self.stop_capture(slot_number, port_number) await self.slot_disable_nio(slot_number, port_number) - await self._hypervisor.send('vm slot_remove_nio_binding "{name}" {slot_number} {port_number}'.format(name=self._name, - slot_number=slot_number, - port_number=port_number)) + await self._hypervisor.send( + 'vm slot_remove_nio_binding "{name}" {slot_number} {port_number}'.format( + name=self._name, slot_number=slot_number, port_number=port_number + ) + ) nio = adapter.get_nio(port_number) if nio is None: @@ -1344,11 +1465,11 @@ class Router(BaseNode): await nio.close() adapter.remove_nio(port_number) - log.info('Router "{name}" [{id}]: NIO {nio_name} removed from port {slot_number}/{port_number}'.format(name=self._name, - id=self._id, - nio_name=nio.name, - slot_number=slot_number, - port_number=port_number)) + log.info( + 'Router "{name}" [{id}]: NIO {nio_name} removed from port {slot_number}/{port_number}'.format( + name=self._name, id=self._id, nio_name=nio.name, slot_number=slot_number, port_number=port_number + ) + ) return nio @@ -1362,14 +1483,17 @@ class Router(BaseNode): is_running = await self.is_running() if is_running: # running router - await self._hypervisor.send('vm slot_enable_nio "{name}" {slot_number} {port_number}'.format(name=self._name, - slot_number=slot_number, - port_number=port_number)) + await self._hypervisor.send( + 'vm slot_enable_nio "{name}" {slot_number} {port_number}'.format( + name=self._name, slot_number=slot_number, port_number=port_number + ) + ) - log.info('Router "{name}" [{id}]: NIO enabled on port {slot_number}/{port_number}'.format(name=self._name, - id=self._id, - slot_number=slot_number, - port_number=port_number)) + log.info( + 'Router "{name}" [{id}]: NIO enabled on port {slot_number}/{port_number}'.format( + name=self._name, id=self._id, slot_number=slot_number, port_number=port_number + ) + ) def get_nio(self, slot_number, port_number): """ @@ -1384,17 +1508,24 @@ class Router(BaseNode): try: adapter = self._slots[slot_number] except IndexError: - raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, - slot_number=slot_number)) + raise DynamipsError( + 'Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, slot_number=slot_number) + ) if not adapter.port_exists(port_number): - raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter, - port_number=port_number)) + raise DynamipsError( + "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 DynamipsError("Port {slot_number}/{port_number} is not connected".format(slot_number=slot_number, - port_number=port_number)) + raise DynamipsError( + "Port {slot_number}/{port_number} is not connected".format( + slot_number=slot_number, port_number=port_number + ) + ) return nio async def slot_disable_nio(self, slot_number, port_number): @@ -1407,14 +1538,17 @@ class Router(BaseNode): is_running = await self.is_running() if is_running: # running router - await self._hypervisor.send('vm slot_disable_nio "{name}" {slot_number} {port_number}'.format(name=self._name, - slot_number=slot_number, - port_number=port_number)) + await self._hypervisor.send( + 'vm slot_disable_nio "{name}" {slot_number} {port_number}'.format( + name=self._name, slot_number=slot_number, port_number=port_number + ) + ) - log.info('Router "{name}" [{id}]: NIO disabled on port {slot_number}/{port_number}'.format(name=self._name, - id=self._id, - slot_number=slot_number, - port_number=port_number)) + log.info( + 'Router "{name}" [{id}]: NIO disabled on port {slot_number}/{port_number}'.format( + name=self._name, id=self._id, slot_number=slot_number, port_number=port_number + ) + ) async def start_capture(self, slot_number, port_number, output_file, data_link_type="DLT_EN10MB"): """ @@ -1427,18 +1561,22 @@ class Router(BaseNode): """ try: - open(output_file, 'w+').close() + open(output_file, "w+").close() except OSError as e: raise DynamipsError(f'Can not write capture to "{output_file}": {str(e)}') try: adapter = self._slots[slot_number] except IndexError: - raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, - slot_number=slot_number)) + raise DynamipsError( + 'Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, slot_number=slot_number) + ) if not adapter.port_exists(port_number): - raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter, - port_number=port_number)) + raise DynamipsError( + "Port {port_number} does not exist on adapter {adapter}".format( + adapter=adapter, port_number=port_number + ) + ) data_link_type = data_link_type.lower() if data_link_type.startswith("dlt_"): @@ -1447,18 +1585,24 @@ class Router(BaseNode): nio = adapter.get_nio(port_number) if not nio: - raise DynamipsError("Port {slot_number}/{port_number} is not connected".format(slot_number=slot_number, - port_number=port_number)) + raise DynamipsError( + "Port {slot_number}/{port_number} is not connected".format( + slot_number=slot_number, port_number=port_number + ) + ) if nio.input_filter[0] is not None and nio.output_filter[0] is not None: - raise DynamipsError("Port {port_number} has already a filter applied on {adapter}".format(adapter=adapter, - port_number=port_number)) + raise DynamipsError( + "Port {port_number} has already a filter applied on {adapter}".format( + adapter=adapter, port_number=port_number + ) + ) await nio.start_packet_capture(output_file, data_link_type) - log.info('Router "{name}" [{id}]: starting packet capture on port {slot_number}/{port_number}'.format(name=self._name, - id=self._id, - nio_name=nio.name, - slot_number=slot_number, - port_number=port_number)) + log.info( + 'Router "{name}" [{id}]: starting packet capture on port {slot_number}/{port_number}'.format( + name=self._name, id=self._id, nio_name=nio.name, slot_number=slot_number, port_number=port_number + ) + ) async def stop_capture(self, slot_number, port_number): """ @@ -1471,27 +1615,34 @@ class Router(BaseNode): try: adapter = self._slots[slot_number] except IndexError: - raise DynamipsError('Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, - slot_number=slot_number)) + raise DynamipsError( + 'Slot {slot_number} does not exist on router "{name}"'.format(name=self._name, slot_number=slot_number) + ) if not adapter.port_exists(port_number): - raise DynamipsError("Port {port_number} does not exist on adapter {adapter}".format(adapter=adapter, - port_number=port_number)) + raise DynamipsError( + "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 DynamipsError("Port {slot_number}/{port_number} is not connected".format(slot_number=slot_number, - port_number=port_number)) + raise DynamipsError( + "Port {slot_number}/{port_number} is not connected".format( + slot_number=slot_number, port_number=port_number + ) + ) if not nio.capturing: return await nio.stop_packet_capture() - log.info('Router "{name}" [{id}]: stopping packet capture on port {slot_number}/{port_number}'.format(name=self._name, - id=self._id, - nio_name=nio.name, - slot_number=slot_number, - port_number=port_number)) + log.info( + 'Router "{name}" [{id}]: stopping packet capture on port {slot_number}/{port_number}'.format( + name=self._name, id=self._id, nio_name=nio.name, slot_number=slot_number, port_number=port_number + ) + ) def _create_slots(self, numslots): """ @@ -1573,7 +1724,7 @@ class Router(BaseNode): except DynamipsError: # for some reason Dynamips gets frozen when it does not find the magic number in the NVRAM file. return None, None - reply = reply[0].rsplit(' ', 2)[-2:] + reply = reply[0].rsplit(" ", 2)[-2:] startup_config = reply[0][1:-1] # get statup-config and remove single quotes private_config = reply[1][1:-1] # get private-config and remove single quotes return startup_config, private_config @@ -1602,7 +1753,7 @@ class Router(BaseNode): except (binascii.Error, OSError) as e: raise DynamipsError(f"Could not save the startup configuration {config_path}: {e}") - if private_config_base64 and base64.b64decode(private_config_base64) != b'\nkerberos password \nend\n': + if private_config_base64 and base64.b64decode(private_config_base64) != b"\nkerberos password \nend\n": private_config = self.private_config_path try: config = base64.b64decode(private_config_base64).decode("utf-8", errors="replace") @@ -1642,5 +1793,5 @@ class Router(BaseNode): return [ os.path.join(self._working_directory, f"{self.platform}_i{self.dynamips_id}_rom"), - os.path.join(self._working_directory, f"{self.platform}_i{self.dynamips_id}_nvram") + os.path.join(self._working_directory, f"{self.platform}_i{self.dynamips_id}_nvram"), ] diff --git a/gns3server/compute/error.py b/gns3server/compute/error.py index 589ec68d..dc293213 100644 --- a/gns3server/compute/error.py +++ b/gns3server/compute/error.py @@ -16,7 +16,6 @@ class NodeError(Exception): - def __init__(self, message, original_exception=None): super().__init__(message) if isinstance(message, Exception): diff --git a/gns3server/compute/iou/__init__.py b/gns3server/compute/iou/__init__.py index 29da6f72..31e7878d 100644 --- a/gns3server/compute/iou/__init__.py +++ b/gns3server/compute/iou/__init__.py @@ -26,6 +26,7 @@ from .iou_error import IOUError from .iou_vm import IOUVM import logging + log = logging.getLogger(__name__) diff --git a/gns3server/compute/iou/iou_vm.py b/gns3server/compute/iou/iou_vm.py index 415b5b64..cd2f1c1c 100644 --- a/gns3server/compute/iou/iou_vm.py +++ b/gns3server/compute/iou/iou_vm.py @@ -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 \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 + ) + ) diff --git a/gns3server/compute/iou/utils/iou_export.py b/gns3server/compute/iou/utils/iou_export.py index 242b3c9d..f3b67d03 100644 --- a/gns3server/compute/iou/utils/iou_export.py +++ b/gns3server/compute/iou/utils/iou_export.py @@ -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: diff --git a/gns3server/compute/iou/utils/iou_import.py b/gns3server/compute/iou/utils/iou_import.py index c0a5c069..361ed0c5 100644 --- a/gns3server/compute/iou/utils/iou_import.py +++ b/gns3server/compute/iou/utils/iou_import.py @@ -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: diff --git a/gns3server/compute/nios/nio_ethernet.py b/gns3server/compute/nios/nio_ethernet.py index a4b95682..a80ab5e2 100644 --- a/gns3server/compute/nios/nio_ethernet.py +++ b/gns3server/compute/nios/nio_ethernet.py @@ -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} diff --git a/gns3server/compute/nios/nio_tap.py b/gns3server/compute/nios/nio_tap.py index 5fb0a6c8..51deefe2 100644 --- a/gns3server/compute/nios/nio_tap.py +++ b/gns3server/compute/nios/nio_tap.py @@ -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} diff --git a/gns3server/compute/nios/nio_udp.py b/gns3server/compute/nios/nio_udp.py index f82e8665..68fd2a8a 100644 --- a/gns3server/compute/nios/nio_udp.py +++ b/gns3server/compute/nios/nio_udp.py @@ -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} diff --git a/gns3server/compute/notification_manager.py b/gns3server/compute/notification_manager.py index f3e48ddf..82388b0a 100644 --- a/gns3server/compute/notification_manager.py +++ b/gns3server/compute/notification_manager.py @@ -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 diff --git a/gns3server/compute/port_manager.py b/gns3server/compute/port_manager.py index 4100a673..cdfaa72a 100644 --- a/gns3server/compute/port_manager.py +++ b/gns3server/compute/port_manager.py @@ -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") diff --git a/gns3server/compute/project.py b/gns3server/compute/project.py index 33149fe7..c1a44225 100644 --- a/gns3server/compute/project.py +++ b/gns3server/compute/project.py @@ -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) diff --git a/gns3server/compute/project_manager.py b/gns3server/compute/project_manager.py index 23291e28..b657007c 100644 --- a/gns3server/compute/project_manager.py +++ b/gns3server/compute/project_manager.py @@ -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 diff --git a/gns3server/compute/qemu/__init__.py b/gns3server/compute/qemu/__init__.py index fea96961..2993ddbf 100644 --- a/gns3server/compute/qemu/__init__.py +++ b/gns3server/compute/qemu/__init__.py @@ -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()): diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index ee81cb07..7e618210 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -50,11 +50,12 @@ from ...utils import macaddress_to_int, int_to_macaddress from gns3server.schemas.qemu_nodes import Qemu, QemuPlatform import logging + log = logging.getLogger(__name__) class QemuVM(BaseNode): - module_name = 'qemu' + module_name = "qemu" """ QEMU VM implementation. @@ -69,9 +70,34 @@ class QemuVM(BaseNode): :param platform: Platform to emulate """ - def __init__(self, name, node_id, project, manager, linked_clone=True, qemu_path=None, console=None, console_type="telnet", aux=None, aux_type="none", platform=None): + def __init__( + self, + name, + node_id, + project, + manager, + linked_clone=True, + qemu_path=None, + console=None, + console_type="telnet", + aux=None, + aux_type="none", + platform=None, + ): - super().__init__(name, node_id, project, manager, console=console, console_type=console_type, linked_clone=linked_clone, aux=aux, aux_type=aux_type, wrap_console=True, wrap_aux=True) + super().__init__( + name, + node_id, + project, + manager, + console=console, + console_type=console_type, + linked_clone=linked_clone, + aux=aux, + aux_type=aux_type, + wrap_console=True, + wrap_aux=True, + ) self._host = manager.config.settings.Server.host self._monitor_host = manager.config.settings.Qemu.monitor_host @@ -209,20 +235,22 @@ class QemuVM(BaseNode): self._platform = "x86_64" else: qemu_bin = os.path.basename(qemu_path) - qemu_bin = re.sub(r'(w)?\.(exe|EXE)$', '', qemu_bin) + qemu_bin = re.sub(r"(w)?\.(exe|EXE)$", "", qemu_bin) # Old version of GNS3 provide a binary named qemu.exe if qemu_bin == "qemu": self._platform = "i386" else: - self._platform = re.sub(r'^qemu-system-(.*)$', r'\1', qemu_bin, re.IGNORECASE) + self._platform = re.sub(r"^qemu-system-(.*)$", r"\1", qemu_bin, re.IGNORECASE) try: QemuPlatform(self._platform.split(".")[0]) except ValueError: raise QemuError(f"Platform {self._platform} is unknown") - log.info('QEMU VM "{name}" [{id}] has set the QEMU path to {qemu_path}'.format(name=self._name, - id=self._id, - qemu_path=qemu_path)) + log.info( + 'QEMU VM "{name}" [{id}] has set the QEMU path to {qemu_path}'.format( + name=self._name, id=self._id, qemu_path=qemu_path + ) + ) def _check_qemu_path(self, qemu_path): @@ -261,12 +289,15 @@ class QemuVM(BaseNode): if not self.linked_clone: for node in self.manager.nodes: if node != self and getattr(node, variable) == value: - raise QemuError(f"Sorry a node without the linked base setting enabled can only be used once on your server. {value} is already used by {node.name}") + raise QemuError( + f"Sorry a node without the linked base setting enabled can only be used once on your server. {value} is already used by {node.name}" + ) setattr(self, "_" + variable, value) - log.info('QEMU VM "{name}" [{id}] has set the QEMU {variable} path to {disk_image}'.format(name=self._name, - variable=variable, - id=self._id, - disk_image=value)) + log.info( + 'QEMU VM "{name}" [{id}] has set the QEMU {variable} path to {disk_image}'.format( + name=self._name, variable=variable, id=self._id, disk_image=value + ) + ) @property def hda_disk_image(self): @@ -367,9 +398,11 @@ class QemuVM(BaseNode): """ self._hda_disk_interface = hda_disk_interface - log.info('QEMU VM "{name}" [{id}] has set the QEMU hda disk interface to {interface}'.format(name=self._name, - id=self._id, - interface=self._hda_disk_interface)) + log.info( + 'QEMU VM "{name}" [{id}] has set the QEMU hda disk interface to {interface}'.format( + name=self._name, id=self._id, interface=self._hda_disk_interface + ) + ) @property def hdb_disk_interface(self): @@ -390,9 +423,11 @@ class QemuVM(BaseNode): """ self._hdb_disk_interface = hdb_disk_interface - log.info('QEMU VM "{name}" [{id}] has set the QEMU hdb disk interface to {interface}'.format(name=self._name, - id=self._id, - interface=self._hdb_disk_interface)) + log.info( + 'QEMU VM "{name}" [{id}] has set the QEMU hdb disk interface to {interface}'.format( + name=self._name, id=self._id, interface=self._hdb_disk_interface + ) + ) @property def hdc_disk_interface(self): @@ -413,9 +448,11 @@ class QemuVM(BaseNode): """ self._hdc_disk_interface = hdc_disk_interface - log.info('QEMU VM "{name}" [{id}] has set the QEMU hdc disk interface to {interface}'.format(name=self._name, - id=self._id, - interface=self._hdc_disk_interface)) + log.info( + 'QEMU VM "{name}" [{id}] has set the QEMU hdc disk interface to {interface}'.format( + name=self._name, id=self._id, interface=self._hdc_disk_interface + ) + ) @property def hdd_disk_interface(self): @@ -436,9 +473,11 @@ class QemuVM(BaseNode): """ self._hdd_disk_interface = hdd_disk_interface - log.info('QEMU VM "{name}" [{id}] has set the QEMU hdd disk interface to {interface}'.format(name=self._name, - id=self._id, - interface=self._hdd_disk_interface)) + log.info( + 'QEMU VM "{name}" [{id}] has set the QEMU hdd disk interface to {interface}'.format( + name=self._name, id=self._id, interface=self._hdd_disk_interface + ) + ) @property def cdrom_image(self): @@ -461,9 +500,11 @@ class QemuVM(BaseNode): if cdrom_image: self._cdrom_image = self.manager.get_abs_image_path(cdrom_image, self.project.path) - log.info('QEMU VM "{name}" [{id}] has set the QEMU cdrom image path to {cdrom_image}'.format(name=self._name, - id=self._id, - cdrom_image=self._cdrom_image)) + log.info( + 'QEMU VM "{name}" [{id}] has set the QEMU cdrom image path to {cdrom_image}'.format( + name=self._name, id=self._id, cdrom_image=self._cdrom_image + ) + ) else: self._cdrom_image = "" @@ -488,9 +529,11 @@ class QemuVM(BaseNode): self._cdrom_option() # this will check the cdrom image is accessible await self._control_vm("eject -f ide1-cd0") await self._control_vm(f"change ide1-cd0 {self._cdrom_image}") - log.info('QEMU VM "{name}" [{id}] has changed the cdrom image path to {cdrom_image}'.format(name=self._name, - id=self._id, - cdrom_image=self._cdrom_image)) + log.info( + 'QEMU VM "{name}" [{id}] has changed the cdrom image path to {cdrom_image}'.format( + name=self._name, id=self._id, cdrom_image=self._cdrom_image + ) + ) else: await self._control_vm("eject -f ide1-cd0") log.info(f'QEMU VM "{self._name}" [{self._id}] has ejected the cdrom image') @@ -514,9 +557,11 @@ class QemuVM(BaseNode): """ self._bios_image = self.manager.get_abs_image_path(bios_image, self.project.path) - log.info('QEMU VM "{name}" [{id}] has set the QEMU bios image path to {bios_image}'.format(name=self._name, - id=self._id, - bios_image=self._bios_image)) + log.info( + 'QEMU VM "{name}" [{id}] has set the QEMU bios image path to {bios_image}'.format( + name=self._name, id=self._id, bios_image=self._bios_image + ) + ) @property def boot_priority(self): @@ -537,9 +582,11 @@ class QemuVM(BaseNode): """ self._boot_priority = boot_priority - log.info('QEMU VM "{name}" [{id}] has set the boot priority to {boot_priority}'.format(name=self._name, - id=self._id, - boot_priority=self._boot_priority)) + log.info( + 'QEMU VM "{name}" [{id}] has set the boot priority to {boot_priority}'.format( + name=self._name, id=self._id, boot_priority=self._boot_priority + ) + ) @property def ethernet_adapters(self): @@ -570,9 +617,11 @@ class QemuVM(BaseNode): for adapter_number in range(0, adapters): self._ethernet_adapters.append(EthernetAdapter()) - log.info('QEMU VM "{name}" [{id}]: number of Ethernet adapters changed to {adapters}'.format(name=self._name, - id=self._id, - adapters=adapters)) + log.info( + 'QEMU VM "{name}" [{id}]: number of Ethernet adapters changed to {adapters}'.format( + name=self._name, id=self._id, adapters=adapters + ) + ) @property def adapter_type(self): @@ -594,9 +643,11 @@ class QemuVM(BaseNode): self._adapter_type = adapter_type - log.info('QEMU VM "{name}" [{id}]: adapter type changed to {adapter_type}'.format(name=self._name, - id=self._id, - adapter_type=adapter_type)) + log.info( + 'QEMU VM "{name}" [{id}]: adapter type changed to {adapter_type}'.format( + name=self._name, id=self._id, adapter_type=adapter_type + ) + ) @property def mac_address(self): @@ -622,9 +673,11 @@ class QemuVM(BaseNode): else: self._mac_address = mac_address - log.info('QEMU VM "{name}" [{id}]: MAC address changed to {mac_addr}'.format(name=self._name, - id=self._id, - mac_addr=self._mac_address)) + log.info( + 'QEMU VM "{name}" [{id}]: MAC address changed to {mac_addr}'.format( + name=self._name, id=self._id, mac_addr=self._mac_address + ) + ) @property def legacy_networking(self): @@ -737,9 +790,11 @@ class QemuVM(BaseNode): :param cpu_throttling: integer """ - log.info('QEMU VM "{name}" [{id}] has set the percentage of CPU allowed to {cpu}'.format(name=self._name, - id=self._id, - cpu=cpu_throttling)) + log.info( + 'QEMU VM "{name}" [{id}] has set the percentage of CPU allowed to {cpu}'.format( + name=self._name, id=self._id, cpu=cpu_throttling + ) + ) self._cpu_throttling = cpu_throttling self._stop_cpulimit() if cpu_throttling: @@ -763,9 +818,11 @@ class QemuVM(BaseNode): :param process_priority: string """ - log.info('QEMU VM "{name}" [{id}] has set the process priority to {priority}'.format(name=self._name, - id=self._id, - priority=process_priority)) + log.info( + 'QEMU VM "{name}" [{id}] has set the process priority to {priority}'.format( + name=self._name, id=self._id, priority=process_priority + ) + ) self._process_priority = process_priority @property @@ -849,9 +906,11 @@ class QemuVM(BaseNode): :param options: QEMU options """ - log.info('QEMU VM "{name}" [{id}] has set the QEMU options to {options}'.format(name=self._name, - id=self._id, - options=options)) + log.info( + 'QEMU VM "{name}" [{id}] has set the QEMU options to {options}'.format( + name=self._name, id=self._id, options=options + ) + ) if not sys.platform.startswith("linux"): if "-no-kvm" in options: @@ -889,11 +948,18 @@ class QemuVM(BaseNode): initrd = self.manager.get_abs_image_path(initrd, self.project.path) - log.info('QEMU VM "{name}" [{id}] has set the QEMU initrd path to {initrd}'.format(name=self._name, - id=self._id, - initrd=initrd)) + log.info( + 'QEMU VM "{name}" [{id}] has set the QEMU initrd path to {initrd}'.format( + name=self._name, id=self._id, initrd=initrd + ) + ) if "asa" in initrd and self._initrd != initrd: - self.project.emit("log.warning", {"message": "Warning ASA 8 is not supported by GNS3 and Cisco, please use ASAv instead. Depending of your hardware and OS this could not work or you could be limited to one instance. If ASA 8 is not booting their is no GNS3 solution, you must to upgrade to ASAv."}) + self.project.emit( + "log.warning", + { + "message": "Warning ASA 8 is not supported by GNS3 and Cisco, please use ASAv instead. Depending of your hardware and OS this could not work or you could be limited to one instance. If ASA 8 is not booting their is no GNS3 solution, you must to upgrade to ASAv." + }, + ) self._initrd = initrd @property @@ -915,9 +981,11 @@ class QemuVM(BaseNode): """ kernel_image = self.manager.get_abs_image_path(kernel_image, self.project.path) - log.info('QEMU VM "{name}" [{id}] has set the QEMU kernel image path to {kernel_image}'.format(name=self._name, - id=self._id, - kernel_image=kernel_image)) + log.info( + 'QEMU VM "{name}" [{id}] has set the QEMU kernel image path to {kernel_image}'.format( + name=self._name, id=self._id, kernel_image=kernel_image + ) + ) self._kernel_image = kernel_image @property @@ -938,9 +1006,11 @@ class QemuVM(BaseNode): :param kernel_command_line: QEMU kernel command line """ - log.info('QEMU VM "{name}" [{id}] has set the QEMU kernel command line to {kernel_command_line}'.format(name=self._name, - id=self._id, - kernel_command_line=kernel_command_line)) + log.info( + 'QEMU VM "{name}" [{id}] has set the QEMU kernel command line to {kernel_command_line}'.format( + name=self._name, id=self._id, kernel_command_line=kernel_command_line + ) + ) self._kernel_command_line = kernel_command_line async def _set_process_priority(self): @@ -991,7 +1061,9 @@ class QemuVM(BaseNode): else: priority = 0 try: - process = await asyncio.create_subprocess_exec('renice', '-n', str(priority), '-p', str(self._process.pid)) + process = await asyncio.create_subprocess_exec( + "renice", "-n", str(priority), "-p", str(self._process.pid) + ) await process.wait() except (OSError, subprocess.SubprocessError) as e: log.error(f'Could not change process priority for QEMU VM "{self._name}": {e}') @@ -1018,10 +1090,15 @@ class QemuVM(BaseNode): try: if sys.platform.startswith("win") and hasattr(sys, "frozen"): - cpulimit_exec = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "cpulimit", "cpulimit.exe") + cpulimit_exec = os.path.join( + os.path.dirname(os.path.abspath(sys.executable)), "cpulimit", "cpulimit.exe" + ) else: cpulimit_exec = "cpulimit" - subprocess.Popen([cpulimit_exec, "--lazy", f"--pid={self._process.pid}", f"--limit={self._cpu_throttling}"], cwd=self.working_dir) + subprocess.Popen( + [cpulimit_exec, "--lazy", f"--pid={self._process.pid}", f"--limit={self._cpu_throttling}"], + cwd=self.working_dir, + ) log.info(f"CPU throttled to {self._cpu_throttling}%") except FileNotFoundError: raise QemuError("cpulimit could not be found, please install it or deactivate CPU throttling") @@ -1056,7 +1133,9 @@ class QemuVM(BaseNode): if self._manager.config.settings.Qemu.enable_monitor: try: - info = socket.getaddrinfo(self._monitor_host, 0, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE) + info = socket.getaddrinfo( + self._monitor_host, 0, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE + ) if not info: raise QemuError(f"getaddrinfo returns an empty list on {self._monitor_host}") for res in info: @@ -1080,11 +1159,10 @@ class QemuVM(BaseNode): log.info(f"logging to {self._stdout_file}") with open(self._stdout_file, "w", encoding="utf-8") as fd: fd.write(f"Start QEMU with {command_string}\n\nExecution log:\n") - self.command_line = ' '.join(command) - self._process = await asyncio.create_subprocess_exec(*command, - stdout=fd, - stderr=subprocess.STDOUT, - cwd=self.working_dir) + self.command_line = " ".join(command) + self._process = await asyncio.create_subprocess_exec( + *command, stdout=fd, stderr=subprocess.STDOUT, cwd=self.working_dir + ) log.info(f'QEMU VM "{self._name}" started PID={self._process.pid}') self._command_line_changed = False self.status = "started" @@ -1106,9 +1184,9 @@ class QemuVM(BaseNode): for adapter_number, adapter in enumerate(self._ethernet_adapters): nio = adapter.get_nio(0) if nio: - await self.add_ubridge_udp_connection(f"QEMU-{self._id}-{adapter_number}", - self._local_udp_tunnels[adapter_number][1], - nio) + await self.add_ubridge_udp_connection( + f"QEMU-{self._id}-{adapter_number}", self._local_udp_tunnels[adapter_number][1], nio + ) if nio.suspend and self._replicate_network_connection_state: set_link_commands.append(f"set_link gns3-{adapter_number} off") elif self._replicate_network_connection_state: @@ -1136,7 +1214,10 @@ class QemuVM(BaseNode): await self.stop() # A return code of 1 seem fine on Windows if returncode != 0 and (not sys.platform.startswith("win") or returncode != 1): - self.project.emit("log.error", {"message": f"QEMU process has stopped, return code: {returncode}\n{self.read_stdout()}"}) + self.project.emit( + "log.error", + {"message": f"QEMU process has stopped, return code: {returncode}\n{self.read_stdout()}"}, + ) async def stop(self): """ @@ -1209,10 +1290,15 @@ class QemuVM(BaseNode): break if not connection_success: - log.warning("Could not connect to QEMU monitor on {}:{}: {}".format(self._monitor_host, self._monitor, - last_exception)) + log.warning( + "Could not connect to QEMU monitor on {}:{}: {}".format( + self._monitor_host, self._monitor, last_exception + ) + ) else: - log.info(f"Connected to QEMU monitor on {self._monitor_host}:{self._monitor} after {time.time() - begin:.4f} seconds") + log.info( + f"Connected to QEMU monitor on {self._monitor_host}:{self._monitor} after {time.time() - begin:.4f} seconds" + ) return reader, writer async def _control_vm(self, command, expected=None): @@ -1233,7 +1319,7 @@ class QemuVM(BaseNode): return result try: - cmd_byte = command.encode('ascii') + cmd_byte = command.encode("ascii") writer.write(cmd_byte + b"\n") if not expected: while True: @@ -1279,7 +1365,7 @@ class QemuVM(BaseNode): for command in commands: log.info(f"Execute QEMU monitor command: {command}") try: - cmd_byte = command.encode('ascii') + cmd_byte = command.encode("ascii") writer.write(cmd_byte + b"\n") while True: line = await asyncio.wait_for(reader.readline(), timeout=3) # echo of the command @@ -1299,7 +1385,7 @@ class QemuVM(BaseNode): if not (await super().close()): return False - #FIXME: Don't wait for ACPI shutdown when closing the project, should we? + # FIXME: Don't wait for ACPI shutdown when closing the project, should we? if self.on_close == "shutdown_signal": self.on_close = "power_off" await self.stop() @@ -1325,15 +1411,29 @@ class QemuVM(BaseNode): :returns: status (string) """ - result = await self._control_vm("info status", [ - b"debug", b"inmigrate", b"internal-error", b"io-error", - b"paused", b"postmigrate", b"prelaunch", b"finish-migrate", - b"restore-vm", b"running", b"save-vm", b"shutdown", b"suspended", - b"watchdog", b"guest-panicked" - ]) + result = await self._control_vm( + "info status", + [ + b"debug", + b"inmigrate", + b"internal-error", + b"io-error", + b"paused", + b"postmigrate", + b"prelaunch", + b"finish-migrate", + b"restore-vm", + b"running", + b"save-vm", + b"shutdown", + b"suspended", + b"watchdog", + b"guest-panicked", + ], + ) if result is None: return result - status = result.rsplit(' ', 1)[1] + status = result.rsplit(" ", 1)[1] if status == "running" or status == "prelaunch": self.status = "started" elif status == "suspended": @@ -1396,25 +1496,32 @@ class QemuVM(BaseNode): try: adapter = self._ethernet_adapters[adapter_number] except IndexError: - raise QemuError('Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format(name=self._name, - adapter_number=adapter_number)) + raise QemuError( + 'Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format( + name=self._name, adapter_number=adapter_number + ) + ) if self.is_running(): try: - await self.add_ubridge_udp_connection(f"QEMU-{self._id}-{adapter_number}", - self._local_udp_tunnels[adapter_number][1], - nio) + await self.add_ubridge_udp_connection( + f"QEMU-{self._id}-{adapter_number}", self._local_udp_tunnels[adapter_number][1], nio + ) if self._replicate_network_connection_state: await self._control_vm(f"set_link gns3-{adapter_number} on") except (IndexError, KeyError): - raise QemuError('Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format(name=self._name, - adapter_number=adapter_number)) + raise QemuError( + 'Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format( + name=self._name, adapter_number=adapter_number + ) + ) adapter.add_nio(0, nio) - log.info('QEMU VM "{name}" [{id}]: {nio} added to adapter {adapter_number}'.format(name=self._name, - id=self._id, - nio=nio, - adapter_number=adapter_number)) + log.info( + 'QEMU 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): """ @@ -1426,17 +1533,20 @@ class QemuVM(BaseNode): if self.is_running(): try: - await self.update_ubridge_udp_connection(f"QEMU-{self._id}-{adapter_number}", - self._local_udp_tunnels[adapter_number][1], - nio) + await self.update_ubridge_udp_connection( + f"QEMU-{self._id}-{adapter_number}", self._local_udp_tunnels[adapter_number][1], nio + ) if self._replicate_network_connection_state: if nio.suspend: await self._control_vm(f"set_link gns3-{adapter_number} off") else: await self._control_vm(f"set_link gns3-{adapter_number} on") except IndexError: - raise QemuError('Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format(name=self._name, - adapter_number=adapter_number)) + raise QemuError( + 'Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format( + name=self._name, adapter_number=adapter_number + ) + ) async def adapter_remove_nio_binding(self, adapter_number): """ @@ -1450,8 +1560,11 @@ class QemuVM(BaseNode): try: adapter = self._ethernet_adapters[adapter_number] except IndexError: - raise QemuError('Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format(name=self._name, - adapter_number=adapter_number)) + raise QemuError( + 'Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format( + name=self._name, adapter_number=adapter_number + ) + ) await self.stop_capture(adapter_number) if self.is_running(): @@ -1464,10 +1577,11 @@ class QemuVM(BaseNode): self.manager.port_manager.release_udp_port(nio.lport, self._project) adapter.remove_nio(0) - log.info('QEMU VM "{name}" [{id}]: {nio} removed from adapter {adapter_number}'.format(name=self._name, - id=self._id, - nio=nio, - adapter_number=adapter_number)) + log.info( + 'QEMU 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): @@ -1482,8 +1596,11 @@ class QemuVM(BaseNode): try: adapter = self._ethernet_adapters[adapter_number] except IndexError: - raise QemuError('Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format(name=self._name, - adapter_number=adapter_number)) + raise QemuError( + 'Adapter {adapter_number} does not exist on QEMU VM "{name}"'.format( + name=self._name, adapter_number=adapter_number + ) + ) nio = adapter.get_nio(0) @@ -1506,12 +1623,17 @@ class QemuVM(BaseNode): nio.start_packet_capture(output_file) if self.ubridge: - await self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(name=f"QEMU-{self._id}-{adapter_number}", - output_file=output_file)) + await self._ubridge_send( + 'bridge start_capture {name} "{output_file}"'.format( + name=f"QEMU-{self._id}-{adapter_number}", output_file=output_file + ) + ) - log.info("QEMU VM '{name}' [{id}]: starting packet capture on adapter {adapter_number}".format(name=self.name, - id=self.id, - adapter_number=adapter_number)) + log.info( + "QEMU 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): """ @@ -1526,11 +1648,13 @@ class QemuVM(BaseNode): nio.stop_packet_capture() if self.ubridge: - await self._ubridge_send('bridge stop_capture {name}'.format(name=f"QEMU-{self._id}-{adapter_number}")) + await self._ubridge_send("bridge stop_capture {name}".format(name=f"QEMU-{self._id}-{adapter_number}")) - log.info("QEMU VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format(name=self.name, - id=self.id, - adapter_number=adapter_number)) + log.info( + "QEMU VM '{name}' [{id}]: stopping packet capture on adapter {adapter_number}".format( + name=self.name, id=self.id, adapter_number=adapter_number + ) + ) @property def started(self): @@ -1634,9 +1758,7 @@ class QemuVM(BaseNode): console_host = "::" else: raise QemuError("IPv6 must be enabled in order to use the SPICE console") - return ["-spice", - f"addr={console_host},port={port},disable-ticketing", - "-vga", "qxl"] + return ["-spice", f"addr={console_host},port={port},disable-ticketing", "-vga", "qxl"] else: return [] @@ -1645,13 +1767,22 @@ class QemuVM(BaseNode): spice_options = self._spice_options(port) if spice_options: # agent options (mouse/screen) - agent_options = ["-device", "virtio-serial", - "-chardev", "spicevmc,id=vdagent,debug=0,name=vdagent", - "-device", "virtserialport,chardev=vdagent,name=com.redhat.spice.0"] + agent_options = [ + "-device", + "virtio-serial", + "-chardev", + "spicevmc,id=vdagent,debug=0,name=vdagent", + "-device", + "virtserialport,chardev=vdagent,name=com.redhat.spice.0", + ] spice_options.extend(agent_options) # folder sharing options - folder_sharing_options = ["-chardev", "spiceport,name=org.spice-space.webdav.0,id=charchannel0", - "-device", "virtserialport,chardev=charchannel0,id=channel0,name=org.spice-space.webdav.0"] + folder_sharing_options = [ + "-chardev", + "spiceport,name=org.spice-space.webdav.0,id=charchannel0", + "-device", + "virtserialport,chardev=charchannel0,id=channel0,name=org.spice-space.webdav.0", + ] spice_options.extend(folder_sharing_options) return spice_options @@ -1720,7 +1851,9 @@ class QemuVM(BaseNode): command_string = " ".join(shlex_quote(s) for s in command) log.info(f"Executing qemu-img with: {command_string}") with open(self._qemu_img_stdout_file, "w", encoding="utf-8") as fd: - process = await asyncio.create_subprocess_exec(*command, stdout=fd, stderr=subprocess.STDOUT, cwd=self.working_dir) + process = await asyncio.create_subprocess_exec( + *command, stdout=fd, stderr=subprocess.STDOUT, cwd=self.working_dir + ) retcode = await process.wait() log.info(f"{self._get_qemu_img()} returned with {retcode}") return retcode @@ -1732,9 +1865,9 @@ class QemuVM(BaseNode): retcode = await self._qemu_img_exec(command) if retcode: stdout = self.read_qemu_img_stdout() - raise QemuError("Could not create '{}' disk image: qemu-img returned with {}\n{}".format(disk_name, - retcode, - stdout)) + raise QemuError( + "Could not create '{}' disk image: qemu-img returned with {}\n{}".format(disk_name, retcode, stdout) + ) except (OSError, subprocess.SubprocessError) as e: stdout = self.read_qemu_img_stdout() raise QemuError(f"Could not create '{disk_name}' disk image: {e}\n{stdout}") @@ -1748,15 +1881,19 @@ class QemuVM(BaseNode): if signature != 0xAA55: raise OSError(f"mcopy failure: {image}: invalid MBR") if part_type not in (1, 4, 6, 11, 12, 14): - raise OSError("mcopy failure: {}: invalid partition type {:02X}" - .format(image, part_type)) + raise OSError("mcopy failure: {}: invalid partition type {:02X}".format(image, part_type)) part_image = image + f"@@{offset}S" process = await asyncio.create_subprocess_exec( - "mcopy", "-i", part_image, *args, + "mcopy", + "-i", + part_image, + *args, stdin=subprocess.DEVNULL, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT, - cwd=self.working_dir) + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + cwd=self.working_dir, + ) (stdout, _) = await process.communicate() retcode = process.returncode except (OSError, subprocess.SubprocessError) as e: @@ -1800,8 +1937,7 @@ class QemuVM(BaseNode): os.mkdir(config_dir) shutil.copyfile(getattr(self, "config_disk_image"), disk_tmp) unpack_zip(zip_file, config_dir) - config_files = [os.path.join(config_dir, fname) - for fname in os.listdir(config_dir)] + config_files = [os.path.join(config_dir, fname) for fname in os.listdir(config_dir)] if config_files: await self._mcopy(disk_tmp, "-s", "-m", "-o", "--", *config_files, "::/") os.replace(disk_tmp, disk) @@ -1825,27 +1961,51 @@ class QemuVM(BaseNode): if interface == "sata": # special case, sata controller doesn't exist in Qemu - options.extend(["-device", f'ahci,id=ahci{disk_index}']) - options.extend(["-drive", f'file={disk},if=none,id=drive{disk_index},index={disk_index},media=disk{extra_drive_options}']) + options.extend(["-device", f"ahci,id=ahci{disk_index}"]) + options.extend( + [ + "-drive", + f"file={disk},if=none,id=drive{disk_index},index={disk_index},media=disk{extra_drive_options}", + ] + ) qemu_version = await self.manager.get_qemu_version(self.qemu_path) if qemu_version and parse_version(qemu_version) >= parse_version("4.2.0"): # The ‘ide-drive’ device is deprecated since version 4.2.0 # https://qemu.readthedocs.io/en/latest/system/deprecated.html#ide-drive-since-4-2 - options.extend(["-device", f'ide-hd,drive=drive{disk_index},bus=ahci{disk_index}.0,id=drive{disk_index}']) + options.extend( + ["-device", f"ide-hd,drive=drive{disk_index},bus=ahci{disk_index}.0,id=drive{disk_index}"] + ) else: - options.extend(["-device", f'ide-drive,drive=drive{disk_index},bus=ahci{disk_index}.0,id=drive{disk_index}']) + options.extend( + ["-device", f"ide-drive,drive=drive{disk_index},bus=ahci{disk_index}.0,id=drive{disk_index}"] + ) elif interface == "nvme": - options.extend(["-drive", f'file={disk},if=none,id=drive{disk_index},index={disk_index},media=disk{extra_drive_options}']) - options.extend(["-device", f'nvme,drive=drive{disk_index},serial={disk_index}']) + options.extend( + [ + "-drive", + f"file={disk},if=none,id=drive{disk_index},index={disk_index},media=disk{extra_drive_options}", + ] + ) + options.extend(["-device", f"nvme,drive=drive{disk_index},serial={disk_index}"]) elif interface == "scsi": - options.extend(["-device", f'virtio-scsi-pci,id=scsi{disk_index}']) - options.extend(["-drive", f'file={disk},if=none,id=drive{disk_index},index={disk_index},media=disk{extra_drive_options}']) - options.extend(["-device", f'scsi-hd,drive=drive{disk_index}']) - #elif interface == "sd": + options.extend(["-device", f"virtio-scsi-pci,id=scsi{disk_index}"]) + options.extend( + [ + "-drive", + f"file={disk},if=none,id=drive{disk_index},index={disk_index},media=disk{extra_drive_options}", + ] + ) + options.extend(["-device", f"scsi-hd,drive=drive{disk_index}"]) + # elif interface == "sd": # options.extend(["-drive", 'file={},id=drive{},index={}{}'.format(disk, disk_index, disk_index, extra_drive_options)]) # options.extend(["-device", 'sd-card,drive=drive{},id=drive{}'.format(disk_index, disk_index, disk_index)]) else: - options.extend(["-drive", f'file={disk},if={interface},index={disk_index},media=disk,id=drive{disk_index}{extra_drive_options}']) + options.extend( + [ + "-drive", + f"file={disk},if={interface},index={disk_index},media=disk,id=drive{disk_index}{extra_drive_options}", + ] + ) return options async def _disk_options(self): @@ -1856,7 +2016,7 @@ class QemuVM(BaseNode): for disk_index, drive in enumerate(drives): # prioritize config disk over harddisk d - if drive == 'd' and self._create_config_disk: + if drive == "d" and self._create_config_disk: continue disk_image = getattr(self, f"_hd{drive}_disk_image") @@ -1872,7 +2032,9 @@ class QemuVM(BaseNode): disk_name = "hd" + drive if not os.path.isfile(disk_image) or not os.path.exists(disk_image): if os.path.islink(disk_image): - raise QemuError(f"{disk_name} disk image '{disk_image}' linked to '{os.path.realpath(disk_image)}' is not accessible") + raise QemuError( + f"{disk_name} disk image '{disk_image}' linked to '{os.path.realpath(disk_image)}' is not accessible" + ) else: raise QemuError(f"{disk_name} disk image '{disk_image}' is not accessible") else: @@ -1883,12 +2045,18 @@ class QemuVM(BaseNode): # image has leaked clusters, but is not corrupted, let's try to fix it log.warning(f"Qemu image {disk_image} has leaked clusters") if (await self._qemu_img_exec([qemu_img_path, "check", "-r", "leaks", f"{disk_image}"])) == 3: - self.project.emit("log.warning", {"message": f"Qemu image '{disk_image}' has leaked clusters and could not be fixed"}) + self.project.emit( + "log.warning", + {"message": f"Qemu image '{disk_image}' has leaked clusters and could not be fixed"}, + ) elif retcode == 2: # image is corrupted, let's try to fix it log.warning(f"Qemu image {disk_image} is corrupted") if (await self._qemu_img_exec([qemu_img_path, "check", "-r", "all", f"{disk_image}"])) == 2: - self.project.emit("log.warning", {"message": f"Qemu image '{disk_image}' is corrupted and could not be fixed"}) + self.project.emit( + "log.warning", + {"message": f"Qemu image '{disk_image}' is corrupted and could not be fixed"}, + ) except (OSError, subprocess.SubprocessError) as e: stdout = self.read_qemu_img_stdout() raise QemuError(f"Could not check '{disk_name}' disk image: {e}\n{stdout}") @@ -1956,7 +2124,9 @@ class QemuVM(BaseNode): if self._cdrom_image: if not os.path.isfile(self._cdrom_image) or not os.path.exists(self._cdrom_image): if os.path.islink(self._cdrom_image): - raise QemuError(f"cdrom image '{self._cdrom_image}' linked to '{os.path.realpath(self._cdrom_image)}' is not accessible") + raise QemuError( + f"cdrom image '{self._cdrom_image}' linked to '{os.path.realpath(self._cdrom_image)}' is not accessible" + ) else: raise QemuError(f"cdrom image '{self._cdrom_image}' is not accessible") if self._hdc_disk_image: @@ -1970,7 +2140,9 @@ class QemuVM(BaseNode): if self._bios_image: if not os.path.isfile(self._bios_image) or not os.path.exists(self._bios_image): if os.path.islink(self._bios_image): - raise QemuError(f"bios image '{self._bios_image}' linked to '{os.path.realpath(self._bios_image)}' is not accessible") + raise QemuError( + f"bios image '{self._bios_image}' linked to '{os.path.realpath(self._bios_image)}' is not accessible" + ) else: raise QemuError(f"bios image '{self._bios_image}' is not accessible") options.extend(["-bios", self._bios_image.replace(",", ",,")]) @@ -1982,14 +2154,18 @@ class QemuVM(BaseNode): if self._initrd: if not os.path.isfile(self._initrd) or not os.path.exists(self._initrd): if os.path.islink(self._initrd): - raise QemuError(f"initrd file '{self._initrd}' linked to '{os.path.realpath(self._initrd)}' is not accessible") + raise QemuError( + f"initrd file '{self._initrd}' linked to '{os.path.realpath(self._initrd)}' is not accessible" + ) else: raise QemuError(f"initrd file '{self._initrd}' is not accessible") options.extend(["-initrd", self._initrd.replace(",", ",,")]) if self._kernel_image: if not os.path.isfile(self._kernel_image) or not os.path.exists(self._kernel_image): if os.path.islink(self._kernel_image): - raise QemuError(f"kernel image '{self._kernel_image}' linked to '{os.path.realpath(self._kernel_image)}' is not accessible") + raise QemuError( + f"kernel image '{self._kernel_image}' linked to '{os.path.realpath(self._kernel_image)}' is not accessible" + ) else: raise QemuError(f"kernel image '{self._kernel_image}' is not accessible") options.extend(["-kernel", self._kernel_image.replace(",", ",,")]) @@ -2001,7 +2177,9 @@ class QemuVM(BaseNode): async def _network_options(self): network_options = [] - network_options.extend(["-net", "none"]) # we do not want any user networking back-end if no adapter is connected. + network_options.extend( + ["-net", "none"] + ) # we do not want any user networking back-end if no adapter is connected. patched_qemu = False if self._legacy_networking: @@ -2020,7 +2198,9 @@ class QemuVM(BaseNode): if pci_bridges >= 1: qemu_version = await self.manager.get_qemu_version(self.qemu_path) if qemu_version and parse_version(qemu_version) < parse_version("2.4.0"): - raise QemuError("Qemu version 2.4 or later is required to run this VM with a large number of network adapters") + raise QemuError( + "Qemu version 2.4 or later is required to run this VM with a large number of network adapters" + ) pci_device_id = 4 + pci_bridges # Bridge consume PCI ports for adapter_number, adapter in enumerate(self._ethernet_adapters): @@ -2044,19 +2224,24 @@ class QemuVM(BaseNode): if isinstance(nio, NIOUDP): if patched_qemu: # use patched Qemu syntax - network_options.extend(["-net", "udp,vlan={},name=gns3-{},sport={},dport={},daddr={}".format(adapter_number, - adapter_number, - nio.lport, - nio.rport, - nio.rhost)]) + network_options.extend( + [ + "-net", + "udp,vlan={},name=gns3-{},sport={},dport={},daddr={}".format( + adapter_number, adapter_number, nio.lport, nio.rport, nio.rhost + ), + ] + ) else: # use UDP tunnel support added in Qemu 1.1.0 - network_options.extend(["-net", "socket,vlan={},name=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number, - adapter_number, - nio.rhost, - nio.rport, - "127.0.0.1", - nio.lport)]) + network_options.extend( + [ + "-net", + "socket,vlan={},name=gns3-{},udp={}:{},localaddr={}:{}".format( + adapter_number, adapter_number, nio.rhost, nio.rport, "127.0.0.1", nio.lport + ), + ] + ) elif isinstance(nio, NIOTAP): network_options.extend(["-net", f"tap,name=gns3-{adapter_number},ifname={nio.tap_device}"]) else: @@ -2069,7 +2254,14 @@ class QemuVM(BaseNode): if bridge_id > 0: if pci_bridges_created < bridge_id: network_options.extend(["-device", f"i82801b11-bridge,id=dmi_pci_bridge{bridge_id}"]) - network_options.extend(["-device", "pci-bridge,id=pci-bridge{bridge_id},bus=dmi_pci_bridge{bridge_id},chassis_nr=0x1,addr=0x{bridge_id},shpc=off".format(bridge_id=bridge_id)]) + network_options.extend( + [ + "-device", + "pci-bridge,id=pci-bridge{bridge_id},bus=dmi_pci_bridge{bridge_id},chassis_nr=0x1,addr=0x{bridge_id},shpc=off".format( + bridge_id=bridge_id + ), + ] + ) pci_bridges_created += 1 addr = pci_device_id % 32 device_string = f"{device_string},bus=pci-bridge{bridge_id},addr=0x{addr:02x}" @@ -2077,13 +2269,18 @@ class QemuVM(BaseNode): if nio: network_options.extend(["-device", f"{device_string},netdev=gns3-{adapter_number}"]) if isinstance(nio, NIOUDP): - network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number, - nio.rhost, - nio.rport, - "127.0.0.1", - nio.lport)]) + network_options.extend( + [ + "-netdev", + "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format( + adapter_number, nio.rhost, nio.rport, "127.0.0.1", nio.lport + ), + ] + ) elif isinstance(nio, NIOTAP): - network_options.extend(["-netdev", f"tap,id=gns3-{adapter_number},ifname={nio.tap_device},script=no,downscript=no"]) + network_options.extend( + ["-netdev", f"tap,id=gns3-{adapter_number},ifname={nio.tap_device},script=no,downscript=no"] + ) else: network_options.extend(["-device", device_string]) @@ -2116,18 +2313,29 @@ class QemuVM(BaseNode): if enable_hardware_accel and "-no-kvm" not in options and "-no-hax" not in options: # Turn OFF hardware acceleration for non x86 architectures if sys.platform.startswith("win"): - supported_binaries = ["qemu-system-x86_64.exe", "qemu-system-x86_64w.exe", "qemu-system-i386.exe", "qemu-system-i386w.exe"] + supported_binaries = [ + "qemu-system-x86_64.exe", + "qemu-system-x86_64w.exe", + "qemu-system-i386.exe", + "qemu-system-i386w.exe", + ] else: supported_binaries = ["qemu-system-x86_64", "qemu-system-i386", "qemu-kvm"] if os.path.basename(qemu_path) not in supported_binaries: if require_hardware_accel: - raise QemuError("Hardware acceleration can only be used with the following Qemu executables: {}".format(", ".join(supported_binaries))) + raise QemuError( + "Hardware acceleration can only be used with the following Qemu executables: {}".format( + ", ".join(supported_binaries) + ) + ) else: return False if sys.platform.startswith("linux") and not os.path.exists("/dev/kvm"): if require_hardware_accel: - raise QemuError("KVM acceleration cannot be used (/dev/kvm doesn't exist). It is possible to turn off KVM support in the gns3_server.conf by adding enable_hardware_acceleration = false to the [Qemu] section.") + raise QemuError( + "KVM acceleration cannot be used (/dev/kvm doesn't exist). It is possible to turn off KVM support in the gns3_server.conf by adding enable_hardware_acceleration = false to the [Qemu] section." + ) else: return False elif sys.platform.startswith("win"): @@ -2135,7 +2343,9 @@ class QemuVM(BaseNode): # HAXM is only available starting with Qemu version 2.9.0 version = await self.manager.get_qemu_version(self.qemu_path) if version and parse_version(version) < parse_version("2.9.0"): - raise QemuError(f"HAXM acceleration can only be enable for Qemu version 2.9.0 and above (current version: {version})") + raise QemuError( + f"HAXM acceleration can only be enable for Qemu version 2.9.0 and above (current version: {version})" + ) # check if HAXM is installed version = self.manager.get_haxm_windows_version() @@ -2145,6 +2355,7 @@ class QemuVM(BaseNode): # check if the HAXM service is running from gns3server.utils.windows_service import check_windows_service_is_running + if not check_windows_service_is_running("intelhaxm"): raise QemuError("Intel HAXM service is not running on this host") @@ -2155,7 +2366,9 @@ class QemuVM(BaseNode): await process.wait() if process.returncode != 0: if require_hardware_accel: - raise QemuError("HAXM acceleration support is not installed on this host (com.intel.kext.intelhaxm extension not loaded)") + raise QemuError( + "HAXM acceleration support is not installed on this host (com.intel.kext.intelhaxm extension not loaded)" + ) else: return False return True @@ -2181,7 +2394,9 @@ class QemuVM(BaseNode): try: json_data = json.loads(output) except ValueError as e: - raise QemuError(f"Invalid JSON data returned by qemu-img while looking for the Qemu VM saved state snapshot: {e}") + raise QemuError( + f"Invalid JSON data returned by qemu-img while looking for the Qemu VM saved state snapshot: {e}" + ) if "snapshots" in json_data: for snapshot in json_data["snapshots"]: if snapshot["name"] == snapshot_name: @@ -2216,13 +2431,17 @@ class QemuVM(BaseNode): try: json_data = json.loads(output) except ValueError as e: - raise QemuError(f"Invalid JSON data returned by qemu-img while looking for the Qemu VM saved state snapshot: {e}") + raise QemuError( + f"Invalid JSON data returned by qemu-img while looking for the Qemu VM saved state snapshot: {e}" + ) if "snapshots" in json_data: for snapshot in json_data["snapshots"]: if snapshot["name"] == snapshot_name: - log.info('QEMU VM "{name}" [{id}] VM saved state detected (snapshot name: {snapshot})'.format(name=self._name, - id=self.id, - snapshot=snapshot_name)) + log.info( + 'QEMU VM "{name}" [{id}] VM saved state detected (snapshot name: {snapshot})'.format( + name=self._name, id=self.id, snapshot=snapshot_name + ) + ) return ["-loadvm", snapshot_name.replace(",", ",,")] except subprocess.SubprocessError as e: @@ -2253,7 +2472,7 @@ class QemuVM(BaseNode): if self._cpus > maxcpus: maxcpus = self._cpus command.extend(["-smp", f"cpus={self._cpus},maxcpus={maxcpus},sockets=1"]) - if (await self._run_with_hardware_acceleration(self.qemu_path, self._options)): + if await self._run_with_hardware_acceleration(self.qemu_path, self._options): if sys.platform.startswith("linux"): command.extend(["-enable-kvm"]) version = await self.manager.get_qemu_version(self.qemu_path) @@ -2288,11 +2507,7 @@ class QemuVM(BaseNode): return command def __json__(self): - answer = { - "project_id": self.project.id, - "node_id": self.id, - "node_directory": self.working_path - } + answer = {"project_id": self.project.id, "node_id": self.id, "node_directory": self.working_path} # Qemu has a long list of options. The JSON schema is the single source of information for field in Qemu.schema()["properties"]: if field not in answer: diff --git a/gns3server/compute/qemu/utils/guest_cid.py b/gns3server/compute/qemu/utils/guest_cid.py index 32d83ea8..b8b34b1d 100644 --- a/gns3server/compute/qemu/utils/guest_cid.py +++ b/gns3server/compute/qemu/utils/guest_cid.py @@ -17,6 +17,7 @@ from ..qemu_error import QemuError import logging + log = logging.getLogger(__name__) diff --git a/gns3server/compute/qemu/utils/qcow2.py b/gns3server/compute/qemu/utils/qcow2.py index 4df55aa6..1119bb60 100644 --- a/gns3server/compute/qemu/utils/qcow2.py +++ b/gns3server/compute/qemu/utils/qcow2.py @@ -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) diff --git a/gns3server/compute/qemu/utils/ziputils.py b/gns3server/compute/qemu/utils/ziputils.py index 3ff8c999..3208da10 100644 --- a/gns3server/compute/qemu/utils/ziputils.py +++ b/gns3server/compute/qemu/utils/ziputils.py @@ -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)) diff --git a/gns3server/compute/traceng/__init__.py b/gns3server/compute/traceng/__init__.py index ff172156..c6c24b43 100644 --- a/gns3server/compute/traceng/__init__.py +++ b/gns3server/compute/traceng/__init__.py @@ -40,4 +40,4 @@ class TraceNG(BaseManager): :returns: TraceNGVM instance """ - return (await super().create_node(*args, **kwargs)) + return await super().create_node(*args, **kwargs) diff --git a/gns3server/compute/traceng/traceng_vm.py b/gns3server/compute/traceng/traceng_vm.py index e8092a14..15cd4d70 100644 --- a/gns3server/compute/traceng/traceng_vm.py +++ b/gns3server/compute/traceng/traceng_vm.py @@ -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}") diff --git a/gns3server/compute/ubridge/hypervisor.py b/gns3server/compute/ubridge/hypervisor.py index eacef080..832f0af6 100644 --- a/gns3server/compute/ubridge/hypervisor.py +++ b/gns3server/compute/ubridge/hypervisor.py @@ -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}") diff --git a/gns3server/compute/ubridge/ubridge_error.py b/gns3server/compute/ubridge/ubridge_error.py index 3b02ec7b..00d856ee 100644 --- a/gns3server/compute/ubridge/ubridge_error.py +++ b/gns3server/compute/ubridge/ubridge_error.py @@ -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 diff --git a/gns3server/compute/ubridge/ubridge_hypervisor.py b/gns3server/compute/ubridge/ubridge_hypervisor.py index 4e927d22..a44bf383 100644 --- a/gns3server/compute/ubridge/ubridge_hypervisor.py +++ b/gns3server/compute/ubridge/ubridge_hypervisor.py @@ -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 diff --git a/gns3server/compute/virtualbox/__init__.py b/gns3server/compute/virtualbox/__init__.py index 709e28e3..561a9051 100644 --- a/gns3server/compute/virtualbox/__init__.py +++ b/gns3server/compute/virtualbox/__init__.py @@ -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 diff --git a/gns3server/compute/virtualbox/virtualbox_vm.py b/gns3server/compute/virtualbox/virtualbox_vm.py index acd248fb..0174b32a 100644 --- a/gns3server/compute/virtualbox/virtualbox_vm.py +++ b/gns3server/compute/virtualbox/virtualbox_vm.py @@ -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 + ) + ) diff --git a/gns3server/compute/vmware/__init__.py b/gns3server/compute/vmware/__init__.py index 8a64299d..569c642e 100644 --- a/gns3server/compute/vmware/__init__.py +++ b/gns3server/compute/vmware/__init__.py @@ -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") diff --git a/gns3server/compute/vmware/vmware_vm.py b/gns3server/compute/vmware/vmware_vm.py index 5b8a63fa..f2262e4c 100644 --- a/gns3server/compute/vmware/vmware_vm.py +++ b/gns3server/compute/vmware/vmware_vm.py @@ -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 + ) + ) diff --git a/gns3server/compute/vpcs/vpcs_vm.py b/gns3server/compute/vpcs/vpcs_vm.py index 65466e02..a5acb49f 100644 --- a/gns3server/compute/vpcs/vpcs_vm.py +++ b/gns3server/compute/vpcs/vpcs_vm.py @@ -40,11 +40,12 @@ from ..base_node import BaseNode import logging + log = logging.getLogger(__name__) class VPCSVM(BaseNode): - module_name = 'vpcs' + module_name = "vpcs" """ VPCS VM implementation. @@ -121,14 +122,16 @@ class VPCSVM(BaseNode): def __json__(self): - return {"name": self.name, - "node_id": self.id, - "node_directory": self.working_path, - "status": self.status, - "console": self._console, - "console_type": self._console_type, - "project_id": self.project.id, - "command_line": self.command_line} + return { + "name": self.name, + "node_id": self.id, + "node_directory": self.working_path, + "status": self.status, + "console": self._console, + "console_type": self._console_type, + "project_id": self.project.id, + "command_line": self.command_line, + } def _vpcs_path(self): """ @@ -153,7 +156,7 @@ class VPCSVM(BaseNode): if self.script_file: content = self.startup_script content = content.replace(self._name, new_name) - escaped_name = new_name.replace('\\', '') + escaped_name = new_name.replace("\\", "") content = re.sub(r"^set pcname .+$", "set pcname " + escaped_name, content, flags=re.MULTILINE) self.startup_script = content @@ -184,10 +187,10 @@ class VPCSVM(BaseNode): """ try: - startup_script_path = os.path.join(self.working_dir, 'startup.vpc') - with open(startup_script_path, "w+", encoding='utf-8') as f: + startup_script_path = os.path.join(self.working_dir, "startup.vpc") + with open(startup_script_path, "w+", encoding="utf-8") as f: if startup_script is None: - f.write('') + f.write("") else: startup_script = startup_script.replace("%h", self._name) f.write(startup_script) @@ -228,12 +231,10 @@ class VPCSVM(BaseNode): if sys.platform.startswith("win32"): flags = subprocess.CREATE_NEW_PROCESS_GROUP with open(self._vpcs_stdout_file, "w", encoding="utf-8") as fd: - self.command_line = ' '.join(command) - self._process = await asyncio.create_subprocess_exec(*command, - stdout=fd, - stderr=subprocess.STDOUT, - cwd=self.working_dir, - creationflags=flags) + self.command_line = " ".join(command) + self._process = await asyncio.create_subprocess_exec( + *command, stdout=fd, stderr=subprocess.STDOUT, cwd=self.working_dir, creationflags=flags + ) monitor_process(self._process, self._termination_callback) await self._start_ubridge() @@ -265,7 +266,10 @@ class VPCSVM(BaseNode): await self._stop_ubridge() await super().stop() if returncode != 0: - self.project.emit("log.error", {"message": f"VPCS process has stopped, return code: {returncode}\n{self.read_vpcs_stdout()}"}) + self.project.emit( + "log.error", + {"message": f"VPCS process has stopped, return code: {returncode}\n{self.read_vpcs_stdout()}"}, + ) async def stop(self): """ @@ -365,17 +369,21 @@ class VPCSVM(BaseNode): """ if not self._ethernet_adapter.port_exists(port_number): - raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter, - port_number=port_number)) + raise VPCSError( + "Port {port_number} doesn't exist on adapter {adapter}".format( + adapter=self._ethernet_adapter, port_number=port_number + ) + ) if self.is_running(): await self.add_ubridge_udp_connection(f"VPCS-{self._id}", self._local_udp_tunnel[1], nio) self._ethernet_adapter.add_nio(port_number, nio) - log.info('VPCS "{name}" [{id}]: {nio} added to port {port_number}'.format(name=self._name, - id=self.id, - nio=nio, - port_number=port_number)) + log.info( + 'VPCS "{name}" [{id}]: {nio} added to port {port_number}'.format( + name=self._name, id=self.id, nio=nio, port_number=port_number + ) + ) return nio @@ -388,8 +396,11 @@ class VPCSVM(BaseNode): """ if not self._ethernet_adapter.port_exists(port_number): - raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter, - port_number=port_number)) + raise VPCSError( + "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"VPCS-{self._id}", self._local_udp_tunnel[1], nio) @@ -403,8 +414,11 @@ class VPCSVM(BaseNode): """ if not self._ethernet_adapter.port_exists(port_number): - raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter, - port_number=port_number)) + raise VPCSError( + "Port {port_number} doesn't exist on adapter {adapter}".format( + adapter=self._ethernet_adapter, port_number=port_number + ) + ) await self.stop_capture(port_number) if self.is_running(): @@ -415,10 +429,11 @@ class VPCSVM(BaseNode): self.manager.port_manager.release_udp_port(nio.lport, self._project) self._ethernet_adapter.remove_nio(port_number) - log.info('VPCS "{name}" [{id}]: {nio} removed from port {port_number}'.format(name=self._name, - id=self.id, - nio=nio, - port_number=port_number)) + log.info( + 'VPCS "{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): @@ -431,8 +446,11 @@ class VPCSVM(BaseNode): """ if not self._ethernet_adapter.port_exists(port_number): - raise VPCSError("Port {port_number} doesn't exist on adapter {adapter}".format(adapter=self._ethernet_adapter, - port_number=port_number)) + raise VPCSError( + "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 VPCSError(f"Port {port_number} is not connected") @@ -452,12 +470,15 @@ class VPCSVM(BaseNode): nio.start_packet_capture(output_file) if self.ubridge: - await self._ubridge_send('bridge start_capture {name} "{output_file}"'.format(name=f"VPCS-{self._id}", - output_file=output_file)) + await self._ubridge_send( + 'bridge start_capture {name} "{output_file}"'.format(name=f"VPCS-{self._id}", output_file=output_file) + ) - log.info("VPCS '{name}' [{id}]: starting packet capture on port {port_number}".format(name=self.name, - id=self.id, - port_number=port_number)) + log.info( + "VPCS '{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): """ @@ -472,11 +493,13 @@ class VPCSVM(BaseNode): nio.stop_packet_capture() if self.ubridge: - await self._ubridge_send('bridge stop_capture {name}'.format(name=f"VPCS-{self._id}")) + await self._ubridge_send("bridge stop_capture {name}".format(name=f"VPCS-{self._id}")) - log.info("VPCS '{name}' [{id}]: stopping packet capture on port {port_number}".format(name=self.name, - id=self.id, - port_number=port_number)) + log.info( + "VPCS '{name}' [{id}]: stopping packet capture on port {port_number}".format( + name=self.name, id=self.id, port_number=port_number + ) + ) def _build_command(self): """ @@ -516,7 +539,9 @@ class VPCSVM(BaseNode): command = [self._vpcs_path()] command.extend(["-p", str(self._internal_console_port)]) # listen to console port - command.extend(["-m", str(self._manager.get_mac_id(self.id))]) # the unique ID is used to set the MAC address offset + command.extend( + ["-m", str(self._manager.get_mac_id(self.id))] + ) # the unique ID is used to set the MAC address offset command.extend(["-i", "1"]) # option to start only one VPC instance command.extend(["-F"]) # option to avoid the daemonization of VPCS if self._vpcs_version >= parse_version("0.8b"): @@ -533,7 +558,9 @@ class VPCSVM(BaseNode): command.extend(["-s", str(nio.lport)]) # source UDP port command.extend(["-c", str(nio.rport)]) # destination UDP port try: - command.extend(["-t", socket.gethostbyname(nio.rhost)]) # destination host, we need to resolve the hostname because VPCS doesn't support it + command.extend( + ["-t", socket.gethostbyname(nio.rhost)] + ) # destination host, we need to resolve the hostname because VPCS doesn't support it except socket.gaierror as e: raise VPCSError(f"Can't resolve hostname {nio.rhost}") @@ -550,7 +577,7 @@ class VPCSVM(BaseNode): """ # use the default VPCS file if it exists - path = os.path.join(self.working_dir, 'startup.vpc') + path = os.path.join(self.working_dir, "startup.vpc") if os.path.exists(path): return path else: diff --git a/gns3server/config.py b/gns3server/config.py index cc048aa1..022a3ce1 100644 --- a/gns3server/config.py +++ b/gns3server/config.py @@ -30,6 +30,7 @@ from .version import __version_info__ from .utils.file_watcher import FileWatcher import logging + log = logging.getLogger(__name__) @@ -84,11 +85,13 @@ class Config: server_filename = "gns3_server.ini" if self._files is None and not hasattr(sys, "_called_from_test"): - self._files = [os.path.join(os.getcwd(), server_filename), - os.path.join(versioned_user_dir, server_filename), - os.path.join(appdata, appname + ".ini"), - os.path.join(common_appdata, appname, server_filename), - os.path.join(common_appdata, appname + ".ini")] + self._files = [ + os.path.join(os.getcwd(), server_filename), + os.path.join(versioned_user_dir, server_filename), + os.path.join(appdata, appname + ".ini"), + os.path.join(common_appdata, appname, server_filename), + os.path.join(common_appdata, appname + ".ini"), + ] else: # On UNIX-like platforms, the configuration file location can be one of the following: @@ -109,12 +112,14 @@ class Config: versioned_user_dir = os.path.join(home, ".config", appname, version) if self._files is None and not hasattr(sys, "_called_from_test"): - self._files = [os.path.join(os.getcwd(), server_filename), - os.path.join(versioned_user_dir, server_filename), - os.path.join(home, ".config", appname + ".conf"), - os.path.join("/etc/gns3", server_filename), - os.path.join("/etc/xdg", appname, server_filename), - os.path.join("/etc/xdg", appname + ".conf")] + self._files = [ + os.path.join(os.getcwd(), server_filename), + os.path.join(versioned_user_dir, server_filename), + os.path.join(home, ".config", appname + ".conf"), + os.path.join("/etc/gns3", server_filename), + os.path.join("/etc/xdg", appname, server_filename), + os.path.join("/etc/xdg", appname + ".conf"), + ] if self._files is None: self._files = [] diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index 1018cdd4..46b3c966 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -39,6 +39,7 @@ from .controller_error import ControllerError, ControllerNotFoundError import logging + log = logging.getLogger(__name__) @@ -56,8 +57,7 @@ class Controller: self.gns3vm = GNS3VM(self) self.symbols = Symbols() self._appliance_manager = ApplianceManager() - self._iou_license_settings = {"iourc_content": "", - "license_check": True} + self._iou_license_settings = {"iourc_content": "", "license_check": True} self._config_loaded = False async def start(self, computes=None): @@ -92,19 +92,23 @@ class Controller: log.warning(f"Protocol changed to 'https' for local compute because SSL is enabled") protocol = "https" try: - self._local_server = await self.add_compute(compute_id="local", - name=name, - protocol=protocol, - host=host, - console_host=console_host, - port=port, - user=server_config.user, - password=server_config.password, - force=True, - connect=True, - ssl_context=self._ssl_context) + self._local_server = await self.add_compute( + compute_id="local", + name=name, + protocol=protocol, + host=host, + console_host=console_host, + port=port, + user=server_config.user, + password=server_config.password, + force=True, + connect=True, + ssl_context=self._ssl_context, + ) except ControllerError: - log.fatal(f"Cannot access to the local server, make sure something else is not running on the TCP port {port}") + log.fatal( + f"Cannot access to the local server, make sure something else is not running on the TCP port {port}" + ) sys.exit(1) if computes: @@ -125,6 +129,7 @@ class Controller: def _create_ssl_context(self, server_config): import ssl + ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23) certfile = server_config.certfile certkey = server_config.certkey @@ -204,7 +209,7 @@ class Controller: iourc_path = os.path.join(server_config.secrets_dir, "gns3_iourc_license") try: - with open(iourc_path, 'w+') as f: + with open(iourc_path, "w+") as f: f.write(self._iou_license_settings["iourc_content"]) log.info(f"iourc file '{iourc_path}' saved") except OSError as e: @@ -258,8 +263,8 @@ class Controller: log.error(f"Cannot read IOU license file '{iourc_path}': {e}") self._iou_license_settings["license_check"] = iou_config.license_check - #self._appliance_manager.appliances_etag = controller_config.get("appliances_etag", None) - #self._appliance_manager.load_appliances() + # self._appliance_manager.appliances_etag = controller_config.get("appliances_etag", None) + # self._appliance_manager.load_appliances() self._config_loaded = True async def load_projects(self): @@ -290,7 +295,7 @@ class Controller: """ dst_path = self.configs_path() - src_path = get_resource('configs') + src_path = get_resource("configs") try: for file in os.listdir(src_path): if not os.path.exists(os.path.join(dst_path, file)): @@ -332,12 +337,12 @@ class Controller: if compute_id not in self._computes: # We disallow to create from the outside the local and VM server - if (compute_id == 'local' or compute_id == 'vm') and not force: + if (compute_id == "local" or compute_id == "vm") and not force: return None # It seem we have error with a gns3vm imported as a remote server and conflict # with GNS3 VM settings. That's why we ignore server name gns3vm - if name == 'gns3vm': + if name == "gns3vm": return None for compute in self._computes.values(): @@ -346,7 +351,7 @@ class Controller: compute = Compute(compute_id=compute_id, controller=self, name=name, **kwargs) self._computes[compute.id] = compute - #self.save() + # self.save() if connect: asyncio.get_event_loop().call_later(1, lambda: asyncio.ensure_future(compute.connect())) self.notification.controller_emit("compute.created", compute.__json__()) @@ -392,7 +397,7 @@ class Controller: await self.close_compute_projects(compute) await compute.close() del self._computes[compute_id] - #self.save() + # self.save() self.notification.controller_emit("compute.deleted", compute.__json__()) @property @@ -494,7 +499,9 @@ class Controller: if topo_data["project_id"] in self._projects: project = self._projects[topo_data["project_id"]] else: - project = await self.add_project(path=os.path.dirname(path), status="closed", filename=os.path.basename(path), **topo_data) + project = await self.add_project( + path=os.path.dirname(path), status="closed", filename=os.path.basename(path), **topo_data + ) if load or project.auto_open: await project.open() return project @@ -566,7 +573,7 @@ class Controller: :returns: instance of Controller """ - if not hasattr(Controller, '_instance') or Controller._instance is None: + if not hasattr(Controller, "_instance") or Controller._instance is None: Controller._instance = Controller() return Controller._instance @@ -586,7 +593,9 @@ class Controller: await project.delete() self.remove_project(project) project = await self.add_project(name="AUTOIDLEPC") - node = await project.add_node(compute, "AUTOIDLEPC", str(uuid.uuid4()), node_type="dynamips", platform=platform, image=image, ram=ram) + node = await project.add_node( + compute, "AUTOIDLEPC", str(uuid.uuid4()), node_type="dynamips", platform=platform, image=image, ram=ram + ) res = await node.dynamips_auto_idlepc() await project.delete() self.remove_project(project) diff --git a/gns3server/controller/appliance.py b/gns3server/controller/appliance.py index a1a27a36..d96d436b 100644 --- a/gns3server/controller/appliance.py +++ b/gns3server/controller/appliance.py @@ -19,11 +19,11 @@ import copy import uuid import logging + log = logging.getLogger(__name__) class Appliance: - def __init__(self, appliance_id, data, builtin=True): if appliance_id is None: self._id = str(uuid.uuid4()) @@ -36,7 +36,7 @@ class Appliance: if "appliance_id" in self._data: del self._data["appliance_id"] - if self.status != 'broken': + if self.status != "broken": log.debug(f'Appliance "{self.name}" [{self._id}] loaded') @property diff --git a/gns3server/controller/appliance_manager.py b/gns3server/controller/appliance_manager.py index bf539906..e98274e6 100644 --- a/gns3server/controller/appliance_manager.py +++ b/gns3server/controller/appliance_manager.py @@ -29,6 +29,7 @@ from ..utils.http_client import HTTPClient from .controller_error import ControllerError import logging + log = logging.getLogger(__name__) @@ -82,18 +83,29 @@ class ApplianceManager: """ self._appliances = {} - for directory, builtin in ((get_resource('appliances'), True,), (self.appliances_path(), False,)): + for directory, builtin in ( + ( + get_resource("appliances"), + True, + ), + ( + self.appliances_path(), + False, + ), + ): if directory and os.path.isdir(directory): for file in os.listdir(directory): - if not file.endswith('.gns3a') and not file.endswith('.gns3appliance'): + if not file.endswith(".gns3a") and not file.endswith(".gns3appliance"): continue path = os.path.join(directory, file) - appliance_id = uuid.uuid3(uuid.NAMESPACE_URL, path) # Generate UUID from path to avoid change between reboots + appliance_id = uuid.uuid3( + uuid.NAMESPACE_URL, path + ) # Generate UUID from path to avoid change between reboots try: - with open(path, encoding='utf-8') as f: + with open(path, encoding="utf-8") as f: appliance = Appliance(appliance_id, json.load(f), builtin=builtin) json_data = appliance.__json__() # Check if loaded without error - if appliance.status != 'broken': + if appliance.status != "broken": self._appliances[appliance.id] = appliance if not appliance.symbol or appliance.symbol.startswith(":/symbols/"): # apply a default symbol if the appliance has none or a default symbol @@ -110,6 +122,7 @@ class ApplianceManager: """ from . import Controller + controller = Controller.instance() category = appliance["category"] if category == "guest": @@ -125,6 +138,7 @@ class ApplianceManager: """ from . import Controller + symbol_dir = Controller.instance().symbols.symbols_path() self.load_appliances() for appliance in self._appliances.values(): @@ -145,12 +159,14 @@ class ApplianceManager: symbol_url = f"https://raw.githubusercontent.com/GNS3/gns3-registry/master/symbols/{symbol}" async with HTTPClient.get(symbol_url) as response: if response.status != 200: - log.warning(f"Could not retrieve appliance symbol {symbol} from GitHub due to HTTP error code {response.status}") + log.warning( + f"Could not retrieve appliance symbol {symbol} from GitHub due to HTTP error code {response.status}" + ) else: try: symbol_data = await response.read() log.info(f"Saving {symbol} symbol to {destination_path}") - with open(destination_path, 'wb') as f: + with open(destination_path, "wb") as f: f.write(symbol_data) except asyncio.TimeoutError: log.warning(f"Timeout while downloading '{symbol_url}'") @@ -169,19 +185,24 @@ class ApplianceManager: log.info(f"Checking if appliances are up-to-date (ETag {self._appliances_etag})") headers["If-None-Match"] = self._appliances_etag - async with HTTPClient.get('https://api.github.com/repos/GNS3/gns3-registry/contents/appliances', headers=headers) as response: + async with HTTPClient.get( + "https://api.github.com/repos/GNS3/gns3-registry/contents/appliances", headers=headers + ) as response: if response.status == 304: log.info(f"Appliances are already up-to-date (ETag {self._appliances_etag})") return elif response.status != 200: - raise ControllerError(f"Could not retrieve appliances from GitHub due to HTTP error code {response.status}") + raise ControllerError( + f"Could not retrieve appliances from GitHub due to HTTP error code {response.status}" + ) etag = response.headers.get("ETag") if etag: self._appliances_etag = etag from . import Controller + Controller.instance().save() json_data = await response.json() - appliances_dir = get_resource('appliances') + appliances_dir = get_resource("appliances") downloaded_appliance_files = [] for appliance in json_data: if appliance["type"] == "file": @@ -189,7 +210,11 @@ class ApplianceManager: log.info("Download appliance file from '{}'".format(appliance["download_url"])) async with HTTPClient.get(appliance["download_url"]) as response: if response.status != 200: - log.warning("Could not download '{}' due to HTTP error code {}".format(appliance["download_url"], response.status)) + log.warning( + "Could not download '{}' due to HTTP error code {}".format( + appliance["download_url"], response.status + ) + ) continue try: appliance_data = await response.read() @@ -199,7 +224,7 @@ class ApplianceManager: path = os.path.join(appliances_dir, appliance_name) try: log.info(f"Saving {appliance_name} file to {path}") - with open(path, 'wb') as f: + with open(path, "wb") as f: f.write(appliance_data) except OSError as e: raise ControllerError(f"Could not write appliance file '{path}': {e}") diff --git a/gns3server/controller/compute.py b/gns3server/controller/compute.py index 8d7f9897..04c73950 100644 --- a/gns3server/controller/compute.py +++ b/gns3server/controller/compute.py @@ -34,11 +34,13 @@ from ..controller.controller_error import ( ControllerNotFoundError, ControllerForbiddenError, ControllerTimeoutError, - ControllerUnauthorizedError) + ControllerUnauthorizedError, +) from ..version import __version__, __version_info__ import logging + log = logging.getLogger(__name__) @@ -64,8 +66,19 @@ class Compute: A GNS3 compute. """ - def __init__(self, compute_id, controller=None, protocol="http", host="localhost", - port=3080, user=None, password=None, name=None, console_host=None, ssl_context=None): + def __init__( + self, + compute_id, + controller=None, + protocol="http", + host="localhost", + port=3080, + user=None, + password=None, + name=None, + console_host=None, + ssl_context=None, + ): self._http_session = None assert controller is not None log.info("Create compute %s", compute_id) @@ -91,14 +104,7 @@ class Compute: self._disk_usage_percent = 0 self._last_error = None self._ssl_context = ssl_context - self._capabilities = { - "version": "", - "platform": "", - "cpus": 0, - "memory": 0, - "disk_size": 0, - "node_types": [] - } + self._capabilities = {"version": "", "platform": "", "cpus": 0, "memory": 0, "disk_size": 0, "node_types": []} self.name = name # Cache of interfaces on remote host self._interfaces_cache = None @@ -223,7 +229,7 @@ class Compute: try: return socket.gethostbyname(self._host) except socket.gaierror: - return '0.0.0.0' + return "0.0.0.0" @host.setter def host(self, host): @@ -295,7 +301,7 @@ class Compute: "name": self._name, "protocol": self._protocol, "host": self._host, - "port": self._port + "port": self._port, } return { "compute_id": self._id, @@ -309,7 +315,7 @@ class Compute: "memory_usage_percent": self._memory_usage_percent, "disk_usage_percent": self._disk_usage_percent, "capabilities": self._capabilities, - "last_error": self._last_error + "last_error": self._last_error, } async def download_file(self, project, path): @@ -380,7 +386,9 @@ class Compute: # Try to reconnect after 5 seconds if server unavailable only if not during tests (otherwise we create a ressource usage bomb) if not hasattr(sys, "_called_from_test") or not sys._called_from_test: if self.id != "local" and self.id != "vm" and not self._controller.compute_has_open_project(self): - log.warning(f"Not reconnecting to compute '{self._id}' because there is no project opened on it") + log.warning( + f"Not reconnecting to compute '{self._id}' because there is no project opened on it" + ) return self._connection_failure += 1 # After 5 failure we close the project using the compute to avoid sync issues @@ -407,12 +415,15 @@ class Compute: if response.json["version"].split("-")[0] != __version__.split("-")[0]: if self._name.startswith("GNS3 VM"): - msg = "GNS3 version {} is not the same as the GNS3 VM version {}. Please upgrade the GNS3 VM.".format(__version__, - response.json["version"]) + msg = ( + "GNS3 version {} is not the same as the GNS3 VM version {}. Please upgrade the GNS3 VM.".format( + __version__, response.json["version"] + ) + ) else: - msg = "GNS3 controller version {} is not the same as compute {} version {}".format(__version__, - self._name, - response.json["version"]) + msg = "GNS3 controller version {} is not the same as compute {} version {}".format( + __version__, self._name, response.json["version"] + ) if __version_info__[3] == 0: # Stable release log.error(msg) @@ -454,10 +465,12 @@ class Compute: self._cpu_usage_percent = event["cpu_usage_percent"] self._memory_usage_percent = event["memory_usage_percent"] self._disk_usage_percent = event["disk_usage_percent"] - #FIXME: slow down number of compute events + # FIXME: slow down number of compute events self._controller.notification.controller_emit("compute.updated", self.__json__()) else: - await self._controller.notification.dispatch(action, event, project_id=project_id, compute_id=self.id) + await self._controller.notification.dispatch( + action, event, project_id=project_id, compute_id=self.id + ) else: if response.type == aiohttp.WSMsgType.CLOSE: await ws.close() @@ -504,31 +517,40 @@ class Compute: async def _run_http_query(self, method, path, data=None, timeout=20, raw=False): with async_timeout.timeout(timeout): url = self._getUrl(path) - headers = {'content-type': 'application/json'} + headers = {"content-type": "application/json"} chunked = None if data == {}: data = None elif data is not None: - if hasattr(data, '__json__'): + if hasattr(data, "__json__"): data = json.dumps(data.__json__()) elif isinstance(data, aiohttp.streams.EmptyStreamReader): data = None # Stream the request elif isinstance(data, aiohttp.streams.StreamReader) or isinstance(data, bytes): chunked = True - headers['content-type'] = 'application/octet-stream' + headers["content-type"] = "application/octet-stream" # If the data is an open file we will iterate on it elif isinstance(data, io.BufferedIOBase): chunked = True - headers['content-type'] = 'application/octet-stream' + headers["content-type"] = "application/octet-stream" else: data = json.dumps(data).encode("utf-8") try: log.debug(f"Attempting request to compute: {method} {url} {headers}") - response = await self._session().request(method, url, headers=headers, data=data, auth=self._auth, chunked=chunked, timeout=timeout) + response = await self._session().request( + method, url, headers=headers, data=data, auth=self._auth, chunked=chunked, timeout=timeout + ) except asyncio.TimeoutError: raise ComputeError(f"Timeout error for {method} call to {url} after {timeout}s") - except (aiohttp.ClientError, aiohttp.ServerDisconnectedError, aiohttp.ClientResponseError, ValueError, KeyError, socket.gaierror) as e: + except ( + aiohttp.ClientError, + aiohttp.ServerDisconnectedError, + aiohttp.ClientResponseError, + ValueError, + KeyError, + socket.gaierror, + ) as e: # aiohttp 2.3.1 raises socket.gaierror when cannot find host raise ComputeError(str(e)) body = await response.read() @@ -614,12 +636,12 @@ class Compute: try: if type in ["qemu", "dynamips", "iou"]: - #for local_image in list_images(type): + # for local_image in list_images(type): # if local_image['filename'] not in [i['filename'] for i in images]: # images.append(local_image) - images = sorted(images, key=itemgetter('filename')) + images = sorted(images, key=itemgetter("filename")) else: - images = sorted(images, key=itemgetter('image')) + images = sorted(images, key=itemgetter("image")) except OSError as e: raise ComputeError(f"Cannot list images: {str(e)}") return images @@ -643,7 +665,7 @@ class Compute: return self.host_ip, self.host_ip # Perhaps the user has correct network gateway, we trust him - if self.host_ip not in ('0.0.0.0', '127.0.0.1') and other_compute.host_ip not in ('0.0.0.0', '127.0.0.1'): + if self.host_ip not in ("0.0.0.0", "127.0.0.1") and other_compute.host_ip not in ("0.0.0.0", "127.0.0.1"): return self.host_ip, other_compute.host_ip this_compute_interfaces = await self.interfaces() @@ -652,7 +674,9 @@ class Compute: # Sort interface to put the compute host in first position # we guess that if user specified this host it could have a reason (VMware Nat / Host only interface) this_compute_interfaces = sorted(this_compute_interfaces, key=lambda i: i["ip_address"] != self.host_ip) - other_compute_interfaces = sorted(other_compute_interfaces, key=lambda i: i["ip_address"] != other_compute.host_ip) + other_compute_interfaces = sorted( + other_compute_interfaces, key=lambda i: i["ip_address"] != other_compute.host_ip + ) for this_interface in this_compute_interfaces: # Skip if no ip or no netmask (vbox when stopped set a null netmask) @@ -662,7 +686,9 @@ class Compute: if this_interface["ip_address"].startswith("169.254."): continue - this_network = ipaddress.ip_network("{}/{}".format(this_interface["ip_address"], this_interface["netmask"]), strict=False) + this_network = ipaddress.ip_network( + "{}/{}".format(this_interface["ip_address"], this_interface["netmask"]), strict=False + ) for other_interface in other_compute_interfaces: if len(other_interface["ip_address"]) == 0 or other_interface["netmask"] is None: @@ -672,7 +698,9 @@ class Compute: if other_interface["ip_address"] == this_interface["ip_address"]: continue - other_network = ipaddress.ip_network("{}/{}".format(other_interface["ip_address"], other_interface["netmask"]), strict=False) + other_network = ipaddress.ip_network( + "{}/{}".format(other_interface["ip_address"], other_interface["netmask"]), strict=False + ) if this_network.overlaps(other_network): return this_interface["ip_address"], other_interface["ip_address"] diff --git a/gns3server/controller/controller_error.py b/gns3server/controller/controller_error.py index 04569e7c..515c88fd 100644 --- a/gns3server/controller/controller_error.py +++ b/gns3server/controller/controller_error.py @@ -17,7 +17,6 @@ class ControllerError(Exception): - def __init__(self, message: str): super().__init__() self._message = message @@ -30,30 +29,25 @@ class ControllerError(Exception): class ControllerNotFoundError(ControllerError): - def __init__(self, message: str): super().__init__(message) class ControllerBadRequestError(ControllerError): - def __init__(self, message: str): super().__init__(message) class ControllerUnauthorizedError(ControllerError): - def __init__(self, message: str): super().__init__(message) class ControllerForbiddenError(ControllerError): - def __init__(self, message: str): super().__init__(message) class ControllerTimeoutError(ControllerError): - def __init__(self, message: str): super().__init__(message) diff --git a/gns3server/controller/drawing.py b/gns3server/controller/drawing.py index b7c25f32..a1a89809 100644 --- a/gns3server/controller/drawing.py +++ b/gns3server/controller/drawing.py @@ -27,6 +27,7 @@ from gns3server.utils.picture import get_size import logging + log = logging.getLogger(__name__) @@ -74,7 +75,9 @@ class Drawing: return data.decode() except UnicodeError: width, height, filetype = get_size(data) - return "\n\n".format(b64=base64.b64encode(data).decode(), filetype=filetype, width=width, height=height) + return '\n\n'.format( + b64=base64.b64encode(data).decode(), filetype=filetype, width=width, height=height + ) except OSError: log.warning("Image file %s missing", filename) return "" @@ -99,8 +102,8 @@ class Drawing: log.error(f"Can't parse SVG: {e}") return # SVG is the default namespace no need to prefix it - ET.register_namespace('xmlns', "http://www.w3.org/2000/svg") - ET.register_namespace('xmlns:xlink', "http://www.w3.org/1999/xlink") + ET.register_namespace("xmlns", "http://www.w3.org/2000/svg") + ET.register_namespace("xmlns:xlink", "http://www.w3.org/1999/xlink") if len(root.findall("{http://www.w3.org/2000/svg}image")) == 1: href = "{http://www.w3.org/1999/xlink}href" @@ -208,7 +211,7 @@ class Drawing: "z": self._z, "locked": self._locked, "rotation": self._rotation, - "svg": self._svg + "svg": self._svg, } return { "project_id": self._project.id, @@ -218,7 +221,7 @@ class Drawing: "z": self._z, "locked": self._locked, "rotation": self._rotation, - "svg": self.svg + "svg": self.svg, } def __repr__(self): diff --git a/gns3server/controller/export_project.py b/gns3server/controller/export_project.py index fe947558..0301ebc8 100644 --- a/gns3server/controller/export_project.py +++ b/gns3server/controller/export_project.py @@ -28,12 +28,22 @@ from .controller_error import ControllerError, ControllerNotFoundError, Controll from datetime import datetime import logging + log = logging.getLogger(__name__) CHUNK_SIZE = 1024 * 8 # 8KB -async def export_project(zstream, project, temporary_dir, include_images=False, include_snapshots=False, keep_compute_id=False, allow_all_nodes=False, reset_mac_addresses=False): +async def export_project( + zstream, + project, + temporary_dir, + include_images=False, + include_snapshots=False, + keep_compute_id=False, + allow_all_nodes=False, + reset_mac_addresses=False, +): """ Export a project to a zip file. @@ -63,7 +73,16 @@ async def export_project(zstream, project, temporary_dir, include_images=False, # First we process the .gns3 in order to be sure we don't have an error for file in os.listdir(project._path): if file.endswith(".gns3"): - await _patch_project_file(project, os.path.join(project._path, file), zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir, reset_mac_addresses) + await _patch_project_file( + project, + os.path.join(project._path, file), + zstream, + include_images, + keep_compute_id, + allow_all_nodes, + temporary_dir, + reset_mac_addresses, + ) # Export the local files for root, dirs, files in os.walk(project._path, topdown=True, followlinks=False): @@ -97,15 +116,21 @@ async def export_project(zstream, project, temporary_dir, include_images=False, log.debug("Downloading file '{}' from compute '{}'".format(compute_file["path"], compute.id)) response = await compute.download_file(project, compute_file["path"]) if response.status != 200: - log.warning(f"Cannot export file from compute '{compute.id}'. Compute returned status code {response.status}.") + log.warning( + f"Cannot export file from compute '{compute.id}'. Compute returned status code {response.status}." + ) continue (fd, temp_path) = tempfile.mkstemp(dir=temporary_dir) - async with aiofiles.open(fd, 'wb') as f: + async with aiofiles.open(fd, "wb") as f: while True: try: data = await response.content.read(CHUNK_SIZE) except asyncio.TimeoutError: - raise ControllerTimeoutError("Timeout when downloading file '{}' from remote compute {}:{}".format(compute_file["path"], compute.host, compute.port)) + raise ControllerTimeoutError( + "Timeout when downloading file '{}' from remote compute {}:{}".format( + compute_file["path"], compute.host, compute.port + ) + ) if not data: break await f.write(data) @@ -161,12 +186,14 @@ def _is_exportable(path, include_snapshots=False): # do not export log files and OS noise filename = os.path.basename(path) - if filename.endswith('_log.txt') or filename.endswith('.log') or filename == '.DS_Store': + if filename.endswith("_log.txt") or filename.endswith(".log") or filename == ".DS_Store": return False return True -async def _patch_project_file(project, path, zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir, reset_mac_addresses): +async def _patch_project_file( + project, path, zstream, include_images, keep_compute_id, allow_all_nodes, temporary_dir, reset_mac_addresses +): """ Patch a project file (.gns3) to export a project. The .gns3 file is renamed to project.gns3 @@ -186,10 +213,14 @@ async def _patch_project_file(project, path, zstream, include_images, keep_compu if "topology" in topology: if "nodes" in topology["topology"]: for node in topology["topology"]["nodes"]: - compute_id = node.get('compute_id', 'local') + compute_id = node.get("compute_id", "local") if node["node_type"] == "virtualbox" and node.get("properties", {}).get("linked_clone"): - raise ControllerError("Projects with a linked {} clone node cannot not be exported. Please use Qemu instead.".format(node["node_type"])) + raise ControllerError( + "Projects with a linked {} clone node cannot not be exported. Please use Qemu instead.".format( + node["node_type"] + ) + ) if not allow_all_nodes and node["node_type"] in ["virtualbox", "vmware"]: raise ControllerError("Projects with a {} node cannot be exported".format(node["node_type"])) @@ -208,30 +239,26 @@ async def _patch_project_file(project, path, zstream, include_images, keep_compu continue elif not prop.endswith("image"): continue - if value is None or value.strip() == '': + if value is None or value.strip() == "": continue if not keep_compute_id: # If we keep the original compute we can keep the image path node["properties"][prop] = os.path.basename(value) if include_images is True: - images.append({ - 'compute_id': compute_id, - 'image': value, - 'image_type': node['node_type'] - }) + images.append({"compute_id": compute_id, "image": value, "image_type": node["node_type"]}) if not keep_compute_id: - topology["topology"]["computes"] = [] # Strip compute information because could contain secret info like password + topology["topology"][ + "computes" + ] = [] # Strip compute information because could contain secret info like password - local_images = {i['image'] for i in images if i['compute_id'] == 'local'} + local_images = {i["image"] for i in images if i["compute_id"] == "local"} for image in local_images: _export_local_image(image, zstream) - remote_images = { - (i['compute_id'], i['image_type'], i['image']) - for i in images if i['compute_id'] != 'local'} + remote_images = {(i["compute_id"], i["image_type"], i["image"]) for i in images if i["compute_id"] != "local"} for compute_id, image_type, image in remote_images: await _export_remote_images(project, compute_id, image_type, image, zstream, temporary_dir) @@ -249,6 +276,7 @@ def _export_local_image(image, zstream): """ from ..compute import MODULES + for module in MODULES: try: images_directory = module.instance().get_images_directory() @@ -282,15 +310,19 @@ async def _export_remote_images(project, compute_id, image_type, image, project_ response = await compute.download_image(image_type, image) if response.status != 200: - raise ControllerError(f"Cannot export image from compute '{compute_id}'. Compute returned status code {response.status}.") + raise ControllerError( + f"Cannot export image from compute '{compute_id}'. Compute returned status code {response.status}." + ) (fd, temp_path) = tempfile.mkstemp(dir=temporary_dir) - async with aiofiles.open(fd, 'wb') as f: + async with aiofiles.open(fd, "wb") as f: while True: try: data = await response.content.read(CHUNK_SIZE) except asyncio.TimeoutError: - raise ControllerTimeoutError(f"Timeout when downloading image '{image}' from remote compute {compute.host}:{compute.port}") + raise ControllerTimeoutError( + f"Timeout when downloading image '{image}' from remote compute {compute.host}:{compute.port}" + ) if not data: break await f.write(data) diff --git a/gns3server/controller/gns3vm/__init__.py b/gns3server/controller/gns3vm/__init__.py index 4b5da724..e23f6999 100644 --- a/gns3server/controller/gns3vm/__init__.py +++ b/gns3server/controller/gns3vm/__init__.py @@ -31,6 +31,7 @@ from ..compute import ComputeError from ..controller_error import ControllerError import logging + log = logging.getLogger(__name__) @@ -60,37 +61,47 @@ class GNS3VM: :returns: Return list of engines supported by GNS3 for the GNS3VM """ - download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VMware.Workstation.{version}.zip".format(version=__version__) + download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VMware.Workstation.{version}.zip".format( + version=__version__ + ) vmware_info = { "engine_id": "vmware", "description": f'VMware is the recommended choice for best performances.
The GNS3 VM can be downloaded here.', "support_when_exit": True, "support_headless": True, - "support_ram": True + "support_ram": True, } if sys.platform.startswith("darwin"): vmware_info["name"] = "VMware Fusion (recommended)" else: vmware_info["name"] = "VMware Workstation / Player (recommended)" - download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.Hyper-V.{version}.zip".format(version=__version__) + download_url = ( + "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.Hyper-V.{version}.zip".format( + version=__version__ + ) + ) hyperv_info = { "engine_id": "hyper-v", "name": "Hyper-V", "description": f'Hyper-V support (Windows 10/Server 2016 and above). Nested virtualization must be supported and enabled (Intel processor only)
The GNS3 VM can be downloaded here', "support_when_exit": True, "support_headless": False, - "support_ram": True + "support_ram": True, } - download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VirtualBox.{version}.zip".format(version=__version__) + download_url = ( + "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VirtualBox.{version}.zip".format( + version=__version__ + ) + ) virtualbox_info = { "engine_id": "virtualbox", "name": "VirtualBox", "description": f'VirtualBox support. Nested virtualization for both Intel and AMD processors is supported since version 6.1
The GNS3 VM can be downloaded here', "support_when_exit": True, "support_headless": True, - "support_ram": True + "support_ram": True, } remote_info = { @@ -99,12 +110,10 @@ class GNS3VM: "description": "Use a remote GNS3 server as the GNS3 VM.", "support_when_exit": False, "support_headless": False, - "support_ram": False + "support_ram": False, } - engines = [vmware_info, - virtualbox_info, - remote_info] + engines = [vmware_info, virtualbox_info, remote_info] if sys.platform.startswith("win"): engines.append(hyperv_info) @@ -260,7 +269,7 @@ class GNS3VM: engine = self._get_engine(engine) vms = [] try: - for vm in (await engine.list()): + for vm in await engine.list(): vms.append({"vmname": vm["vmname"]}) except GNS3VMError as e: # We raise error only if user activated the GNS3 VM @@ -280,10 +289,9 @@ class GNS3VM: except GNS3VMError as e: # User will receive the error later when they will try to use the node try: - compute = await self._controller.add_compute(compute_id="vm", - name=f"GNS3 VM ({self.current_engine().vmname})", - host=None, - force=True) + compute = await self._controller.add_compute( + compute_id="vm", name=f"GNS3 VM ({self.current_engine().vmname})", host=None, force=True + ) compute.set_last_error(str(e)) except ControllerError: @@ -318,11 +326,9 @@ class GNS3VM: engine.vcpus = self._settings["vcpus"] engine.headless = self._settings["headless"] engine.port = self._settings["port"] - compute = await self._controller.add_compute(compute_id="vm", - name=f"GNS3 VM is starting ({engine.vmname})", - host=None, - force=True, - connect=False) + compute = await self._controller.add_compute( + compute_id="vm", name=f"GNS3 VM is starting ({engine.vmname})", host=None, force=True, connect=False + ) try: await engine.start() @@ -333,12 +339,14 @@ class GNS3VM: compute.set_last_error(str(e)) raise e await compute.connect() # we can connect now that the VM has started - await compute.update(name=f"GNS3 VM ({engine.vmname})", - protocol=self.protocol, - host=self.ip_address, - port=self.port, - user=self.user, - password=self.password) + await compute.update( + name=f"GNS3 VM ({engine.vmname})", + protocol=self.protocol, + host=self.ip_address, + port=self.port, + user=self.user, + password=self.password, + ) # check if the VM is in the same subnet as the local server, start 10 seconds later to give # some time for the compute in the VM to be ready for requests @@ -370,11 +378,9 @@ class GNS3VM: if netmask: compute_network = ipaddress.ip_interface(f"{compute.host_ip}/{netmask}").network if vm_network.compare_networks(compute_network) != 0: - msg = "The GNS3 VM (IP={}, NETWORK={}) is not on the same network as the {} server (IP={}, NETWORK={}), please make sure the local server binding is in the same network as the GNS3 VM".format(self.ip_address, - vm_network, - compute_id, - compute.host_ip, - compute_network) + msg = "The GNS3 VM (IP={}, NETWORK={}) is not on the same network as the {} server (IP={}, NETWORK={}), please make sure the local server binding is in the same network as the GNS3 VM".format( + self.ip_address, vm_network, compute_id, compute.host_ip, compute_network + ) self._controller.notification.controller_emit("log.warning", {"message": msg}) except ComputeError as e: log.warning(f"Could not check the VM is in the same subnet as the local server: {e}") diff --git a/gns3server/controller/gns3vm/base_gns3_vm.py b/gns3server/controller/gns3vm/base_gns3_vm.py index 14d8aae4..4c5c8578 100644 --- a/gns3server/controller/gns3vm/base_gns3_vm.py +++ b/gns3server/controller/gns3vm/base_gns3_vm.py @@ -18,11 +18,11 @@ import psutil import logging + log = logging.getLogger(__name__) class BaseGNS3VM: - def __init__(self, controller): self._controller = controller @@ -42,9 +42,9 @@ class BaseGNS3VM: # because this is likely to degrade performances. self._vcpus = psutil.cpu_count(logical=False) # we want to allocate half of the available physical memory - #ram = int(psutil.virtual_memory().total / (1024 * 1024) / 2) + # ram = int(psutil.virtual_memory().total / (1024 * 1024) / 2) # value must be a multiple of 4 (VMware requirement) - #ram -= ram % 4 + # ram -= ram % 4 ram = 2048 diff --git a/gns3server/controller/gns3vm/gns3_vm_error.py b/gns3server/controller/gns3vm/gns3_vm_error.py index b72605a6..4f8a4d39 100644 --- a/gns3server/controller/gns3vm/gns3_vm_error.py +++ b/gns3server/controller/gns3vm/gns3_vm_error.py @@ -17,7 +17,6 @@ class GNS3VMError(Exception): - def __init__(self, message): super().__init__(message) self._message = message diff --git a/gns3server/controller/gns3vm/hyperv_gns3_vm.py b/gns3server/controller/gns3vm/hyperv_gns3_vm.py index df3eea48..50b053f6 100644 --- a/gns3server/controller/gns3vm/hyperv_gns3_vm.py +++ b/gns3server/controller/gns3vm/hyperv_gns3_vm.py @@ -23,6 +23,7 @@ import ipaddress from .base_gns3_vm import BaseGNS3VM from .gns3_vm_error import GNS3VMError + log = logging.getLogger(__name__) @@ -55,17 +56,25 @@ class HyperVGNS3VM(BaseGNS3VM): raise GNS3VMError("Hyper-V is only supported on Windows") if sys.getwindowsversion().platform_version[0] < 10: - raise GNS3VMError(f"Windows 10/Windows Server 2016 or a later version is required to run Hyper-V with nested virtualization enabled (version {sys.getwindowsversion().platform_version[0]} detected)") + raise GNS3VMError( + f"Windows 10/Windows Server 2016 or a later version is required to run Hyper-V with nested virtualization enabled (version {sys.getwindowsversion().platform_version[0]} detected)" + ) - is_windows_10 = sys.getwindowsversion().platform_version[0] == 10 and sys.getwindowsversion().platform_version[1] == 0 + is_windows_10 = ( + sys.getwindowsversion().platform_version[0] == 10 and sys.getwindowsversion().platform_version[1] == 0 + ) if is_windows_10 and sys.getwindowsversion().platform_version[2] < 14393: - raise GNS3VMError("Hyper-V with nested virtualization is only supported on Windows 10 Anniversary Update (build 10.0.14393) or later") + raise GNS3VMError( + "Hyper-V with nested virtualization is only supported on Windows 10 Anniversary Update (build 10.0.14393) or later" + ) try: import pythoncom + pythoncom.CoInitialize() import wmi + self._wmi = wmi conn = self._wmi.WMI() except self._wmi.x_wmi as e: @@ -77,12 +86,16 @@ class HyperVGNS3VM(BaseGNS3VM): if conn.Win32_Processor()[0].Manufacturer != "GenuineIntel": if is_windows_10 and conn.Win32_Processor()[0].Manufacturer == "AuthenticAMD": if sys.getwindowsversion().platform_version[2] < 19640: - raise GNS3VMError("Windows 10 (build 10.0.19640) or later is required by Hyper-V to support nested virtualization with AMD processors") + raise GNS3VMError( + "Windows 10 (build 10.0.19640) or later is required by Hyper-V to support nested virtualization with AMD processors" + ) else: - raise GNS3VMError("An Intel processor is required by Hyper-V to support nested virtualization on this version of Windows") + raise GNS3VMError( + "An Intel processor is required by Hyper-V to support nested virtualization on this version of Windows" + ) # This is not reliable - #if not conn.Win32_Processor()[0].VirtualizationFirmwareEnabled: + # if not conn.Win32_Processor()[0].VirtualizationFirmwareEnabled: # raise GNS3VMError("Nested Virtualization (VT-x) is not enabled on this system") def _connect(self): @@ -134,8 +147,8 @@ class HyperVGNS3VM(BaseGNS3VM): :param vm: VM instance """ - vm_settings = vm.associators(wmi_result_class='Msvm_VirtualSystemSettingData') - return [s for s in vm_settings if s.VirtualSystemType == 'Microsoft:Hyper-V:System:Realized'][0] + vm_settings = vm.associators(wmi_result_class="Msvm_VirtualSystemSettingData") + return [s for s in vm_settings if s.VirtualSystemType == "Microsoft:Hyper-V:System:Realized"][0] def _get_vm_resources(self, vm, resource_class): """ @@ -158,11 +171,13 @@ class HyperVGNS3VM(BaseGNS3VM): available_vcpus = psutil.cpu_count(logical=False) if vcpus > available_vcpus: - raise GNS3VMError(f"You have allocated too many vCPUs for the GNS3 VM! (max available is {available_vcpus} vCPUs)") + raise GNS3VMError( + f"You have allocated too many vCPUs for the GNS3 VM! (max available is {available_vcpus} vCPUs)" + ) try: - mem_settings = self._get_vm_resources(self._vm, 'Msvm_MemorySettingData')[0] - cpu_settings = self._get_vm_resources(self._vm, 'Msvm_ProcessorSettingData')[0] + mem_settings = self._get_vm_resources(self._vm, "Msvm_MemorySettingData")[0] + cpu_settings = self._get_vm_resources(self._vm, "Msvm_ProcessorSettingData")[0] mem_settings.VirtualQuantity = ram mem_settings.Reservation = ram @@ -201,7 +216,7 @@ class HyperVGNS3VM(BaseGNS3VM): Gets the WMI object. """ - return self._wmi.WMI(moniker=path.replace('\\', '/')) + return self._wmi.WMI(moniker=path.replace("\\", "/")) async def _set_state(self, state): """ @@ -234,8 +249,12 @@ class HyperVGNS3VM(BaseGNS3VM): IPv4/v6 (4098) """ - wql = "SELECT * FROM Msvm_GuestNetworkAdapterConfiguration WHERE InstanceID like \ - 'Microsoft:GuestNetwork\\" + self._vm.Name + "%' and ProtocolIFType > 0 " + wql = ( + "SELECT * FROM Msvm_GuestNetworkAdapterConfiguration WHERE InstanceID like \ + 'Microsoft:GuestNetwork\\" + + self._vm.Name + + "%' and ProtocolIFType > 0 " + ) nic_count = len(self._conn.query(wql)) while nic_count == 0: await asyncio.sleep(0.1) # 100ms @@ -272,15 +291,15 @@ class HyperVGNS3VM(BaseGNS3VM): trial = 120 guest_ip_address = "" log.info("Waiting for GNS3 VM IP") - ports = self._get_vm_resources(self._vm, 'Msvm_EthernetPortAllocationSettingData') - vnics = self._get_vm_resources(self._vm, 'Msvm_SyntheticEthernetPortSettingData') + ports = self._get_vm_resources(self._vm, "Msvm_EthernetPortAllocationSettingData") + vnics = self._get_vm_resources(self._vm, "Msvm_SyntheticEthernetPortSettingData") while True: for port in ports: try: vnic = [v for v in vnics if port.Parent == v.path_()][0] except IndexError: continue - config = vnic.associators(wmi_result_class='Msvm_GuestNetworkAdapterConfiguration') + config = vnic.associators(wmi_result_class="Msvm_GuestNetworkAdapterConfiguration") ip_addresses = config[0].IPAddresses for ip_address in ip_addresses: # take the first valid IPv4 address diff --git a/gns3server/controller/gns3vm/remote_gns3_vm.py b/gns3server/controller/gns3vm/remote_gns3_vm.py index 0e6cf83a..f71aa9e8 100644 --- a/gns3server/controller/gns3vm/remote_gns3_vm.py +++ b/gns3server/controller/gns3vm/remote_gns3_vm.py @@ -20,11 +20,11 @@ from .gns3_vm_error import GNS3VMError import logging + log = logging.getLogger(__name__) class RemoteGNS3VM(BaseGNS3VM): - def __init__(self, controller): self._engine = "remote" diff --git a/gns3server/controller/gns3vm/virtualbox_gns3_vm.py b/gns3server/controller/gns3vm/virtualbox_gns3_vm.py index b6d15974..95d21e8c 100644 --- a/gns3server/controller/gns3vm/virtualbox_gns3_vm.py +++ b/gns3server/controller/gns3vm/virtualbox_gns3_vm.py @@ -27,16 +27,12 @@ from gns3server.utils import parse_version from gns3server.utils.http_client import HTTPClient from gns3server.utils.asyncio import wait_run_in_executor -from ...compute.virtualbox import ( - VirtualBox, - VirtualBoxError -) +from ...compute.virtualbox import VirtualBox, VirtualBoxError log = logging.getLogger(__name__) class VirtualBoxGNS3VM(BaseGNS3VM): - def __init__(self, controller): self._engine = "virtualbox" @@ -48,7 +44,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM): try: result = await self._virtualbox_manager.execute(subcommand, args, timeout) - return ("\n".join(result)) + return "\n".join(result) except VirtualBoxError as e: raise GNS3VMError(f"Error while executing VBoxManage command: {e}") @@ -61,8 +57,8 @@ class VirtualBoxGNS3VM(BaseGNS3VM): result = await self._execute("showvminfo", [self._vmname, "--machinereadable"]) for info in result.splitlines(): - if '=' in info: - name, value = info.split('=', 1) + if "=" in info: + name, value = info.split("=", 1) if name == "VMState": return value.strip('"') return "unknown" @@ -77,7 +73,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM): properties = await self._execute("list", ["systemproperties"]) for prop in properties.splitlines(): try: - name, value = prop.split(':', 1) + name, value = prop.split(":", 1) except ValueError: continue self._system_properties[name.strip()] = value.strip() @@ -92,14 +88,19 @@ class VirtualBoxGNS3VM(BaseGNS3VM): if "API version" not in self._system_properties: raise VirtualBoxError(f"Can't access to VirtualBox API version:\n{self._system_properties}") from cpuinfo import get_cpu_info + cpu_info = await wait_run_in_executor(get_cpu_info) - vendor_id = cpu_info.get('vendor_id_raw') + vendor_id = cpu_info.get("vendor_id_raw") if vendor_id == "GenuineIntel": if parse_version(self._system_properties["API version"]) < parse_version("6_1"): - raise VirtualBoxError("VirtualBox version 6.1 or above is required to run the GNS3 VM with nested virtualization enabled on Intel processors") + raise VirtualBoxError( + "VirtualBox version 6.1 or above is required to run the GNS3 VM with nested virtualization enabled on Intel processors" + ) elif vendor_id == "AuthenticAMD": if parse_version(self._system_properties["API version"]) < parse_version("6_0"): - raise VirtualBoxError("VirtualBox version 6.0 or above is required to run the GNS3 VM with nested virtualization enabled on AMD processors") + raise VirtualBoxError( + "VirtualBox version 6.0 or above is required to run the GNS3 VM with nested virtualization enabled on AMD processors" + ) else: log.warning(f"Could not determine CPU vendor: {vendor_id}") @@ -113,8 +114,8 @@ class VirtualBoxGNS3VM(BaseGNS3VM): result = await self._execute("showvminfo", [self._vmname, "--machinereadable"]) interface = -1 for info in result.splitlines(): - if '=' in info: - name, value = info.split('=', 1) + if "=" in info: + name, value = info.split("=", 1) if name.startswith("nic") and value.strip('"') == network_backend: try: interface = int(name[3:]) @@ -132,8 +133,8 @@ class VirtualBoxGNS3VM(BaseGNS3VM): result = await self._execute("showvminfo", [self._vmname, "--machinereadable"]) for info in result.splitlines(): - if '=' in info: - name, value = info.split('=', 1) + if "=" in info: + name, value = info.split("=", 1) if name == f"hostonlyadapter{interface_number}": return value.strip('"') return None @@ -150,7 +151,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM): flag_dhcp_server_found = False for prop in properties.splitlines(): try: - name, value = prop.split(':', 1) + name, value = prop.split(":", 1) except ValueError: continue if name.strip() == "NetworkName" and value.strip().endswith(vboxnet): @@ -171,7 +172,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM): properties = await self._execute("list", ["hostonlyifs"]) for prop in properties.splitlines(): try: - name, value = prop.split(':', 1) + name, value = prop.split(":", 1) except ValueError: continue if name.strip() == "Name" and value.strip() == vboxnet: @@ -186,7 +187,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM): properties = await self._execute("list", ["hostonlyifs"]) for prop in properties.splitlines(): try: - name, value = prop.split(':', 1) + name, value = prop.split(":", 1) except ValueError: continue if name.strip() == "Name": @@ -202,8 +203,8 @@ class VirtualBoxGNS3VM(BaseGNS3VM): result = await self._execute("showvminfo", [self._vmname, "--machinereadable"]) for info in result.splitlines(): - if '=' in info: - name, value = info.split('=', 1) + if "=" in info: + name, value = info.split("=", 1) if name.startswith("Forwarding") and value.strip('"').startswith("GNS3VM"): return True return False @@ -237,7 +238,9 @@ class VirtualBoxGNS3VM(BaseGNS3VM): vboxnet = await self._look_for_vboxnet(hostonly_interface_number) if vboxnet is None: - raise GNS3VMError(f'A VirtualBox host-only network could not be found on network adapter {hostonly_interface_number} for "{self._vmname}"') + raise GNS3VMError( + f'A VirtualBox host-only network could not be found on network adapter {hostonly_interface_number} for "{self._vmname}"' + ) if not (await self._check_vboxnet_exists(vboxnet)): if sys.platform.startswith("win") and vboxnet == "vboxnet0": @@ -245,13 +248,17 @@ class VirtualBoxGNS3VM(BaseGNS3VM): # on Windows. Try to patch this with the first available vboxnet we find. first_available_vboxnet = await self._find_first_available_vboxnet() if first_available_vboxnet is None: - raise GNS3VMError(f'Please add a VirtualBox host-only network with DHCP enabled and attached it to network adapter {hostonly_interface_number} for "{self._vmname}"') + raise GNS3VMError( + f'Please add a VirtualBox host-only network with DHCP enabled and attached it to network adapter {hostonly_interface_number} for "{self._vmname}"' + ) await self.set_hostonly_network(hostonly_interface_number, first_available_vboxnet) vboxnet = first_available_vboxnet else: - raise GNS3VMError('VirtualBox host-only network "{}" does not exist, please make the sure the network adapter {} configuration is valid for "{}"'.format(vboxnet, - hostonly_interface_number, - self._vmname)) + raise GNS3VMError( + 'VirtualBox host-only network "{}" does not exist, please make the sure the network adapter {} configuration is valid for "{}"'.format( + vboxnet, hostonly_interface_number, self._vmname + ) + ) if not (await self._check_dhcp_server(vboxnet)): raise GNS3VMError(f'DHCP must be enabled on VirtualBox host-only network "{vboxnet}"') @@ -287,15 +294,17 @@ class VirtualBoxGNS3VM(BaseGNS3VM): except OSError as e: raise GNS3VMError(f"Error while getting random port: {e}") - if (await self._check_vbox_port_forwarding()): + if await self._check_vbox_port_forwarding(): # delete the GNS3VM NAT port forwarding rule if it exists log.info(f"Removing GNS3VM NAT port forwarding rule from interface {nat_interface_number}") await self._execute("controlvm", [self._vmname, f"natpf{nat_interface_number}", "delete", "GNS3VM"]) # add a GNS3VM NAT port forwarding rule to redirect 127.0.0.1 with random port to the port in the VM log.info(f"Adding GNS3VM NAT port forwarding rule with port {api_port} to interface {nat_interface_number}") - await self._execute("controlvm", [self._vmname, f"natpf{nat_interface_number}", - f"GNS3VM,tcp,{ip_address},{api_port},,{self.port}"]) + await self._execute( + "controlvm", + [self._vmname, f"natpf{nat_interface_number}", f"GNS3VM,tcp,{ip_address},{api_port},,{self.port}"], + ) self.ip_address = await self._get_ip(hostonly_interface_number, api_port) log.info(f"GNS3 VM has been started with IP {self.ip_address}") @@ -320,7 +329,8 @@ class VirtualBoxGNS3VM(BaseGNS3VM): if json_data: for interface in json_data: if "name" in interface and interface["name"] == "eth{}".format( - hostonly_interface_number - 1): + hostonly_interface_number - 1 + ): if "ip_address" in interface and len(interface["ip_address"]) > 0: return interface["ip_address"] except ValueError: @@ -405,7 +415,11 @@ class VirtualBoxGNS3VM(BaseGNS3VM): :param hostonly_network_name: name of the VirtualBox host-only network """ - await self._execute("modifyvm", [self._vmname, f"--hostonlyadapter{adapter_number}", hostonly_network_name], timeout=3) - log.info('VirtualBox host-only network "{}" set on network adapter {} for "{}"'.format(hostonly_network_name, - adapter_number, - self._vmname)) + await self._execute( + "modifyvm", [self._vmname, f"--hostonlyadapter{adapter_number}", hostonly_network_name], timeout=3 + ) + log.info( + 'VirtualBox host-only network "{}" set on network adapter {} for "{}"'.format( + hostonly_network_name, adapter_number, self._vmname + ) + ) diff --git a/gns3server/controller/gns3vm/vmware_gns3_vm.py b/gns3server/controller/gns3vm/vmware_gns3_vm.py index e28a6e18..5062d8dc 100644 --- a/gns3server/controller/gns3vm/vmware_gns3_vm.py +++ b/gns3server/controller/gns3vm/vmware_gns3_vm.py @@ -20,18 +20,15 @@ import logging import asyncio import psutil -from gns3server.compute.vmware import ( - VMware, - VMwareError -) +from gns3server.compute.vmware import VMware, VMwareError from .base_gns3_vm import BaseGNS3VM from .gns3_vm_error import GNS3VMError + log = logging.getLogger(__name__) class VMwareGNS3VM(BaseGNS3VM): - def __init__(self, controller): self._engine = "vmware" @@ -47,7 +44,7 @@ class VMwareGNS3VM(BaseGNS3VM): try: result = await self._vmware_manager.execute(subcommand, args, timeout, log_level=log_level) - return (''.join(result)) + return "".join(result) except VMwareError as e: raise GNS3VMError(f"Error while executing VMware command: {e}") @@ -73,7 +70,9 @@ class VMwareGNS3VM(BaseGNS3VM): if not float(vcpus).is_integer(): raise GNS3VMError(f"The allocated vCPUs value is not an integer: {vcpus}") if vcpus > available_vcpus: - raise GNS3VMError(f"You have allocated too many vCPUs for the GNS3 VM! (max available is {available_vcpus} vCPUs)") + raise GNS3VMError( + f"You have allocated too many vCPUs for the GNS3 VM! (max available is {available_vcpus} vCPUs)" + ) try: pairs = VMware.parse_vmware_file(self._vmx_path) @@ -94,9 +93,7 @@ class VMwareGNS3VM(BaseGNS3VM): Due to bug/change in VMWare 14 we're not able to pass Hardware Virtualization in GNS3VM. We only enable this when it's not present in current configuration and user hasn't deactivated that. """ - extra_config = ( - ("vhv.enable", "TRUE"), - ) + extra_config = (("vhv.enable", "TRUE"),) pairs = VMware.parse_vmware_file(self._vmx_path) updated = False for key, value in extra_config: @@ -117,7 +114,7 @@ class VMwareGNS3VM(BaseGNS3VM): """ try: - return (await self._vmware_manager.list_vms()) + return await self._vmware_manager.list_vms() except VMwareError as e: raise GNS3VMError(f"Could not list VMware VMs: {str(e)}") @@ -169,7 +166,9 @@ class VMwareGNS3VM(BaseGNS3VM): log.info("Waiting for GNS3 VM IP") while True: try: - guest_ip_address = await self._execute("readVariable", [self._vmx_path, "guestVar", "gns3.eth0"], timeout=120, log_level=logging.DEBUG) + guest_ip_address = await self._execute( + "readVariable", [self._vmx_path, "guestVar", "gns3.eth0"], timeout=120, log_level=logging.DEBUG + ) guest_ip_address = guest_ip_address.strip() if len(guest_ip_address) != 0: break diff --git a/gns3server/controller/import_project.py b/gns3server/controller/import_project.py index a48b0a35..2348fe73 100644 --- a/gns3server/controller/import_project.py +++ b/gns3server/controller/import_project.py @@ -31,6 +31,7 @@ from ..utils.asyncio import wait_run_in_executor from ..utils.asyncio import aiozipstream import logging + log = logging.getLogger(__name__) """ @@ -150,9 +151,17 @@ async def import_project(controller, project_id, stream, location=None, name=Non # Project created on the remote GNS3 VM? if node["compute_id"] not in compute_created: compute = controller.get_compute(node["compute_id"]) - await compute.post("/projects", data={"name": project_name, "project_id": project_id,}) + await compute.post( + "/projects", + data={ + "name": project_name, + "project_id": project_id, + }, + ) compute_created.add(node["compute_id"]) - await _move_files_to_compute(compute, project_id, path, os.path.join("project-files", node["node_type"], node["node_id"])) + await _move_files_to_compute( + compute, project_id, path, os.path.join("project-files", node["node_type"], node["node_id"]) + ) # And we dump the updated.gns3 dot_gns3_path = os.path.join(path, project_name + ".gns3") @@ -259,7 +268,9 @@ async def _import_snapshots(snapshots_path, project_name, project_id): except OSError as e: raise ControllerError(f"Cannot open snapshot '{os.path.basename(snapshot)}': {e}") except zipfile.BadZipFile: - raise ControllerError(f"Cannot extract files from snapshot '{os.path.basename(snapshot)}': not a GNS3 project (invalid zip)") + raise ControllerError( + f"Cannot extract files from snapshot '{os.path.basename(snapshot)}': not a GNS3 project (invalid zip)" + ) # patch the topology with the correct project name and ID try: @@ -272,9 +283,13 @@ async def _import_snapshots(snapshots_path, project_name, project_id): with open(topology_file_path, "w+", encoding="utf-8") as f: json.dump(topology, f, indent=4, sort_keys=True) except OSError as e: - raise ControllerError(f"Cannot update snapshot '{os.path.basename(snapshot)}': the project.gns3 file cannot be modified: {e}") + raise ControllerError( + f"Cannot update snapshot '{os.path.basename(snapshot)}': the project.gns3 file cannot be modified: {e}" + ) except (ValueError, KeyError): - raise ControllerError(f"Cannot update snapshot '{os.path.basename(snapshot)}': the project.gns3 file is corrupted") + raise ControllerError( + f"Cannot update snapshot '{os.path.basename(snapshot)}': the project.gns3 file is corrupted" + ) # write everything back to the original snapshot file try: @@ -283,8 +298,10 @@ async def _import_snapshots(snapshots_path, project_name, project_id): for file in files: path = os.path.join(root, file) zstream.write(path, os.path.relpath(path, tmpdir)) - async with aiofiles.open(snapshot_path, 'wb+') as f: + async with aiofiles.open(snapshot_path, "wb+") as f: async for chunk in zstream: await f.write(chunk) except OSError as e: - raise ControllerError(f"Cannot update snapshot '{os.path.basename(snapshot)}': the snapshot cannot be recreated: {e}") + raise ControllerError( + f"Cannot update snapshot '{os.path.basename(snapshot)}': the snapshot cannot be recreated: {e}" + ) diff --git a/gns3server/controller/link.py b/gns3server/controller/link.py index 7541993f..a88dce33 100644 --- a/gns3server/controller/link.py +++ b/gns3server/controller/link.py @@ -23,6 +23,7 @@ import html from .controller_error import ControllerError, ControllerNotFoundError import logging + log = logging.getLogger(__name__) @@ -31,76 +32,35 @@ FILTERS = [ "type": "frequency_drop", "name": "Frequency drop", "description": "It will drop everything with a -1 frequency, drop every Nth packet with a positive frequency, or drop nothing", - "parameters": [ - { - "name": "Frequency", - "minimum": -1, - "maximum": 32767, - "type": "int", - "unit": "th packet" - } - ] + "parameters": [{"name": "Frequency", "minimum": -1, "maximum": 32767, "type": "int", "unit": "th packet"}], }, { "type": "packet_loss", "name": "Packet loss", "description": "The percentage represents the chance for a packet to be lost", - "parameters": [ - { - "name": "Chance", - "minimum": 0, - "maximum": 100, - "type": "int", - "unit": "%" - } - ] + "parameters": [{"name": "Chance", "minimum": 0, "maximum": 100, "type": "int", "unit": "%"}], }, { "type": "delay", "name": "Delay", "description": "Delay packets in milliseconds. You can add jitter in milliseconds (+/-) of the delay", "parameters": [ - { - "name": "Latency", - "minimum": 0, - "maximum": 32767, - "unit": "ms", - "type": "int" - }, - { - "name": "Jitter (-/+)", - "minimum": 0, - "maximum": 32767, - "unit": "ms", - "type": "int" - } - ] + {"name": "Latency", "minimum": 0, "maximum": 32767, "unit": "ms", "type": "int"}, + {"name": "Jitter (-/+)", "minimum": 0, "maximum": 32767, "unit": "ms", "type": "int"}, + ], }, { "type": "corrupt", "name": "Corrupt", "description": "The percentage represents the chance for a packet to be corrupted", - "parameters": [ - { - "name": "Chance", - "minimum": 0, - "maximum": 100, - "unit": "%", - "type": "int" - } - ] + "parameters": [{"name": "Chance", "minimum": 0, "maximum": 100, "unit": "%", "type": "int"}], }, { "type": "bpf", "name": "Berkeley Packet Filter (BPF)", "description": "This filter will drop any packet matching a BPF expression. Put one expression per line", - "parameters": [ - { - "name": "Filters", - "type": "text" - } - ] - } + "parameters": [{"name": "Filters", "type": "text"}], + }, ] @@ -193,7 +153,7 @@ class Link: else: new_values.append(int(value)) values = new_values - if len(values) != 0 and values[0] != 0 and values[0] != '': + if len(values) != 0 and values[0] != 0 and values[0] != "": new_filters[filter] = values if new_filters != self.filters: @@ -238,28 +198,32 @@ class Link: if node.node_type in ["nat", "cloud"]: if other_node["node"].node_type in ["nat", "cloud"]: - raise ControllerError("Connecting a {} to a {} is not allowed".format(other_node["node"].node_type, node.node_type)) + raise ControllerError( + "Connecting a {} to a {} is not allowed".format(other_node["node"].node_type, node.node_type) + ) # Check if user is not connecting serial => ethernet other_port = other_node["node"].get_port(other_node["adapter_number"], other_node["port_number"]) if other_port is None: - raise ControllerNotFoundError("Port {}/{} for {} not found".format(other_node["adapter_number"], other_node["port_number"], other_node["node"].name)) + raise ControllerNotFoundError( + "Port {}/{} for {} not found".format( + other_node["adapter_number"], other_node["port_number"], other_node["node"].name + ) + ) if port.link_type != other_port.link_type: - raise ControllerError(f"Connecting a {other_port.link_type} interface to a {port.link_type} interface is not allowed") + raise ControllerError( + f"Connecting a {other_port.link_type} interface to a {port.link_type} interface is not allowed" + ) if label is None: label = { "text": html.escape(f"{adapter_number}/{port_number}"), - "style": "font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;" + "style": "font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;", } - self._nodes.append({ - "node": node, - "adapter_number": adapter_number, - "port_number": port_number, - "port": port, - "label": label - }) + self._nodes.append( + {"node": node, "adapter_number": adapter_number, "port_number": port_number, "port": port, "label": label} + ) if len(self._nodes) == 2: await self.create() @@ -345,12 +309,16 @@ class Link: node_id = self.capture_node["node"].id adapter_number = self.capture_node["adapter_number"] port_number = self.capture_node["port_number"] - url = "/projects/{project_id}/{node_type}/nodes/{node_id}/adapters/{adapter_number}/" \ - "ports/{port_number}/capture/stream".format(project_id=self.project.id, - node_type=node_type, - node_id=node_id, - adapter_number=adapter_number, - port_number=port_number) + url = ( + "/projects/{project_id}/{node_type}/nodes/{node_id}/adapters/{adapter_number}/" + "ports/{port_number}/capture/stream".format( + project_id=self.project.id, + node_type=node_type, + node_id=node_id, + adapter_number=adapter_number, + port_number=port_number, + ) + ) return compute._getUrl(url) @@ -365,12 +333,14 @@ class Link: :returns: File name for a capture on this link """ - capture_file_name = "{}_{}-{}_to_{}_{}-{}".format(self._nodes[0]["node"].name, - self._nodes[0]["adapter_number"], - self._nodes[0]["port_number"], - self._nodes[1]["node"].name, - self._nodes[1]["adapter_number"], - self._nodes[1]["port_number"]) + capture_file_name = "{}_{}-{}_to_{}_{}-{}".format( + self._nodes[0]["node"].name, + self._nodes[0]["adapter_number"], + self._nodes[0]["port_number"], + self._nodes[1]["node"].name, + self._nodes[1]["adapter_number"], + self._nodes[1]["port_number"], + ) return re.sub(r"[^0-9A-Za-z_-]", "", capture_file_name) + ".pcap" @property @@ -379,7 +349,7 @@ class Link: @property def nodes(self): - return [node['node'] for node in self._nodes] + return [node["node"] for node in self._nodes] @property def capturing(self): @@ -425,16 +395,18 @@ class Link: :returns: None if no node support filtering else the node """ for node in self._nodes: - if node["node"].node_type in ('vpcs', - 'traceng', - 'vmware', - 'dynamips', - 'qemu', - 'iou', - 'cloud', - 'nat', - 'virtualbox', - 'docker'): + if node["node"].node_type in ( + "vpcs", + "traceng", + "vmware", + "dynamips", + "qemu", + "iou", + "cloud", + "nat", + "virtualbox", + "docker", + ): return node["node"] return None @@ -452,19 +424,16 @@ class Link: """ res = [] for side in self._nodes: - res.append({ - "node_id": side["node"].id, - "adapter_number": side["adapter_number"], - "port_number": side["port_number"], - "label": side["label"] - }) + res.append( + { + "node_id": side["node"].id, + "adapter_number": side["adapter_number"], + "port_number": side["port_number"], + "label": side["label"], + } + ) if topology_dump: - return { - "nodes": res, - "link_id": self._id, - "filters": self._filters, - "suspend": self._suspended - } + return {"nodes": res, "link_id": self._id, "filters": self._filters, "suspend": self._suspended} return { "nodes": res, "link_id": self._id, @@ -475,5 +444,5 @@ class Link: "capture_compute_id": self.capture_compute_id, "link_type": self._link_type, "filters": self._filters, - "suspend": self._suspended + "suspend": self._suspended, } diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index 27aeae3d..dea6c4cd 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -29,14 +29,29 @@ from ..utils.qt import qt_font_to_style import logging + log = logging.getLogger(__name__) class Node: # This properties are used only on controller and are not forwarded to the compute - CONTROLLER_ONLY_PROPERTIES = ["x", "y", "z", "locked", "width", "height", "symbol", "label", "console_host", - "port_name_format", "first_port_name", "port_segment_size", "ports", - "category", "console_auto_start"] + CONTROLLER_ONLY_PROPERTIES = [ + "x", + "y", + "z", + "locked", + "width", + "height", + "symbol", + "label", + "console_host", + "port_name_format", + "first_port_name", + "port_segment_size", + "ports", + "category", + "console_auto_start", + ] def __init__(self, project, compute, name, node_id=None, node_type=None, template_id=None, **kwargs): """ @@ -93,7 +108,7 @@ class Node: # This properties will be recompute ignore_properties = ("width", "height", "hover_symbol") - self.properties = kwargs.pop('properties', {}) + self.properties = kwargs.pop("properties", {}) # Update node properties with additional elements for prop in kwargs: @@ -124,10 +139,7 @@ class Node: :returns: Boolean True if the node is always running like ethernet switch """ - return self.node_type not in ( - "qemu", "docker", "dynamips", - "vpcs", "vmware", "virtualbox", - "iou", "traceng") + return self.node_type not in ("qemu", "docker", "dynamips", "vpcs", "vmware", "virtualbox", "iou", "traceng") @property def id(self): @@ -299,7 +311,7 @@ class Node: # Apply to label user style or default try: style = None # FIXME: allow configuration of default label font & color on controller - #style = qt_font_to_style(self._project.controller.settings["GraphicsView"]["default_label_font"], + # style = qt_font_to_style(self._project.controller.settings["GraphicsView"]["default_label_font"], # self._project.controller.settings["GraphicsView"]["default_label_color"]) except KeyError: style = "font-family: TypeWriter;font-size: 10.0;font-weight: bold;fill: #000000;fill-opacity: 1.0;" @@ -309,7 +321,7 @@ class Node: "text": html.escape(self._name), "style": style, # None: means the client will apply its default style "x": None, # None: means the client should center it - "rotation": 0 + "rotation": 0, } @property @@ -384,7 +396,9 @@ class Node: trial = 0 while trial != 6: try: - response = await self._compute.post(f"/projects/{self._project.id}/{self._node_type}/nodes", data=data, timeout=timeout) + response = await self._compute.post( + f"/projects/{self._project.id}/{self._node_type}/nodes", data=data, timeout=timeout + ) except ComputeConflict as e: if e.response.get("exception") == "ImageMissingError": res = await self._upload_missing_image(self._node_type, e.response["image"]) @@ -419,7 +433,12 @@ class Node: if prop == "properties": compute_properties = kwargs[prop] else: - if prop == "name" and self.status == "started" and self._node_type not in ("cloud", "nat", "ethernet_switch", "ethernet_hub", "frame_relay_switch", "atm_switch"): + if ( + prop == "name" + and self.status == "started" + and self._node_type + not in ("cloud", "nat", "ethernet_switch", "ethernet_hub", "frame_relay_switch", "atm_switch") + ): raise ControllerError("Sorry, it is not possible to rename a node that is already powered on") setattr(self, prop, kwargs[prop]) @@ -458,10 +477,14 @@ class Node: self._aux_type = value elif key == "name": self.name = value - elif key in ["node_id", "project_id", "console_host", - "startup_config_content", - "private_config_content", - "startup_script"]: + elif key in [ + "node_id", + "project_id", + "console_host", + "startup_config_content", + "private_config_content", + "startup_script", + ]: if key in self._properties: del self._properties[key] else: @@ -495,13 +518,26 @@ class Node: if self._console: # console is optional for builtin nodes data["console"] = self._console - if self._console_type and self._node_type not in ("cloud", "nat", "ethernet_hub", "frame_relay_switch", "atm_switch"): + if self._console_type and self._node_type not in ( + "cloud", + "nat", + "ethernet_hub", + "frame_relay_switch", + "atm_switch", + ): # console_type is not supported by all builtin nodes excepting Ethernet switch data["console_type"] = self._console_type if self._aux: # aux is optional for builtin nodes data["aux"] = self._aux - if self._aux_type and self._node_type not in ("cloud", "nat", "ethernet_switch", "ethernet_hub", "frame_relay_switch", "atm_switch"): + if self._aux_type and self._node_type not in ( + "cloud", + "nat", + "ethernet_switch", + "ethernet_hub", + "frame_relay_switch", + "atm_switch", + ): # aux_type is not supported by all builtin nodes data["aux_type"] = self._aux_type if self.custom_adapters: @@ -526,9 +562,11 @@ class Node: if self.node_type == "iou": license_check = self._project.controller.iou_license.get("license_check", True) iourc_content = self._project.controller.iou_license.get("iourc_content", None) - #if license_check and not iourc_content: + # if license_check and not iourc_content: # raise aiohttp.web.HTTPConflict(text="IOU licence is not configured") - await self.post("/start", timeout=240, data={"license_check": license_check, "iourc_content": iourc_content}) + await self.post( + "/start", timeout=240, data={"license_check": license_check, "iourc_content": iourc_content} + ) else: await self.post("/start", data=data, timeout=240) except asyncio.TimeoutError: @@ -580,9 +618,13 @@ class Node: HTTP post on the node """ if data: - return (await self._compute.post(f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}{path}", data=data, **kwargs)) + return await self._compute.post( + f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}{path}", data=data, **kwargs + ) else: - return (await self._compute.post(f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}{path}", **kwargs)) + return await self._compute.post( + f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}{path}", **kwargs + ) async def put(self, path, data=None, **kwargs): """ @@ -593,18 +635,22 @@ class Node: else: path = f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}{path}" if data: - return (await self._compute.put(path, data=data, **kwargs)) + return await self._compute.put(path, data=data, **kwargs) else: - return (await self._compute.put(path, **kwargs)) + return await self._compute.put(path, **kwargs) async def delete(self, path=None, **kwargs): """ HTTP post on the node """ if path is None: - return await self._compute.delete(f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}", **kwargs) + return await self._compute.delete( + f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}", **kwargs + ) else: - return await self._compute.delete(f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}{path}", **kwargs) + return await self._compute.delete( + f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}{path}", **kwargs + ) async def _upload_missing_image(self, type, img): """ @@ -617,8 +663,10 @@ class Node: if os.path.exists(image): self.project.emit_notification("log.info", {"message": f"Uploading missing image {img}"}) try: - with open(image, 'rb') as f: - await self._compute.post(f"/{self._node_type}/images/{os.path.basename(img)}", data=f, timeout=None) + with open(image, "rb") as f: + await self._compute.post( + f"/{self._node_type}/images/{os.path.basename(img)}", data=f, timeout=None + ) except OSError as e: raise ControllerError(f"Can't upload {image}: {str(e)}") self.project.emit_notification("log.info", {"message": f"Upload finished for {img}"}) @@ -629,13 +677,21 @@ class Node: """ Compute the idle PC for a dynamips node """ - return (await self._compute.get(f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}/auto_idlepc", timeout=240)).json + return ( + await self._compute.get( + f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}/auto_idlepc", timeout=240 + ) + ).json async def dynamips_idlepc_proposals(self): """ Compute a list of potential idle PC """ - return (await self._compute.get(f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}/idlepc_proposals", timeout=240)).json + return ( + await self._compute.get( + f"/projects/{self._project.id}/{self._node_type}/nodes/{self._id}/idlepc_proposals", timeout=240 + ) + ).json def get_port(self, adapter_number, port_number): """ @@ -693,7 +749,9 @@ class Node: # Basic node we don't want to have adapter number port_number = 0 for port in self._properties.get("ports_mapping", []): - self._ports.append(PortFactory(port["name"], 0, 0, port_number, "ethernet", short_name=f"e{port_number}")) + self._ports.append( + PortFactory(port["name"], 0, 0, port_number, "ethernet", short_name=f"e{port_number}") + ) port_number += 1 elif self._node_type in ("vpcs", "traceng"): self._ports.append(PortFactory("Ethernet0", 0, 0, 0, "ethernet", short_name="e0")) @@ -703,7 +761,14 @@ class Node: self._ports.append(PortFactory(port["name"], 0, 0, port_number, "ethernet", short_name=port["name"])) port_number += 1 else: - self._ports = StandardPortFactory(self._properties, self._port_by_adapter, self._first_port_name, self._port_name_format, self._port_segment_size, self._custom_adapters) + self._ports = StandardPortFactory( + self._properties, + self._port_by_adapter, + self._first_port_name, + self._port_name_format, + self._port_segment_size, + self._custom_adapters, + ) def __repr__(self): return f"" @@ -742,7 +807,7 @@ class Node: "port_name_format": self._port_name_format, "port_segment_size": self._port_segment_size, "first_port_name": self._first_port_name, - "custom_adapters": self._custom_adapters + "custom_adapters": self._custom_adapters, } return { "compute_id": str(self._compute.id), @@ -773,5 +838,5 @@ class Node: "port_segment_size": self._port_segment_size, "first_port_name": self._first_port_name, "custom_adapters": self._custom_adapters, - "ports": [port.__json__() for port in self.ports] + "ports": [port.__json__() for port in self.ports], } diff --git a/gns3server/controller/notification.py b/gns3server/controller/notification.py index 6d72ed07..9b9c3c5a 100644 --- a/gns3server/controller/notification.py +++ b/gns3server/controller/notification.py @@ -76,9 +76,10 @@ class Notification: os.makedirs("docs/api/notifications", exist_ok=True) try: import json + data = json.dumps(event, indent=4, sort_keys=True) if "MagicMock" not in data: - with open(os.path.join("docs/api/notifications", action + ".json"), 'w+') as f: + with open(os.path.join("docs/api/notifications", action + ".json"), "w+") as f: f.write(data) except TypeError: # If we receive a mock as an event it will raise TypeError when using json dump pass @@ -113,8 +114,8 @@ class Notification: except ControllerError: # Project closing return elif action == "ping": - event["compute_id"] = compute_id - self.project_emit(action, event) + event["compute_id"] = compute_id + self.project_emit(action, event) else: self.project_emit(action, event, project_id) @@ -131,9 +132,10 @@ class Notification: os.makedirs("docs/api/notifications", exist_ok=True) try: import json + data = json.dumps(event, indent=4, sort_keys=True) if "MagicMock" not in data: - with open(os.path.join("docs/api/notifications", action + ".json"), 'w+') as f: + with open(os.path.join("docs/api/notifications", action + ".json"), "w+") as f: f.write(data) except TypeError: # If we receive a mock as an event it will raise TypeError when using json dump pass diff --git a/gns3server/controller/ports/atm_port.py b/gns3server/controller/ports/atm_port.py index 01d5f2f3..b984e561 100644 --- a/gns3server/controller/ports/atm_port.py +++ b/gns3server/controller/ports/atm_port.py @@ -22,7 +22,6 @@ from .serial_port import SerialPort class ATMPort(SerialPort): - @staticmethod def long_name_type(): """ diff --git a/gns3server/controller/ports/fastethernet_port.py b/gns3server/controller/ports/fastethernet_port.py index 9e97f02d..0cf19719 100644 --- a/gns3server/controller/ports/fastethernet_port.py +++ b/gns3server/controller/ports/fastethernet_port.py @@ -22,7 +22,6 @@ from .port import Port class FastEthernetPort(Port): - @staticmethod def long_name_type(): """ diff --git a/gns3server/controller/ports/frame_relay_port.py b/gns3server/controller/ports/frame_relay_port.py index 1f2e0255..98f3fb27 100644 --- a/gns3server/controller/ports/frame_relay_port.py +++ b/gns3server/controller/ports/frame_relay_port.py @@ -22,7 +22,6 @@ from .serial_port import SerialPort class FrameRelayPort(SerialPort): - @staticmethod def long_name_type(): """ diff --git a/gns3server/controller/ports/gigabitethernet_port.py b/gns3server/controller/ports/gigabitethernet_port.py index 0b542273..98aa5710 100644 --- a/gns3server/controller/ports/gigabitethernet_port.py +++ b/gns3server/controller/ports/gigabitethernet_port.py @@ -22,7 +22,6 @@ from .port import Port class GigabitEthernetPort(Port): - @staticmethod def long_name_type(): """ diff --git a/gns3server/controller/ports/port.py b/gns3server/controller/ports/port.py index b2209889..9db29c45 100644 --- a/gns3server/controller/ports/port.py +++ b/gns3server/controller/ports/port.py @@ -83,7 +83,7 @@ class Port: # If port name format has changed we use the port name as the short name (1.X behavior) if self._short_name: return self._short_name - elif '/' in self._name: + elif "/" in self._name: return self._name.replace(self.long_name_type(), self.short_name_type()) elif self._name.startswith(f"{self.long_name_type()}{self._interface_number}"): return self.short_name_type() + f"{self._interface_number}" @@ -100,7 +100,7 @@ class Port: "data_link_types": self.data_link_types, "port_number": self._port_number, "adapter_number": self._adapter_number, - "link_type": self.link_type + "link_type": self.link_type, } if self._adapter_type: info["adapter_type"] = self._adapter_type diff --git a/gns3server/controller/ports/port_factory.py b/gns3server/controller/ports/port_factory.py index 26f1b7fb..efa97350 100644 --- a/gns3server/controller/ports/port_factory.py +++ b/gns3server/controller/ports/port_factory.py @@ -27,15 +27,16 @@ from .pos_port import POSPort import logging + log = logging.getLogger(__name__) PORTS = { - 'atm': ATMPort, - 'frame_relay': FrameRelayPort, - 'fastethernet': FastEthernetPort, - 'gigabitethernet': GigabitEthernetPort, - 'ethernet': EthernetPort, - 'serial': SerialPort + "atm": ATMPort, + "frame_relay": FrameRelayPort, + "fastethernet": FastEthernetPort, + "gigabitethernet": GigabitEthernetPort, + "ethernet": EthernetPort, + "serial": SerialPort, } @@ -52,7 +53,10 @@ class StandardPortFactory: """ Create ports for standard device """ - def __new__(cls, properties, port_by_adapter, first_port_name, port_name_format, port_segment_size, custom_adapters): + + def __new__( + cls, properties, port_by_adapter, first_port_name, port_name_format, port_segment_size, custom_adapters + ): ports = [] adapter_number = interface_number = segment_number = 0 @@ -72,14 +76,17 @@ class StandardPortFactory: for port_number in range(0, port_by_adapter): if first_port_name and adapter_number == 0: port_name = custom_adapter_settings.get("port_name", first_port_name) - port = PortFactory(port_name, segment_number, adapter_number, port_number, "ethernet", short_name=port_name) + port = PortFactory( + port_name, segment_number, adapter_number, port_number, "ethernet", short_name=port_name + ) else: try: port_name = port_name_format.format( interface_number, segment_number, adapter=adapter_number, - **cls._generate_replacement(interface_number, segment_number)) + **cls._generate_replacement(interface_number, segment_number), + ) except (IndexError, ValueError, KeyError) as e: raise ControllerError(f"Invalid port name format {port_name_format}: {str(e)}") @@ -106,7 +113,15 @@ class StandardPortFactory: if "serial_adapters" in properties: for adapter_number in range(adapter_number, properties["serial_adapters"] + adapter_number): for port_number in range(0, port_by_adapter): - ports.append(PortFactory(f"Serial{segment_number}/{port_number}", segment_number, adapter_number, port_number, "serial")) + ports.append( + PortFactory( + f"Serial{segment_number}/{port_number}", + segment_number, + adapter_number, + port_number, + "serial", + ) + ) segment_number += 1 return ports @@ -132,67 +147,38 @@ class DynamipsPortFactory: """ ADAPTER_MATRIX = { - "C1700-MB-1FE": {"nb_ports": 1, - "port": FastEthernetPort}, - "C1700-MB-WIC1": {"nb_ports": 0, - "port": None}, - "C2600-MB-1E": {"nb_ports": 1, - "port": EthernetPort}, - "C2600-MB-1FE": {"nb_ports": 1, - "port": FastEthernetPort}, - "C2600-MB-2E": {"nb_ports": 2, - "port": EthernetPort}, - "C2600-MB-2FE": {"nb_ports": 2, - "port": FastEthernetPort}, - "C7200-IO-2FE": {"nb_ports": 2, - "port": FastEthernetPort}, - "C7200-IO-FE": {"nb_ports": 1, - "port": FastEthernetPort}, - "C7200-IO-GE-E": {"nb_ports": 1, - "port": GigabitEthernetPort}, - "GT96100-FE": {"nb_ports": 2, - "port": FastEthernetPort}, - "Leopard-2FE": {"nb_ports": 2, - "port": FastEthernetPort}, - "NM-16ESW": {"nb_ports": 16, - "port": FastEthernetPort}, - "NM-1E": {"nb_ports": 1, - "port": EthernetPort}, - "NM-1FE-TX": {"nb_ports": 1, - "port": FastEthernetPort}, - "NM-4E": {"nb_ports": 4, - "port": EthernetPort}, - "NM-4T": {"nb_ports": 4, - "port": SerialPort}, - "PA-2FE-TX": {"nb_ports": 2, - "port": FastEthernetPort}, - "PA-4E": {"nb_ports": 4, - "port": EthernetPort}, - "PA-4T+": {"nb_ports": 4, - "port": SerialPort}, - "PA-8E": {"nb_ports": 8, - "port": EthernetPort}, - "PA-8T": {"nb_ports": 8, - "port": SerialPort}, - "PA-A1": {"nb_ports": 1, - "port": ATMPort}, - "PA-FE-TX": {"nb_ports": 1, - "port": FastEthernetPort}, - "PA-GE": {"nb_ports": 1, - "port": GigabitEthernetPort}, - "PA-POS-OC3": {"nb_ports": 1, - "port": POSPort}, + "C1700-MB-1FE": {"nb_ports": 1, "port": FastEthernetPort}, + "C1700-MB-WIC1": {"nb_ports": 0, "port": None}, + "C2600-MB-1E": {"nb_ports": 1, "port": EthernetPort}, + "C2600-MB-1FE": {"nb_ports": 1, "port": FastEthernetPort}, + "C2600-MB-2E": {"nb_ports": 2, "port": EthernetPort}, + "C2600-MB-2FE": {"nb_ports": 2, "port": FastEthernetPort}, + "C7200-IO-2FE": {"nb_ports": 2, "port": FastEthernetPort}, + "C7200-IO-FE": {"nb_ports": 1, "port": FastEthernetPort}, + "C7200-IO-GE-E": {"nb_ports": 1, "port": GigabitEthernetPort}, + "GT96100-FE": {"nb_ports": 2, "port": FastEthernetPort}, + "Leopard-2FE": {"nb_ports": 2, "port": FastEthernetPort}, + "NM-16ESW": {"nb_ports": 16, "port": FastEthernetPort}, + "NM-1E": {"nb_ports": 1, "port": EthernetPort}, + "NM-1FE-TX": {"nb_ports": 1, "port": FastEthernetPort}, + "NM-4E": {"nb_ports": 4, "port": EthernetPort}, + "NM-4T": {"nb_ports": 4, "port": SerialPort}, + "PA-2FE-TX": {"nb_ports": 2, "port": FastEthernetPort}, + "PA-4E": {"nb_ports": 4, "port": EthernetPort}, + "PA-4T+": {"nb_ports": 4, "port": SerialPort}, + "PA-8E": {"nb_ports": 8, "port": EthernetPort}, + "PA-8T": {"nb_ports": 8, "port": SerialPort}, + "PA-A1": {"nb_ports": 1, "port": ATMPort}, + "PA-FE-TX": {"nb_ports": 1, "port": FastEthernetPort}, + "PA-GE": {"nb_ports": 1, "port": GigabitEthernetPort}, + "PA-POS-OC3": {"nb_ports": 1, "port": POSPort}, } - WIC_MATRIX = {"WIC-1ENET": {"nb_ports": 1, - "port": EthernetPort}, - - "WIC-1T": {"nb_ports": 1, - "port": SerialPort}, - - "WIC-2T": {"nb_ports": 2, - "port": SerialPort} - } + WIC_MATRIX = { + "WIC-1ENET": {"nb_ports": 1, "port": EthernetPort}, + "WIC-1T": {"nb_ports": 1, "port": SerialPort}, + "WIC-2T": {"nb_ports": 2, "port": SerialPort}, + } def __new__(cls, properties): diff --git a/gns3server/controller/ports/pos_port.py b/gns3server/controller/ports/pos_port.py index 296f466e..01398914 100644 --- a/gns3server/controller/ports/pos_port.py +++ b/gns3server/controller/ports/pos_port.py @@ -22,7 +22,6 @@ from .serial_port import SerialPort class POSPort(SerialPort): - @staticmethod def long_name_type(): """ diff --git a/gns3server/controller/ports/serial_port.py b/gns3server/controller/ports/serial_port.py index 62a75d59..64629211 100644 --- a/gns3server/controller/ports/serial_port.py +++ b/gns3server/controller/ports/serial_port.py @@ -22,7 +22,6 @@ from .port import Port class SerialPort(Port): - @staticmethod def long_name_type(): """ @@ -61,6 +60,4 @@ class SerialPort(Port): :return: dictionary """ - return {"Frame Relay": "DLT_FRELAY", - "Cisco HDLC": "DLT_C_HDLC", - "Cisco PPP": "DLT_PPP_SERIAL"} + return {"Frame Relay": "DLT_FRELAY", "Cisco HDLC": "DLT_C_HDLC", "Cisco PPP": "DLT_PPP_SERIAL"} diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 709821b2..8a5c7d3c 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -46,6 +46,7 @@ from .import_project import import_project from .controller_error import ControllerError, ControllerForbiddenError, ControllerNotFoundError import logging + log = logging.getLogger(__name__) @@ -58,6 +59,7 @@ def open_required(func): if self._status == "closed": raise ControllerForbiddenError("The project is not opened") return func(self, *args, **kwargs) + return wrapper @@ -70,10 +72,29 @@ class Project: :param status: Status of the project (opened / closed) """ - def __init__(self, name=None, project_id=None, path=None, controller=None, status="opened", - filename=None, auto_start=False, auto_open=False, auto_close=True, - scene_height=1000, scene_width=2000, zoom=100, show_layers=False, snap_to_grid=False, show_grid=False, - grid_size=75, drawing_grid_size=25, show_interface_labels=False, variables=None, supplier=None): + def __init__( + self, + name=None, + project_id=None, + path=None, + controller=None, + status="opened", + filename=None, + auto_start=False, + auto_open=False, + auto_close=True, + scene_height=1000, + scene_width=2000, + zoom=100, + show_layers=False, + snap_to_grid=False, + show_grid=False, + grid_size=75, + drawing_grid_size=25, + show_interface_labels=False, + variables=None, + supplier=None, + ): self._controller = controller assert name is not None @@ -160,11 +181,7 @@ class Project: # update on computes for compute in list(self._project_created_on_compute): - await compute.put( - f"/projects/{self._id}", { - "variables": self.variables - } - ) + await compute.put(f"/projects/{self._id}", {"variables": self.variables}) def reset(self): """ @@ -409,7 +426,9 @@ class Project: raise ControllerError(f"Could not create project directory: {e}") if '"' in path: - raise ControllerForbiddenError("You are not allowed to use \" in the project directory path. Not supported by Dynamips.") + raise ControllerForbiddenError( + 'You are not allowed to use " in the project directory path. Not supported by Dynamips.' + ) self._path = path @@ -465,7 +484,7 @@ class Project: if base_name in self._allocated_node_names: base_name = re.sub(r"[0-9]+$", "{0}", base_name) - if '{0}' in base_name or '{id}' in base_name: + if "{0}" in base_name or "{id}" in base_name: # base name is a template, replace {0} or {id} by an unique identifier for number in range(1, 1000000): try: @@ -524,16 +543,9 @@ class Project: if compute not in self._project_created_on_compute: # For a local server we send the project path if compute.id == "local": - data = { - "name": self._name, - "project_id": self._id, - "path": self._path - } + data = {"name": self._name, "project_id": self._id, "path": self._path} else: - data = { - "name": self._name, - "project_id": self._id - } + data = {"name": self._name, "project_id": self._id} if self._variables: data["variables"] = self._variables @@ -568,7 +580,9 @@ class Project: # to generate MAC addresses) when creating multiple IOU node at the same time if "properties" in kwargs.keys(): # allocate a new application id for nodes loaded from the project - kwargs.get("properties")["application_id"] = get_next_application_id(self._controller.projects, self._computes) + kwargs.get("properties")["application_id"] = get_next_application_id( + self._controller.projects, self._computes + ) elif "application_id" not in kwargs.keys() and not kwargs.get("properties"): # allocate a new application id for nodes added to the project kwargs["application_id"] = get_next_application_id(self._controller.projects, self._computes) @@ -821,7 +835,7 @@ class Project: # don't remove supplier's logo if self.supplier: try: - logo = self.supplier['logo'] + logo = self.supplier["logo"] pictures.remove(logo) except KeyError: pass @@ -846,7 +860,9 @@ class Project: try: project_directory = get_default_project_directory() if not os.path.commonprefix([project_directory, self.path]) == project_directory: - raise ControllerError(f"Project '{self._name}' cannot be deleted because it is not in the default project directory: '{project_directory}'") + raise ControllerError( + f"Project '{self._name}' cannot be deleted because it is not in the default project directory: '{project_directory}'" + ) shutil.rmtree(self.path) except OSError as e: raise ControllerError(f"Cannot delete project directory {self.path}: {str(e)}") @@ -906,7 +922,7 @@ class Project: try: project_data = load_topology(path) - #load meta of project + # load meta of project keys_to_load = [ "auto_start", "auto_close", @@ -919,7 +935,7 @@ class Project: "show_grid", "grid_size", "drawing_grid_size", - "show_interface_labels" + "show_interface_labels", ] for key in keys_to_load: @@ -944,7 +960,7 @@ class Project: node_id = node.pop("node_id", str(uuid.uuid4())) await self.add_node(compute, name, node_id, dump=False, **node) for link_data in topology.get("links", []): - if 'link_id' not in link_data.keys(): + if "link_id" not in link_data.keys(): # skip the link continue link = await self.add_link(link_id=link_data["link_id"]) @@ -954,12 +970,26 @@ class Project: node = self.get_node(node_link["node_id"]) port = node.get_port(node_link["adapter_number"], node_link["port_number"]) if port is None: - log.warning("Port {}/{} for {} not found".format(node_link["adapter_number"], node_link["port_number"], node.name)) + log.warning( + "Port {}/{} for {} not found".format( + node_link["adapter_number"], node_link["port_number"], node.name + ) + ) continue if port.link is not None: - log.warning("Port {}/{} is already connected to link ID {}".format(node_link["adapter_number"], node_link["port_number"], port.link.id)) + log.warning( + "Port {}/{} is already connected to link ID {}".format( + node_link["adapter_number"], node_link["port_number"], port.link.id + ) + ) continue - await link.add_node(node, node_link["adapter_number"], node_link["port_number"], label=node_link.get("label"), dump=False) + await link.add_node( + node, + node_link["adapter_number"], + node_link["port_number"], + label=node_link.get("label"), + dump=False, + ) if len(link.nodes) != 2: # a link should have 2 attached nodes, this can happen with corrupted projects await self.delete_link(link.id, force_delete=True) @@ -1039,18 +1069,27 @@ class Project: with tempfile.TemporaryDirectory(dir=working_dir) as tmpdir: # Do not compress the exported project when duplicating with aiozipstream.ZipFile(compression=zipfile.ZIP_STORED) as zstream: - await export_project(zstream, self, tmpdir, keep_compute_id=True, allow_all_nodes=True, reset_mac_addresses=reset_mac_addresses) + await export_project( + zstream, + self, + tmpdir, + keep_compute_id=True, + allow_all_nodes=True, + reset_mac_addresses=reset_mac_addresses, + ) # export the project to a temporary location project_path = os.path.join(tmpdir, "project.gns3p") log.info(f"Exporting project to '{project_path}'") - async with aiofiles.open(project_path, 'wb') as f: + async with aiofiles.open(project_path, "wb") as f: async for chunk in zstream: await f.write(chunk) # import the temporary project with open(project_path, "rb") as f: - project = await import_project(self._controller, str(uuid.uuid4()), f, location=location, name=name, keep_compute_id=True) + project = await import_project( + self._controller, str(uuid.uuid4()), f, location=location, name=name, keep_compute_id=True + ) log.info(f"Project '{project.name}' duplicated in {time.time() - begin:.4f} seconds") except (ValueError, OSError, UnicodeEncodeError) as e: @@ -1143,31 +1182,26 @@ class Project: data = copy.deepcopy(node.__json__(topology_dump=True)) # Some properties like internal ID should not be duplicated for unique_property in ( - 'node_id', - 'name', - 'mac_addr', - 'mac_address', - 'compute_id', - 'application_id', - 'dynamips_id'): + "node_id", + "name", + "mac_addr", + "mac_address", + "compute_id", + "application_id", + "dynamips_id", + ): data.pop(unique_property, None) - if 'properties' in data: - data['properties'].pop(unique_property, None) - node_type = data.pop('node_type') - data['x'] = x - data['y'] = y - data['z'] = z - data['locked'] = False # duplicated node must not be locked + if "properties" in data: + data["properties"].pop(unique_property, None) + node_type = data.pop("node_type") + data["x"] = x + data["y"] = y + data["z"] = z + data["locked"] = False # duplicated node must not be locked new_node_uuid = str(uuid.uuid4()) - new_node = await self.add_node(node.compute, - node.name, - new_node_uuid, - node_type=node_type, - **data) + new_node = await self.add_node(node.compute, node.name, new_node_uuid, node_type=node_type, **data) try: - await node.post("/duplicate", timeout=None, data={ - "destination_node_id": new_node_uuid - }) + await node.post("/duplicate", timeout=None, data={"destination_node_id": new_node_uuid}) except ControllerNotFoundError: await self.delete_node(new_node_uuid) raise ControllerError("This node type cannot be duplicated") @@ -1182,7 +1216,7 @@ class Project: "nodes": len(self._nodes), "links": len(self._links), "drawings": len(self._drawings), - "snapshots": len(self._snapshots) + "snapshots": len(self._snapshots), } def __json__(self): @@ -1205,7 +1239,7 @@ class Project: "drawing_grid_size": self._drawing_grid_size, "show_interface_labels": self._show_interface_labels, "supplier": self._supplier, - "variables": self._variables + "variables": self._variables, } def __repr__(self): diff --git a/gns3server/controller/snapshot.py b/gns3server/controller/snapshot.py index dcc4b6c7..e0af79af 100644 --- a/gns3server/controller/snapshot.py +++ b/gns3server/controller/snapshot.py @@ -32,6 +32,7 @@ from .export_project import export_project from .import_project import import_project import logging + log = logging.getLogger(__name__) @@ -48,17 +49,26 @@ class Snapshot: assert filename or name, "You need to pass a name or a filename" - self._id = str(uuid.uuid4()) # We don't need to keep id between project loading because they are use only as key for operation like delete, update.. but have no impact on disk + self._id = str( + uuid.uuid4() + ) # We don't need to keep id between project loading because they are use only as key for operation like delete, update.. but have no impact on disk self._project = project if name: self._name = name self._created_at = datetime.now().timestamp() - filename = self._name + "_" + datetime.utcfromtimestamp(self._created_at).replace(tzinfo=None).strftime(FILENAME_TIME_FORMAT) + ".gns3project" + filename = ( + self._name + + "_" + + datetime.utcfromtimestamp(self._created_at).replace(tzinfo=None).strftime(FILENAME_TIME_FORMAT) + + ".gns3project" + ) else: self._name = filename.split("_")[0] datestring = filename.replace(self._name + "_", "").split(".")[0] try: - self._created_at = datetime.strptime(datestring, FILENAME_TIME_FORMAT).replace(tzinfo=timezone.utc).timestamp() + self._created_at = ( + datetime.strptime(datestring, FILENAME_TIME_FORMAT).replace(tzinfo=timezone.utc).timestamp() + ) except ValueError: self._created_at = datetime.utcnow().timestamp() self._path = os.path.join(project.path, "snapshots", filename) @@ -99,7 +109,7 @@ class Snapshot: # Do not compress the snapshots with aiozipstream.ZipFile(compression=zipfile.ZIP_STORED) as zstream: await export_project(zstream, self._project, tmpdir, keep_compute_id=True, allow_all_nodes=True) - async with aiofiles.open(self.path, 'wb') as f: + async with aiofiles.open(self.path, "wb") as f: async for chunk in zstream: await f.write(chunk) log.info(f"Snapshot '{self.name}' created in {time.time() - begin:.4f} seconds") @@ -121,7 +131,9 @@ class Snapshot: if os.path.exists(project_files_path): await wait_run_in_executor(shutil.rmtree, project_files_path) with open(self._path, "rb") as f: - project = await import_project(self._project.controller, self._project.id, f, location=self._project.path) + project = await import_project( + self._project.controller, self._project.id, f, location=self._project.path + ) except (OSError, PermissionError) as e: raise ControllerError(str(e)) await project.open() @@ -133,5 +145,5 @@ class Snapshot: "snapshot_id": self._id, "name": self._name, "created_at": int(self._created_at), - "project_id": self._project.id + "project_id": self._project.id, } diff --git a/gns3server/controller/symbol_themes.py b/gns3server/controller/symbol_themes.py index 68937ce6..9ea77eca 100644 --- a/gns3server/controller/symbol_themes.py +++ b/gns3server/controller/symbol_themes.py @@ -16,115 +16,131 @@ # along with this program. If not, see . -CLASSIC_SYMBOL_THEME = {"cloud": ":/symbols/classic/cloud.svg", - "ethernet_switch": ":/symbols/classic/ethernet_switch.svg", - "ethernet_hub": ":/symbols/classic/hub.svg", - "frame_relay_switch": ":/symbols/classic/frame_relay_switch.svg", - "atm_switch": ":/symbols/classic/atm_switch.svg", - "router": ":/symbols/classic/router.svg", - "multilayer_switch": ":/symbols/classic/multilayer_switch.svg", - "firewall": ":/symbols/classic/firewall.svg", - "computer": ":/symbols/classic/computer.svg", - "vpcs_guest": ":/symbols/classic/vpcs_guest.svg", - "qemu_guest": ":/symbols/classic/qemu_guest.svg", - "vbox_guest": ":/symbols/classic/vbox_guest.svg", - "vmware_guest": ":/symbols/classic/vmware_guest.svg", - "docker_guest": ":/symbols/classic/docker_guest.svg"} +CLASSIC_SYMBOL_THEME = { + "cloud": ":/symbols/classic/cloud.svg", + "ethernet_switch": ":/symbols/classic/ethernet_switch.svg", + "ethernet_hub": ":/symbols/classic/hub.svg", + "frame_relay_switch": ":/symbols/classic/frame_relay_switch.svg", + "atm_switch": ":/symbols/classic/atm_switch.svg", + "router": ":/symbols/classic/router.svg", + "multilayer_switch": ":/symbols/classic/multilayer_switch.svg", + "firewall": ":/symbols/classic/firewall.svg", + "computer": ":/symbols/classic/computer.svg", + "vpcs_guest": ":/symbols/classic/vpcs_guest.svg", + "qemu_guest": ":/symbols/classic/qemu_guest.svg", + "vbox_guest": ":/symbols/classic/vbox_guest.svg", + "vmware_guest": ":/symbols/classic/vmware_guest.svg", + "docker_guest": ":/symbols/classic/docker_guest.svg", +} -AFFINITY_SQUARE_BLUE_SYMBOL_THEME = {"cloud": ":/symbols/affinity/square/blue/cloud.svg", - "ethernet_switch": ":/symbols/affinity/square/blue/switch.svg", - "ethernet_hub": ":/symbols/affinity/square/blue/hub.svg", - "frame_relay_switch.svg": ":/symbols/affinity/square/blue/isdn.svg", - "atm_switch": ":/symbols/affinity/square/blue/atm.svg", - "router": ":/symbols/affinity/square/blue/router.svg", - "multilayer_switch": ":/symbols/affinity/square/blue/switch_multilayer.svg", - "firewall": ":/symbols/affinity/square/blue/firewall3.svg", - "computer": ":/symbols/affinity/square/blue/client.svg", - "vpcs_guest": ":/symbols/affinity/square/blue/client.svg", - "qemu_guest": ":/symbols/affinity/square/blue/client_vm.svg", - "vbox_guest": ":/symbols/affinity/square/blue/virtualbox.svg", - "vmware_guest": ":/symbols/affinity/square/blue/vmware.svg", - "docker_guest": ":/symbols/affinity/square/blue/docker.svg"} +AFFINITY_SQUARE_BLUE_SYMBOL_THEME = { + "cloud": ":/symbols/affinity/square/blue/cloud.svg", + "ethernet_switch": ":/symbols/affinity/square/blue/switch.svg", + "ethernet_hub": ":/symbols/affinity/square/blue/hub.svg", + "frame_relay_switch.svg": ":/symbols/affinity/square/blue/isdn.svg", + "atm_switch": ":/symbols/affinity/square/blue/atm.svg", + "router": ":/symbols/affinity/square/blue/router.svg", + "multilayer_switch": ":/symbols/affinity/square/blue/switch_multilayer.svg", + "firewall": ":/symbols/affinity/square/blue/firewall3.svg", + "computer": ":/symbols/affinity/square/blue/client.svg", + "vpcs_guest": ":/symbols/affinity/square/blue/client.svg", + "qemu_guest": ":/symbols/affinity/square/blue/client_vm.svg", + "vbox_guest": ":/symbols/affinity/square/blue/virtualbox.svg", + "vmware_guest": ":/symbols/affinity/square/blue/vmware.svg", + "docker_guest": ":/symbols/affinity/square/blue/docker.svg", +} -AFFINITY_SQUARE_RED_SYMBOL_THEME = {"cloud": ":/symbols/affinity/square/red/cloud.svg", - "ethernet_switch": ":/symbols/affinity/square/red/switch.svg", - "ethernet_hub": ":/symbols/affinity/square/red/hub.svg", - "frame_relay_switch": ":/symbols/affinity/square/red/isdn.svg", - "atm_switch": ":/symbols/affinity/square/red/atm.svg", - "router": ":/symbols/affinity/square/red/router.svg", - "multilayer_switch": ":/symbols/affinity/square/red/switch_multilayer.svg", - "firewall": ":/symbols/affinity/square/red/firewall3.svg", - "computer": ":/symbols/affinity/square/red/client.svg", - "vpcs_guest": ":/symbols/affinity/square/red/client.svg", - "qemu_guest": ":/symbols/affinity/square/red/client_vm.svg", - "vbox_guest": ":/symbols/affinity/square/red/virtualbox.svg", - "vmware_guest": ":/symbols/affinity/square/red/vmware.svg", - "docker_guest": ":/symbols/affinity/square/red/docker.svg"} +AFFINITY_SQUARE_RED_SYMBOL_THEME = { + "cloud": ":/symbols/affinity/square/red/cloud.svg", + "ethernet_switch": ":/symbols/affinity/square/red/switch.svg", + "ethernet_hub": ":/symbols/affinity/square/red/hub.svg", + "frame_relay_switch": ":/symbols/affinity/square/red/isdn.svg", + "atm_switch": ":/symbols/affinity/square/red/atm.svg", + "router": ":/symbols/affinity/square/red/router.svg", + "multilayer_switch": ":/symbols/affinity/square/red/switch_multilayer.svg", + "firewall": ":/symbols/affinity/square/red/firewall3.svg", + "computer": ":/symbols/affinity/square/red/client.svg", + "vpcs_guest": ":/symbols/affinity/square/red/client.svg", + "qemu_guest": ":/symbols/affinity/square/red/client_vm.svg", + "vbox_guest": ":/symbols/affinity/square/red/virtualbox.svg", + "vmware_guest": ":/symbols/affinity/square/red/vmware.svg", + "docker_guest": ":/symbols/affinity/square/red/docker.svg", +} -AFFINITY_SQUARE_GRAY_SYMBOL_THEME = {"cloud": ":/symbols/affinity/square/gray/cloud.svg", - "ethernet_switch": ":/symbols/affinity/square/gray/switch.svg", - "ethernet_hub": ":/symbols/affinity/square/gray/hub.svg", - "frame_relay_switch": ":/symbols/affinity/square/gray/isdn.svg", - "atm_switch": ":/symbols/affinity/square/gray/atm.svg", - "router": ":/symbols/affinity/square/gray/router.svg", - "multilayer_switch": ":/symbols/affinity/square/gray/switch_multilayer.svg", - "firewall": ":/symbols/affinity/square/gray/firewall3.svg", - "computer": ":/symbols/affinity/square/gray/client.svg", - "vpcs_guest": ":/symbols/affinity/square/gray/client.svg", - "qemu_guest": ":/symbols/affinity/square/gray/client_vm.svg", - "vbox_guest": ":/symbols/affinity/square/gray/virtualbox.svg", - "vmware_guest": ":/symbols/affinity/square/gray/vmware.svg", - "docker_guest": ":/symbols/affinity/square/gray/docker.svg"} +AFFINITY_SQUARE_GRAY_SYMBOL_THEME = { + "cloud": ":/symbols/affinity/square/gray/cloud.svg", + "ethernet_switch": ":/symbols/affinity/square/gray/switch.svg", + "ethernet_hub": ":/symbols/affinity/square/gray/hub.svg", + "frame_relay_switch": ":/symbols/affinity/square/gray/isdn.svg", + "atm_switch": ":/symbols/affinity/square/gray/atm.svg", + "router": ":/symbols/affinity/square/gray/router.svg", + "multilayer_switch": ":/symbols/affinity/square/gray/switch_multilayer.svg", + "firewall": ":/symbols/affinity/square/gray/firewall3.svg", + "computer": ":/symbols/affinity/square/gray/client.svg", + "vpcs_guest": ":/symbols/affinity/square/gray/client.svg", + "qemu_guest": ":/symbols/affinity/square/gray/client_vm.svg", + "vbox_guest": ":/symbols/affinity/square/gray/virtualbox.svg", + "vmware_guest": ":/symbols/affinity/square/gray/vmware.svg", + "docker_guest": ":/symbols/affinity/square/gray/docker.svg", +} -AFFINITY_CIRCLE_BLUE_SYMBOL_THEME = {"cloud": ":/symbols/affinity/circle/blue/cloud.svg", - "ethernet_switch": ":/symbols/affinity/circle/blue/switch.svg", - "ethernet_hub": ":/symbols/affinity/circle/blue/hub.svg", - "frame_relay_switch": ":/symbols/affinity/circle/blue/isdn.svg", - "atm_switch": ":/symbols/affinity/circle/blue/atm.svg", - "router": ":/symbols/affinity/circle/blue/router.svg", - "multilayer_switch": ":/symbols/affinity/circle/blue/switch_multilayer.svg", - "firewall": ":/symbols/affinity/circle/blue/firewall3.svg", - "computer": ":/symbols/affinity/circle/blue/client.svg", - "vpcs_guest": ":/symbols/affinity/circle/blue/client.svg", - "qemu_guest": ":/symbols/affinity/circle/blue/client_vm.svg", - "vbox_guest": ":/symbols/affinity/circle/blue/virtualbox.svg", - "vmware_guest": ":/symbols/affinity/circle/blue/vmware.svg", - "docker_guest": ":/symbols/affinity/circle/blue/docker.svg"} +AFFINITY_CIRCLE_BLUE_SYMBOL_THEME = { + "cloud": ":/symbols/affinity/circle/blue/cloud.svg", + "ethernet_switch": ":/symbols/affinity/circle/blue/switch.svg", + "ethernet_hub": ":/symbols/affinity/circle/blue/hub.svg", + "frame_relay_switch": ":/symbols/affinity/circle/blue/isdn.svg", + "atm_switch": ":/symbols/affinity/circle/blue/atm.svg", + "router": ":/symbols/affinity/circle/blue/router.svg", + "multilayer_switch": ":/symbols/affinity/circle/blue/switch_multilayer.svg", + "firewall": ":/symbols/affinity/circle/blue/firewall3.svg", + "computer": ":/symbols/affinity/circle/blue/client.svg", + "vpcs_guest": ":/symbols/affinity/circle/blue/client.svg", + "qemu_guest": ":/symbols/affinity/circle/blue/client_vm.svg", + "vbox_guest": ":/symbols/affinity/circle/blue/virtualbox.svg", + "vmware_guest": ":/symbols/affinity/circle/blue/vmware.svg", + "docker_guest": ":/symbols/affinity/circle/blue/docker.svg", +} -AFFINITY_CIRCLE_RED_SYMBOL_THEME = {"cloud": ":/symbols/affinity/circle/red/cloud.svg", - "ethernet_switch": ":/symbols/affinity/circle/red/switch.svg", - "ethernet_hub": ":/symbols/affinity/circle/red/hub.svg", - "frame_relay_switch": ":/symbols/affinity/circle/red/isdn.svg", - "atm_switch": ":/symbols/affinity/circle/red/atm.svg", - "router": ":/symbols/affinity/circle/red/router.svg", - "multilayer_switch": ":/symbols/affinity/circle/red/switch_multilayer.svg", - "firewall": ":/symbols/affinity/circle/red/firewall3.svg", - "computer": ":/symbols/affinity/circle/red/client.svg", - "vpcs_guest": ":/symbols/affinity/circle/red/client.svg", - "qemu_guest": ":/symbols/affinity/circle/red/client_vm.svg", - "vbox_guest": ":/symbols/affinity/circle/red/virtualbox.svg", - "vmware_guest": ":/symbols/affinity/circle/red/vmware.svg", - "docker_guest": ":/symbols/affinity/circle/red/docker.svg"} +AFFINITY_CIRCLE_RED_SYMBOL_THEME = { + "cloud": ":/symbols/affinity/circle/red/cloud.svg", + "ethernet_switch": ":/symbols/affinity/circle/red/switch.svg", + "ethernet_hub": ":/symbols/affinity/circle/red/hub.svg", + "frame_relay_switch": ":/symbols/affinity/circle/red/isdn.svg", + "atm_switch": ":/symbols/affinity/circle/red/atm.svg", + "router": ":/symbols/affinity/circle/red/router.svg", + "multilayer_switch": ":/symbols/affinity/circle/red/switch_multilayer.svg", + "firewall": ":/symbols/affinity/circle/red/firewall3.svg", + "computer": ":/symbols/affinity/circle/red/client.svg", + "vpcs_guest": ":/symbols/affinity/circle/red/client.svg", + "qemu_guest": ":/symbols/affinity/circle/red/client_vm.svg", + "vbox_guest": ":/symbols/affinity/circle/red/virtualbox.svg", + "vmware_guest": ":/symbols/affinity/circle/red/vmware.svg", + "docker_guest": ":/symbols/affinity/circle/red/docker.svg", +} -AFFINITY_CIRCLE_GRAY_SYMBOL_THEME = {"cloud": ":/symbols/affinity/circle/gray/cloud.svg", - "ethernet_switch": ":/symbols/affinity/circle/gray/switch.svg", - "ethernet_hub": ":/symbols/affinity/circle/gray/hub.svg", - "frame_relay_switch": ":/symbols/affinity/circle/gray/isdn.svg", - "atm_switch": ":/symbols/affinity/circle/gray/atm.svg", - "router": ":/symbols/affinity/circle/gray/router.svg", - "multilayer_switch": ":/symbols/affinity/circle/gray/switch_multilayer.svg", - "firewall": ":/symbols/affinity/circle/gray/firewall3.svg", - "computer": ":/symbols/affinity/circle/gray/client.svg", - "vpcs_guest": ":/symbols/affinity/circle/gray/client.svg", - "qemu_guest": ":/symbols/affinity/circle/gray/client_vm.svg", - "vbox_guest": ":/symbols/affinity/circle/gray/virtualbox.svg", - "vmware_guest": ":/symbols/affinity/circle/gray/vmware.svg", - "docker_guest": ":/symbols/affinity/circle/gray/docker.svg"} +AFFINITY_CIRCLE_GRAY_SYMBOL_THEME = { + "cloud": ":/symbols/affinity/circle/gray/cloud.svg", + "ethernet_switch": ":/symbols/affinity/circle/gray/switch.svg", + "ethernet_hub": ":/symbols/affinity/circle/gray/hub.svg", + "frame_relay_switch": ":/symbols/affinity/circle/gray/isdn.svg", + "atm_switch": ":/symbols/affinity/circle/gray/atm.svg", + "router": ":/symbols/affinity/circle/gray/router.svg", + "multilayer_switch": ":/symbols/affinity/circle/gray/switch_multilayer.svg", + "firewall": ":/symbols/affinity/circle/gray/firewall3.svg", + "computer": ":/symbols/affinity/circle/gray/client.svg", + "vpcs_guest": ":/symbols/affinity/circle/gray/client.svg", + "qemu_guest": ":/symbols/affinity/circle/gray/client_vm.svg", + "vbox_guest": ":/symbols/affinity/circle/gray/virtualbox.svg", + "vmware_guest": ":/symbols/affinity/circle/gray/vmware.svg", + "docker_guest": ":/symbols/affinity/circle/gray/docker.svg", +} -BUILTIN_SYMBOL_THEMES = {"Classic": CLASSIC_SYMBOL_THEME, - "Affinity-square-blue": AFFINITY_SQUARE_BLUE_SYMBOL_THEME, - "Affinity-square-red": AFFINITY_SQUARE_RED_SYMBOL_THEME, - "Affinity-square-gray": AFFINITY_SQUARE_GRAY_SYMBOL_THEME, - "Affinity-circle-blue": AFFINITY_CIRCLE_BLUE_SYMBOL_THEME, - "Affinity-circle-red": AFFINITY_CIRCLE_RED_SYMBOL_THEME, - "Affinity-circle-gray": AFFINITY_CIRCLE_GRAY_SYMBOL_THEME} +BUILTIN_SYMBOL_THEMES = { + "Classic": CLASSIC_SYMBOL_THEME, + "Affinity-square-blue": AFFINITY_SQUARE_BLUE_SYMBOL_THEME, + "Affinity-square-red": AFFINITY_SQUARE_RED_SYMBOL_THEME, + "Affinity-square-gray": AFFINITY_SQUARE_GRAY_SYMBOL_THEME, + "Affinity-circle-blue": AFFINITY_CIRCLE_BLUE_SYMBOL_THEME, + "Affinity-circle-red": AFFINITY_CIRCLE_RED_SYMBOL_THEME, + "Affinity-circle-gray": AFFINITY_CIRCLE_GRAY_SYMBOL_THEME, +} diff --git a/gns3server/controller/symbols.py b/gns3server/controller/symbols.py index e1644444..395fb947 100644 --- a/gns3server/controller/symbols.py +++ b/gns3server/controller/symbols.py @@ -25,6 +25,7 @@ from ..utils.picture import get_size from ..config import Config import logging + log = logging.getLogger(__name__) @@ -79,33 +80,31 @@ class Symbols: if get_resource("symbols"): for root, _, files in os.walk(get_resource("symbols")): for filename in files: - if filename.startswith('.'): + if filename.startswith("."): continue - symbol_file = posixpath.normpath(os.path.relpath(os.path.join(root, filename), get_resource("symbols"))).replace('\\', '/') - theme = posixpath.dirname(symbol_file).replace('/', '-').capitalize() + symbol_file = posixpath.normpath( + os.path.relpath(os.path.join(root, filename), get_resource("symbols")) + ).replace("\\", "/") + theme = posixpath.dirname(symbol_file).replace("/", "-").capitalize() if not theme: continue - symbol_id = ':/symbols/' + symbol_file - symbols.append({'symbol_id': symbol_id, - 'filename': filename, - 'theme': theme, - 'builtin': True}) + symbol_id = ":/symbols/" + symbol_file + symbols.append({"symbol_id": symbol_id, "filename": filename, "theme": theme, "builtin": True}) self._symbols_path[symbol_id] = os.path.join(root, filename) directory = self.symbols_path() if directory: for root, _, files in os.walk(directory): for filename in files: - if filename.startswith('.'): + if filename.startswith("."): continue - symbol_file = posixpath.normpath(os.path.relpath(os.path.join(root, filename), directory)).replace('\\', '/') - theme = posixpath.dirname(symbol_file).replace('/', '-').capitalize() + symbol_file = posixpath.normpath(os.path.relpath(os.path.join(root, filename), directory)).replace( + "\\", "/" + ) + theme = posixpath.dirname(symbol_file).replace("/", "-").capitalize() if not theme: theme = "Custom symbols" - symbols.append({'symbol_id': symbol_file, - 'filename': filename, - 'builtin': False, - 'theme': theme}) + symbols.append({"symbol_id": symbol_file, "filename": filename, "builtin": False, "theme": theme}) self._symbols_path[symbol_file] = os.path.join(root, filename) symbols.sort(key=lambda x: x["filename"]) diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py index ba103da2..687f2b29 100644 --- a/gns3server/controller/topology.py +++ b/gns3server/controller/topology.py @@ -35,6 +35,7 @@ from gns3server.schemas.topology import Topology from gns3server.schemas.dynamips_nodes import DynamipsCreate import logging + log = logging.getLogger(__name__) @@ -81,15 +82,10 @@ def project_to_topology(project): "show_interface_labels": project.show_interface_labels, "variables": project.variables, "supplier": project.supplier, - "topology": { - "nodes": [], - "links": [], - "computes": [], - "drawings": [] - }, + "topology": {"nodes": [], "links": [], "computes": [], "drawings": []}, "type": "topology", "revision": GNS3_FILE_FORMAT_REVISION, - "version": __version__ + "version": __version__, } for node in project.nodes.values(): @@ -110,7 +106,10 @@ def project_to_topology(project): for compute in project.computes: if hasattr(compute, "__json__"): compute = compute.__json__(topology_dump=True) - if compute["compute_id"] not in ("vm", "local", ): + if compute["compute_id"] not in ( + "vm", + "local", + ): data["topology"]["computes"].append(compute) elif isinstance(compute, dict): data["topology"]["computes"].append(compute) @@ -130,7 +129,11 @@ def load_topology(path): raise ControllerError(f"Could not load topology {path}: {str(e)}") if topo.get("revision", 0) > GNS3_FILE_FORMAT_REVISION: - raise ControllerError("This project was created with more recent version of GNS3 (file revision: {}). Please upgrade GNS3 to version {} or later".format(topo["revision"], topo["version"])) + raise ControllerError( + "This project was created with more recent version of GNS3 (file revision: {}). Please upgrade GNS3 to version {} or later".format( + topo["revision"], topo["version"] + ) + ) changed = False if "revision" not in topo or topo["revision"] < GNS3_FILE_FORMAT_REVISION: @@ -317,12 +320,7 @@ def _convert_1_3_later(topo, topo_path): "auto_start": topo.get("auto_start", False), "name": topo["name"], "project_id": topo.get("project_id"), - "topology": { - "links": [], - "drawings": [], - "computes": [], - "nodes": [] - } + "topology": {"links": [], "drawings": [], "computes": [], "nodes": []}, } if new_topo["project_id"] is None: new_topo["project_id"] = str(uuid.uuid4()) # Could arrive for topologues with drawing only @@ -336,7 +334,7 @@ def _convert_1_3_later(topo, topo_path): compute = { "host": server.get("host", "localhost"), "port": server.get("port", 3080), - "protocol": server.get("protocol", "http") + "protocol": server.get("protocol", "http"), } if server["local"]: compute["compute_id"] = "local" @@ -407,27 +405,40 @@ def _convert_1_3_later(topo, topo_path): node["symbol"] = ":/symbols/hub.svg" node["properties"]["ports_mapping"] = [] for port in old_node.get("ports", []): - node["properties"]["ports_mapping"].append({ - "name": "Ethernet{}".format(port["port_number"] - 1), - "port_number": port["port_number"] - 1 - }) + node["properties"]["ports_mapping"].append( + {"name": "Ethernet{}".format(port["port_number"] - 1), "port_number": port["port_number"] - 1} + ) elif old_node["type"] == "EthernetSwitch": node["node_type"] = "ethernet_switch" node["symbol"] = ":/symbols/ethernet_switch.svg" node["console_type"] = None node["properties"]["ports_mapping"] = [] for port in old_node.get("ports", []): - node["properties"]["ports_mapping"].append({ - "name": "Ethernet{}".format(port["port_number"] - 1), - "port_number": port["port_number"] - 1, - "type": port["type"], - "vlan": port["vlan"] - }) + node["properties"]["ports_mapping"].append( + { + "name": "Ethernet{}".format(port["port_number"] - 1), + "port_number": port["port_number"] - 1, + "type": port["type"], + "vlan": port["vlan"], + } + ) elif old_node["type"] == "FrameRelaySwitch": node["node_type"] = "frame_relay_switch" node["symbol"] = ":/symbols/frame_relay_switch.svg" node["console_type"] = None - elif old_node["type"].upper() in ["C1700", "C2600", "C2691", "C3600", "C3620", "C3640", "C3660", "C3725", "C3745", "C7200", "EtherSwitchRouter"]: + elif old_node["type"].upper() in [ + "C1700", + "C2600", + "C2691", + "C3600", + "C3620", + "C3640", + "C3660", + "C3725", + "C3745", + "C7200", + "EtherSwitchRouter", + ]: if node["symbol"] is None: node["symbol"] = ":/symbols/router.svg" node["node_type"] = "dynamips" @@ -485,23 +496,20 @@ def _convert_1_3_later(topo, topo_path): source_node = { "adapter_number": ports[old_link["source_port_id"]].get("adapter_number", 0), "port_number": ports[old_link["source_port_id"]].get("port_number", 0), - "node_id": node_id_to_node_uuid[old_link["source_node_id"]] + "node_id": node_id_to_node_uuid[old_link["source_node_id"]], } nodes.append(source_node) destination_node = { "adapter_number": ports[old_link["destination_port_id"]].get("adapter_number", 0), "port_number": ports[old_link["destination_port_id"]].get("port_number", 0), - "node_id": node_id_to_node_uuid[old_link["destination_node_id"]] + "node_id": node_id_to_node_uuid[old_link["destination_node_id"]], } nodes.append(destination_node) except KeyError: continue - link = { - "link_id": str(uuid.uuid4()), - "nodes": nodes - } + link = {"link_id": str(uuid.uuid4()), "nodes": nodes} new_topo["topology"]["links"].append(link) # Ellipse @@ -514,7 +522,7 @@ def _convert_1_3_later(topo, topo_path): rx=int(ellipse["width"] / 2), ry=int(ellipse["height"] / 2), fill=ellipse.get("color", "#ffffff"), - border_style=_convert_border_style(ellipse) + border_style=_convert_border_style(ellipse), ) new_ellipse = { "drawing_id": str(uuid.uuid4()), @@ -522,7 +530,7 @@ def _convert_1_3_later(topo, topo_path): "y": int(ellipse["y"]), "z": int(ellipse.get("z", 0)), "rotation": int(ellipse.get("rotation", 0)), - "svg": svg + "svg": svg, } new_topo["topology"]["drawings"].append(new_ellipse) @@ -543,12 +551,14 @@ def _convert_1_3_later(topo, topo_path): height=int(font_info[1]) * 2, width=int(font_info[1]) * len(note["text"]), fill="#" + note.get("color", "#00000000")[-6:], - opacity=round(1.0 / 255 * int(note.get("color", "#ffffffff")[:3][-2:], base=16), 2), # Extract the alpha channel from the hexa version + opacity=round( + 1.0 / 255 * int(note.get("color", "#ffffffff")[:3][-2:], base=16), 2 + ), # Extract the alpha channel from the hexa version family=font_info[0], size=int(font_info[1]), weight=weight, style=style, - text=html.escape(note["text"]) + text=html.escape(note["text"]), ) new_note = { "drawing_id": str(uuid.uuid4()), @@ -556,7 +566,7 @@ def _convert_1_3_later(topo, topo_path): "y": int(note["y"]), "z": int(note.get("z", 0)), "rotation": int(note.get("rotation", 0)), - "svg": svg + "svg": svg, } new_topo["topology"]["drawings"].append(new_note) @@ -576,7 +586,7 @@ def _convert_1_3_later(topo, topo_path): "y": int(image["y"]), "z": int(image.get("z", 0)), "rotation": int(image.get("rotation", 0)), - "svg": os.path.basename(img_path) + "svg": os.path.basename(img_path), } new_topo["topology"]["drawings"].append(new_image) @@ -586,7 +596,7 @@ def _convert_1_3_later(topo, topo_path): height=int(rectangle["height"]), width=int(rectangle["width"]), fill=rectangle.get("color", "#ffffff"), - border_style=_convert_border_style(rectangle) + border_style=_convert_border_style(rectangle), ) new_rectangle = { "drawing_id": str(uuid.uuid4()), @@ -594,7 +604,7 @@ def _convert_1_3_later(topo, topo_path): "y": int(rectangle["y"]), "z": int(rectangle.get("z", 0)), "rotation": int(rectangle.get("rotation", 0)), - "svg": svg + "svg": svg, } new_topo["topology"]["drawings"].append(new_rectangle) @@ -608,12 +618,7 @@ def _convert_1_3_later(topo, topo_path): def _convert_border_style(element): - QT_DASH_TO_SVG = { - 2: "25, 25", - 3: "5, 25", - 4: "5, 25, 25", - 5: "25, 25, 5, 25, 5" - } + QT_DASH_TO_SVG = {2: "25, 25", 3: "5, 25", 4: "5, 25, 25", 5: "25, 25, 5, 25, 5"} border_style = int(element.get("border_style", 0)) style = "" if border_style == 1: # No border @@ -623,8 +628,7 @@ def _convert_border_style(element): else: style += f'stroke-dasharray="{QT_DASH_TO_SVG[border_style]}" ' style += 'stroke="{stroke}" stroke-width="{stroke_width}"'.format( - stroke=element.get("border_color", "#000000"), - stroke_width=element.get("border_width", 2) + stroke=element.get("border_color", "#000000"), stroke_width=element.get("border_width", 2) ) return style @@ -639,7 +643,7 @@ def _convert_label(label): "rotation": 0, "style": style, "x": int(label["x"]), - "y": int(label["y"]) + "y": int(label["y"]), } @@ -677,14 +681,14 @@ def _create_cloud(node, old_node, icon): "type": port_type, "lport": int(lport), "rhost": rhost, - "rport": int(rport) + "rport": int(rport), } else: port = { "interface": old_port["name"].split(":")[1], "name": old_port["name"].split(":")[1], "port_number": len(ports) + 1, - "type": port_type + "type": port_type, } keep_ports.append(old_port) ports.append(port) @@ -716,10 +720,14 @@ def _convert_snapshots(topo_dir): if is_gns3_topo: snapshot_arc = os.path.join(new_snapshots_dir, snapshot + ".gns3project") - with zipfile.ZipFile(snapshot_arc, 'w', allowZip64=True) as myzip: + with zipfile.ZipFile(snapshot_arc, "w", allowZip64=True) as myzip: for root, dirs, files in os.walk(snapshot_dir): for file in files: - myzip.write(os.path.join(root, file), os.path.relpath(os.path.join(root, file), snapshot_dir), compress_type=zipfile.ZIP_DEFLATED) + myzip.write( + os.path.join(root, file), + os.path.relpath(os.path.join(root, file), snapshot_dir), + compress_type=zipfile.ZIP_DEFLATED, + ) shutil.rmtree(old_snapshots_dir) @@ -735,16 +743,7 @@ def _convert_qemu_node(node, old_node): node["console_type"] = None node["node_type"] = "nat" del old_node["properties"] - node["properties"] = { - "ports": [ - { - "interface": "eth1", - "name": "nat0", - "port_number": 0, - "type": "ethernet" - } - ] - } + node["properties"] = {"ports": [{"interface": "eth1", "name": "nat0", "port_number": 0, "type": "ethernet"}]} if node["symbol"] is None: node["symbol"] = ":/symbols/cloud.svg" return node diff --git a/gns3server/controller/udp_link.py b/gns3server/controller/udp_link.py index eac87bef..4051b3a0 100644 --- a/gns3server/controller/udp_link.py +++ b/gns3server/controller/udp_link.py @@ -21,7 +21,6 @@ from .link import Link class UDPLink(Link): - def __init__(self, project, link_id=None): super().__init__(project, link_id=link_id) self._created = False @@ -67,26 +66,32 @@ class UDPLink(Link): node2_filters = self.get_active_filters() # Create the tunnel on both side - self._link_data.append({ - "lport": self._node1_port, - "rhost": node2_host, - "rport": self._node2_port, - "type": "nio_udp", - "filters": node1_filters, - "suspend": self._suspended - }) + self._link_data.append( + { + "lport": self._node1_port, + "rhost": node2_host, + "rport": self._node2_port, + "type": "nio_udp", + "filters": node1_filters, + "suspend": self._suspended, + } + ) await node1.post(f"/adapters/{adapter_number1}/ports/{port_number1}/nio", data=self._link_data[0], timeout=120) - self._link_data.append({ - "lport": self._node2_port, - "rhost": node1_host, - "rport": self._node1_port, - "type": "nio_udp", - "filters": node2_filters, - "suspend": self._suspended - }) + self._link_data.append( + { + "lport": self._node2_port, + "rhost": node1_host, + "rport": self._node1_port, + "type": "nio_udp", + "filters": node2_filters, + "suspend": self._suspended, + } + ) try: - await node2.post(f"/adapters/{adapter_number2}/ports/{port_number2}/nio", data=self._link_data[1], timeout=120) + await node2.post( + f"/adapters/{adapter_number2}/ports/{port_number2}/nio", data=self._link_data[1], timeout=120 + ) except Exception as e: # We clean the first NIO await node1.delete(f"/adapters/{adapter_number1}/ports/{port_number1}/nio", timeout=120) @@ -116,14 +121,18 @@ class UDPLink(Link): self._link_data[0]["filters"] = node1_filters self._link_data[0]["suspend"] = self._suspended if node1.node_type not in ("ethernet_switch", "ethernet_hub"): - await node1.put(f"/adapters/{adapter_number1}/ports/{port_number1}/nio", data=self._link_data[0], timeout=120) + await node1.put( + f"/adapters/{adapter_number1}/ports/{port_number1}/nio", data=self._link_data[0], timeout=120 + ) adapter_number2 = self._nodes[1]["adapter_number"] port_number2 = self._nodes[1]["port_number"] self._link_data[1]["filters"] = node2_filters self._link_data[1]["suspend"] = self._suspended if node2.node_type not in ("ethernet_switch", "ethernet_hub"): - await node2.put(f"/adapters/{adapter_number2}/ports/{port_number2}/nio", data=self._link_data[1], timeout=221) + await node2.put( + f"/adapters/{adapter_number2}/ports/{port_number2}/nio", data=self._link_data[1], timeout=221 + ) async def delete(self): """ @@ -172,9 +181,13 @@ class UDPLink(Link): if not capture_file_name: capture_file_name = self.default_capture_file_name() self._capture_node = self._choose_capture_side() - data = {"capture_file_name": capture_file_name, - "data_link_type": data_link_type} - await self._capture_node["node"].post("/adapters/{adapter_number}/ports/{port_number}/start_capture".format(adapter_number=self._capture_node["adapter_number"], port_number=self._capture_node["port_number"]), data=data) + data = {"capture_file_name": capture_file_name, "data_link_type": data_link_type} + await self._capture_node["node"].post( + "/adapters/{adapter_number}/ports/{port_number}/start_capture".format( + adapter_number=self._capture_node["adapter_number"], port_number=self._capture_node["port_number"] + ), + data=data, + ) await super().start_capture(data_link_type=data_link_type, capture_file_name=capture_file_name) async def stop_capture(self): @@ -182,7 +195,11 @@ class UDPLink(Link): Stop capture on a link """ if self._capture_node: - await self._capture_node["node"].post("/adapters/{adapter_number}/ports/{port_number}/stop_capture".format(adapter_number=self._capture_node["adapter_number"], port_number=self._capture_node["port_number"])) + await self._capture_node["node"].post( + "/adapters/{adapter_number}/ports/{port_number}/stop_capture".format( + adapter_number=self._capture_node["adapter_number"], port_number=self._capture_node["port_number"] + ) + ) self._capture_node = None await super().stop_capture() @@ -199,7 +216,11 @@ class UDPLink(Link): ALWAYS_RUNNING_NODES_TYPE = ("cloud", "nat", "ethernet_switch", "ethernet_hub") for node in self._nodes: - if node["node"].compute.id == "local" and node["node"].node_type in ALWAYS_RUNNING_NODES_TYPE and node["node"].status == "started": + if ( + node["node"].compute.id == "local" + and node["node"].node_type in ALWAYS_RUNNING_NODES_TYPE + and node["node"].status == "started" + ): return node for node in self._nodes: diff --git a/gns3server/core/tasks.py b/gns3server/core/tasks.py index 12fd9d03..4623c487 100644 --- a/gns3server/core/tasks.py +++ b/gns3server/core/tasks.py @@ -28,11 +28,11 @@ from gns3server.utils.http_client import HTTPClient from gns3server.db.tasks import connect_to_db, get_computes import logging + log = logging.getLogger(__name__) def create_startup_handler(app: FastAPI) -> Callable: - async def start_app() -> None: loop = asyncio.get_event_loop() logger = logging.getLogger("asyncio") @@ -65,6 +65,7 @@ def create_startup_handler(app: FastAPI) -> Callable: # without md5sum already computed we start the # computing with server start from gns3server.compute.qemu import Qemu + asyncio.ensure_future(Qemu.instance().list_images()) for module in MODULES: @@ -76,7 +77,6 @@ def create_startup_handler(app: FastAPI) -> Callable: def create_shutdown_handler(app: FastAPI) -> Callable: - async def shutdown_handler() -> None: await HTTPClient.close_session() await Controller.instance().stop() diff --git a/gns3server/crash_report.py b/gns3server/crash_report.py index 7afb33ad..012806e6 100644 --- a/gns3server/crash_report.py +++ b/gns3server/crash_report.py @@ -17,6 +17,7 @@ try: import sentry_sdk from sentry_sdk.integrations.logging import LoggingIntegration + SENTRY_SDK_AVAILABLE = True except ImportError: # Sentry SDK is not installed with deb package in order to simplify packaging @@ -34,6 +35,7 @@ from .config import Config from .utils.get_resource import get_resource import logging + log = logging.getLogger(__name__) @@ -63,10 +65,10 @@ class CrashReport: def __init__(self): # We don't want sentry making noise if an error is caught when you don't have internet - sentry_errors = logging.getLogger('sentry.errors') + sentry_errors = logging.getLogger("sentry.errors") sentry_errors.disabled = True - sentry_uncaught = logging.getLogger('sentry.errors.uncaught') + sentry_uncaught = logging.getLogger("sentry.errors.uncaught") sentry_uncaught.disabled = True if SENTRY_SDK_AVAILABLE: @@ -81,11 +83,13 @@ class CrashReport: # Don't send log records as events. sentry_logging = LoggingIntegration(level=logging.INFO, event_level=None) - sentry_sdk.init(dsn=CrashReport.DSN, - release=__version__, - ca_certs=cacert, - default_integrations=False, - integrations=[sentry_logging]) + sentry_sdk.init( + dsn=CrashReport.DSN, + release=__version__, + ca_certs=cacert, + default_integrations=False, + integrations=[sentry_logging], + ) tags = { "os:name": platform.system(), @@ -93,7 +97,6 @@ class CrashReport: "os:win_32": " ".join(platform.win32_ver()), "os:mac": f"{platform.mac_ver()[0]} {platform.mac_ver()[2]}", "os:linux": " ".join(distro.linux_distribution()), - } with sentry_sdk.configure_scope() as scope: @@ -101,12 +104,10 @@ class CrashReport: scope.set_tag(key, value) extra_context = { - "python:version": "{}.{}.{}".format(sys.version_info[0], - sys.version_info[1], - sys.version_info[2]), + "python:version": "{}.{}.{}".format(sys.version_info[0], sys.version_info[1], sys.version_info[2]), "python:bit": struct.calcsize("P") * 8, "python:encoding": sys.getdefaultencoding(), - "python:frozen": "{}".format(hasattr(sys, "frozen")) + "python:frozen": "{}".format(hasattr(sys, "frozen")), } if sys.platform.startswith("linux") and not hasattr(sys, "frozen"): @@ -137,7 +138,9 @@ class CrashReport: if not SENTRY_SDK_AVAILABLE: return - if not hasattr(sys, "frozen") and os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".git")): + if not hasattr(sys, "frozen") and os.path.exists( + os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".git") + ): log.warning(".git directory detected, crash reporting is turned off for developers.") return diff --git a/gns3server/db/models/__init__.py b/gns3server/db/models/__init__.py index 7346a9c9..71ead9d8 100644 --- a/gns3server/db/models/__init__.py +++ b/gns3server/db/models/__init__.py @@ -29,5 +29,5 @@ from .templates import ( QemuTemplate, VirtualBoxTemplate, VMwareTemplate, - VPCSTemplate + VPCSTemplate, ) diff --git a/gns3server/db/models/base.py b/gns3server/db/models/base.py index 6459e122..17c68f76 100644 --- a/gns3server/db/models/base.py +++ b/gns3server/db/models/base.py @@ -26,11 +26,9 @@ from sqlalchemy.ext.declarative import as_declarative @as_declarative() class Base: - def asdict(self): - return {c.key: getattr(self, c.key) - for c in inspect(self).mapper.column_attrs} + return {c.key: getattr(self, c.key) for c in inspect(self).mapper.column_attrs} def asjson(self): @@ -47,7 +45,7 @@ class GUID(TypeDecorator): impl = CHAR def load_dialect_impl(self, dialect): - if dialect.name == 'postgresql': + if dialect.name == "postgresql": return dialect.type_descriptor(UUID()) else: return dialect.type_descriptor(CHAR(32)) @@ -55,7 +53,7 @@ class GUID(TypeDecorator): def process_bind_param(self, value, dialect): if value is None: return value - elif dialect.name == 'postgresql': + elif dialect.name == "postgresql": return str(value) else: if not isinstance(value, uuid.UUID): @@ -78,9 +76,7 @@ class BaseTable(Base): __abstract__ = True created_at = Column(DateTime, default=func.current_timestamp()) - updated_at = Column(DateTime, - default=func.current_timestamp(), - onupdate=func.current_timestamp()) + updated_at = Column(DateTime, default=func.current_timestamp(), onupdate=func.current_timestamp()) def generate_uuid(): diff --git a/gns3server/db/models/templates.py b/gns3server/db/models/templates.py index 2e2580f0..e39e0695 100644 --- a/gns3server/db/models/templates.py +++ b/gns3server/db/models/templates.py @@ -52,10 +52,7 @@ class CloudTemplate(Template): remote_console_type = Column(String) remote_console_http_path = Column(String) - __mapper_args__ = { - "polymorphic_identity": "cloud", - "polymorphic_load": "selectin" - } + __mapper_args__ = {"polymorphic_identity": "cloud", "polymorphic_load": "selectin"} class DockerTemplate(Template): @@ -79,10 +76,7 @@ class DockerTemplate(Template): cpus = Column(Integer) custom_adapters = Column(PickleType) - __mapper_args__ = { - "polymorphic_identity": "docker", - "polymorphic_load": "selectin" - } + __mapper_args__ = {"polymorphic_identity": "docker", "polymorphic_load": "selectin"} class DynamipsTemplate(Template): @@ -125,10 +119,7 @@ class DynamipsTemplate(Template): wic1 = Column(String) wic2 = Column(String) - __mapper_args__ = { - "polymorphic_identity": "dynamips", - "polymorphic_load": "selectin" - } + __mapper_args__ = {"polymorphic_identity": "dynamips", "polymorphic_load": "selectin"} class EthernetHubTemplate(Template): @@ -138,10 +129,7 @@ class EthernetHubTemplate(Template): template_id = Column(GUID, ForeignKey("templates.template_id"), primary_key=True) ports_mapping = Column(PickleType) - __mapper_args__ = { - "polymorphic_identity": "ethernet_hub", - "polymorphic_load": "selectin" - } + __mapper_args__ = {"polymorphic_identity": "ethernet_hub", "polymorphic_load": "selectin"} class EthernetSwitchTemplate(Template): @@ -152,10 +140,7 @@ class EthernetSwitchTemplate(Template): ports_mapping = Column(PickleType) console_type = Column(String) - __mapper_args__ = { - "polymorphic_identity": "ethernet_switch", - "polymorphic_load": "selectin" - } + __mapper_args__ = {"polymorphic_identity": "ethernet_switch", "polymorphic_load": "selectin"} class IOUTemplate(Template): @@ -175,10 +160,7 @@ class IOUTemplate(Template): console_type = Column(String) console_auto_start = Column(Boolean) - __mapper_args__ = { - "polymorphic_identity": "iou", - "polymorphic_load": "selectin" - } + __mapper_args__ = {"polymorphic_identity": "iou", "polymorphic_load": "selectin"} class QemuTemplate(Template): @@ -224,10 +206,7 @@ class QemuTemplate(Template): options = Column(String) custom_adapters = Column(PickleType) - __mapper_args__ = { - "polymorphic_identity": "qemu", - "polymorphic_load": "selectin" - } + __mapper_args__ = {"polymorphic_identity": "qemu", "polymorphic_load": "selectin"} class VirtualBoxTemplate(Template): @@ -250,10 +229,7 @@ class VirtualBoxTemplate(Template): console_auto_start = Column(Boolean) custom_adapters = Column(PickleType) - __mapper_args__ = { - "polymorphic_identity": "virtualbox", - "polymorphic_load": "selectin" - } + __mapper_args__ = {"polymorphic_identity": "virtualbox", "polymorphic_load": "selectin"} class VMwareTemplate(Template): @@ -275,10 +251,7 @@ class VMwareTemplate(Template): console_auto_start = Column(Boolean) custom_adapters = Column(PickleType) - __mapper_args__ = { - "polymorphic_identity": "vmware", - "polymorphic_load": "selectin" - } + __mapper_args__ = {"polymorphic_identity": "vmware", "polymorphic_load": "selectin"} class VPCSTemplate(Template): @@ -290,7 +263,4 @@ class VPCSTemplate(Template): console_type = Column(String) console_auto_start = Column(Boolean, default=False) - __mapper_args__ = { - "polymorphic_identity": "vpcs", - "polymorphic_load": "selectin" - } + __mapper_args__ = {"polymorphic_identity": "vpcs", "polymorphic_load": "selectin"} diff --git a/gns3server/db/repositories/base.py b/gns3server/db/repositories/base.py index ab7c5ca3..86192f81 100644 --- a/gns3server/db/repositories/base.py +++ b/gns3server/db/repositories/base.py @@ -19,7 +19,6 @@ from sqlalchemy.ext.asyncio import AsyncSession class BaseRepository: - def __init__(self, db_session: AsyncSession) -> None: self._db_session = db_session diff --git a/gns3server/db/repositories/computes.py b/gns3server/db/repositories/computes.py index c7829325..445621da 100644 --- a/gns3server/db/repositories/computes.py +++ b/gns3server/db/repositories/computes.py @@ -28,7 +28,6 @@ from gns3server import schemas class ComputesRepository(BaseRepository): - def __init__(self, db_session: AsyncSession) -> None: super().__init__(db_session) @@ -65,7 +64,7 @@ class ComputesRepository(BaseRepository): host=compute_create.host, port=compute_create.port, user=compute_create.user, - password=password + password=password, ) self._db_session.add(db_compute) await self._db_session.commit() @@ -80,9 +79,7 @@ class ComputesRepository(BaseRepository): if password: update_values["password"] = password.get_secret_value() - query = update(models.Compute) \ - .where(models.Compute.compute_id == compute_id) \ - .values(update_values) + query = update(models.Compute).where(models.Compute.compute_id == compute_id).values(update_values) await self._db_session.execute(query) await self._db_session.commit() diff --git a/gns3server/db/repositories/templates.py b/gns3server/db/repositories/templates.py index 8804d931..e5be7971 100644 --- a/gns3server/db/repositories/templates.py +++ b/gns3server/db/repositories/templates.py @@ -36,12 +36,11 @@ TEMPLATE_TYPE_TO_MODEL = { "qemu": models.QemuTemplate, "virtualbox": models.VirtualBoxTemplate, "vmware": models.VMwareTemplate, - "vpcs": models.VPCSTemplate + "vpcs": models.VPCSTemplate, } class TemplatesRepository(BaseRepository): - def __init__(self, db_session: AsyncSession) -> None: super().__init__(db_session) @@ -67,16 +66,11 @@ class TemplatesRepository(BaseRepository): await self._db_session.refresh(db_template) return db_template - async def update_template( - self, - template_id: UUID, - template_update: schemas.TemplateUpdate) -> schemas.Template: + async def update_template(self, template_id: UUID, template_update: schemas.TemplateUpdate) -> schemas.Template: update_values = template_update.dict(exclude_unset=True) - query = update(models.Template) \ - .where(models.Template.template_id == template_id) \ - .values(update_values) + query = update(models.Template).where(models.Template.template_id == template_id).values(update_values) await self._db_session.execute(query) await self._db_session.commit() diff --git a/gns3server/db/repositories/users.py b/gns3server/db/repositories/users.py index 65d5e4a2..2a5ce4aa 100644 --- a/gns3server/db/repositories/users.py +++ b/gns3server/db/repositories/users.py @@ -28,7 +28,6 @@ from gns3server.services import auth_service class UsersRepository(BaseRepository): - def __init__(self, db_session: AsyncSession) -> None: super().__init__(db_session) @@ -62,10 +61,7 @@ class UsersRepository(BaseRepository): hashed_password = self._auth_service.hash_password(user.password) db_user = models.User( - username=user.username, - email=user.email, - full_name=user.full_name, - hashed_password=hashed_password + username=user.username, email=user.email, full_name=user.full_name, hashed_password=hashed_password ) self._db_session.add(db_user) await self._db_session.commit() @@ -79,9 +75,7 @@ class UsersRepository(BaseRepository): if password: update_values["hashed_password"] = self._auth_service.hash_password(password=password) - query = update(models.User) \ - .where(models.User.user_id == user_id) \ - .values(update_values) + query = update(models.User).where(models.User.user_id == user_id).values(update_values) await self._db_session.execute(query) await self._db_session.commit() diff --git a/gns3server/db/tasks.py b/gns3server/db/tasks.py index 2f4a024f..fb53ed4f 100644 --- a/gns3server/db/tasks.py +++ b/gns3server/db/tasks.py @@ -31,6 +31,7 @@ from .models import Base from gns3server.config import Config import logging + log = logging.getLogger(__name__) @@ -56,9 +57,8 @@ async def get_computes(app: FastAPI) -> List[dict]: for db_compute in db_computes: try: compute = jsonable_encoder( - schemas.Compute.from_orm(db_compute), - exclude_unset=True, - exclude={"created_at", "updated_at"}) + schemas.Compute.from_orm(db_compute), exclude_unset=True, exclude={"created_at", "updated_at"} + ) except ValidationError as e: log.error(f"Could not load compute '{db_compute.compute_id}' from database: {e}") continue diff --git a/gns3server/logger.py b/gns3server/logger.py index 9f45b94e..592d9ada 100644 --- a/gns3server/logger.py +++ b/gns3server/logger.py @@ -28,11 +28,11 @@ from logging.handlers import RotatingFileHandler class ColouredFormatter(logging.Formatter): - RESET = '\x1B[0m' - RED = '\x1B[31m' - YELLOW = '\x1B[33m' - GREEN = '\x1B[32m' - PINK = '\x1b[35m' + RESET = "\x1B[0m" + RED = "\x1B[31m" + YELLOW = "\x1B[33m" + GREEN = "\x1B[32m" + PINK = "\x1b[35m" def format(self, record, colour=False): @@ -61,13 +61,12 @@ class ColouredFormatter(logging.Formatter): if record.name.startswith("uvicorn"): message = message.replace(f"{record.name}:{record.lineno}", "uvicorn") - message = f'{colour}{message}{self.RESET}' + message = f"{colour}{message}{self.RESET}" return message class ColouredStreamHandler(logging.StreamHandler): - def format(self, record, colour=False): if not isinstance(self.formatter, ColouredFormatter): @@ -91,7 +90,6 @@ class ColouredStreamHandler(logging.StreamHandler): class WinStreamHandler(logging.StreamHandler): - def emit(self, record): if sys.stdin.encoding != "utf-8": @@ -111,6 +109,7 @@ class LogFilter: """ This filter some noise from the logs """ + def filter(record): if "/settings" in record.msg and "200" in record.msg: return 0 @@ -136,9 +135,9 @@ class CompressedRotatingFileHandler(RotatingFileHandler): dfn = self.baseFilename + ".1.gz" if os.path.exists(dfn): os.remove(dfn) - with open(self.baseFilename, 'rb') as f_in, gzip.open(dfn, 'wb') as f_out: + with open(self.baseFilename, "rb") as f_in, gzip.open(dfn, "wb") as f_out: shutil.copyfileobj(f_in, f_out) - self.mode = 'w' + self.mode = "w" self.stream = self._open() @@ -148,13 +147,19 @@ def init_logger(level, logfile=None, max_bytes=10000000, backup_count=10, compre stream_handler = CompressedRotatingFileHandler(logfile, maxBytes=max_bytes, backupCount=backup_count) else: stream_handler = RotatingFileHandler(logfile, maxBytes=max_bytes, backupCount=backup_count) - stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {filename}:{lineno} {message}", "%Y-%m-%d %H:%M:%S", "{") + stream_handler.formatter = ColouredFormatter( + "{asctime} {levelname} {filename}:{lineno} {message}", "%Y-%m-%d %H:%M:%S", "{" + ) elif sys.platform.startswith("win"): stream_handler = WinStreamHandler(sys.stdout) - stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {filename}:{lineno} {message}", "%Y-%m-%d %H:%M:%S", "{") + stream_handler.formatter = ColouredFormatter( + "{asctime} {levelname} {filename}:{lineno} {message}", "%Y-%m-%d %H:%M:%S", "{" + ) else: stream_handler = ColouredStreamHandler(sys.stdout) - stream_handler.formatter = ColouredFormatter("{asctime} {levelname} {name}:{lineno}#RESET# {message}", "%Y-%m-%d %H:%M:%S", "{") + stream_handler.formatter = ColouredFormatter( + "{asctime} {levelname} {name}:{lineno}#RESET# {message}", "%Y-%m-%d %H:%M:%S", "{" + ) if quiet: stream_handler.addFilter(logging.Filter(name="user_facing")) logging.getLogger("user_facing").propagate = False diff --git a/gns3server/main.py b/gns3server/main.py index 464be247..9f4e8a20 100644 --- a/gns3server/main.py +++ b/gns3server/main.py @@ -35,12 +35,13 @@ import types # To avoid strange bug later we switch the event loop before any other operation if sys.platform.startswith("win"): import asyncio + # use the Proactor event loop on Windows loop = asyncio.ProactorEventLoop() asyncio.set_event_loop(loop) if sys.platform.startswith("win"): - sys.modules['termios'] = types.ModuleType('termios') + sys.modules["termios"] = types.ModuleType("termios") def daemonize(): @@ -80,8 +81,9 @@ def main(): if "--daemon" in sys.argv: daemonize() from gns3server.server import Server + Server().run() -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/gns3server/schemas/__init__.py b/gns3server/schemas/__init__.py index c68f942a..022d428e 100644 --- a/gns3server/schemas/__init__.py +++ b/gns3server/schemas/__init__.py @@ -61,5 +61,5 @@ from .dynamips_templates import ( C3600DynamipsTemplate, C3725DynamipsTemplate, C3745DynamipsTemplate, - C7200DynamipsTemplate + C7200DynamipsTemplate, ) diff --git a/gns3server/schemas/cloud_nodes.py b/gns3server/schemas/cloud_nodes.py index 22b7402e..7ef0fe02 100644 --- a/gns3server/schemas/cloud_nodes.py +++ b/gns3server/schemas/cloud_nodes.py @@ -107,7 +107,9 @@ class CloudBase(BaseModel): remote_console_port: Optional[int] = Field(None, gt=0, le=65535, description="Console TCP port") remote_console_type: Optional[CloudConsoleType] = Field(None, description="Console type") remote_console_http_path: Optional[str] = Field(None, description="Path of the remote web interface") - ports_mapping: Optional[List[Union[EthernetPort, TAPPort, UDPPort]]] = Field(None, description="List of port mappings") + ports_mapping: Optional[List[Union[EthernetPort, TAPPort, UDPPort]]] = Field( + None, description="List of port mappings" + ) interfaces: Optional[List[HostInterface]] = Field(None, description="List of interfaces") diff --git a/gns3server/schemas/computes.py b/gns3server/schemas/computes.py index ab28960e..46972810 100644 --- a/gns3server/schemas/computes.py +++ b/gns3server/schemas/computes.py @@ -54,13 +54,7 @@ class ComputeCreate(ComputeBase): class Config: schema_extra = { - "example": { - "name": "My compute", - "host": "127.0.0.1", - "port": 3080, - "user": "user", - "password": "password" - } + "example": {"name": "My compute", "host": "127.0.0.1", "port": 3080, "user": "user", "password": "password"} } @validator("name", always=True) @@ -142,10 +136,4 @@ class AutoIdlePC(BaseModel): ram: int = Field(..., description="Amount of RAM in MB") class Config: - schema_extra = { - "example": { - "platform": "c7200", - "image": "/path/to/c7200_image.bin", - "ram": 256 - } - } + schema_extra = {"example": {"platform": "c7200", "image": "/path/to/c7200_image.bin", "ram": 256}} diff --git a/gns3server/schemas/config.py b/gns3server/schemas/config.py index 7e6787c6..c30dada7 100644 --- a/gns3server/schemas/config.py +++ b/gns3server/schemas/config.py @@ -141,13 +141,13 @@ class ServerSettings(BaseModel): @validator("additional_images_paths", pre=True) def split_additional_images_paths(cls, v): if v: - return v.split(';') + return v.split(";") return list() @validator("allowed_interfaces", pre=True) def split_allowed_interfaces(cls, v): if v: - return v.split(',') + return v.split(",") return list() @validator("console_end_port_range") @@ -188,7 +188,7 @@ class ServerSettings(BaseModel): class ServerConfig(BaseModel): - Server: ServerSettings= ServerSettings() + Server: ServerSettings = ServerSettings() Controller: ControllerSettings = ControllerSettings() VPCS: VPCSSettings = VPCSSettings() Dynamips: DynamipsSettings = DynamipsSettings() diff --git a/gns3server/schemas/docker_nodes.py b/gns3server/schemas/docker_nodes.py index 6d728c93..4f033c85 100644 --- a/gns3server/schemas/docker_nodes.py +++ b/gns3server/schemas/docker_nodes.py @@ -66,7 +66,9 @@ class DockerUpdate(DockerBase): class Docker(DockerBase): - container_id: str = Field(..., min_length=12, max_length=64, regex="^[a-f0-9]+$", description="Docker container ID (read only)") + container_id: str = Field( + ..., min_length=12, max_length=64, regex="^[a-f0-9]+$", description="Docker container ID (read only)" + ) project_id: UUID = Field(..., description="Project ID") node_directory: str = Field(..., description="Path to the node working directory (read only)") status: NodeStatus = Field(..., description="Container status (read only)") diff --git a/gns3server/schemas/docker_templates.py b/gns3server/schemas/docker_templates.py index 3cd91e25..48f16e1a 100644 --- a/gns3server/schemas/docker_templates.py +++ b/gns3server/schemas/docker_templates.py @@ -34,10 +34,19 @@ class DockerTemplate(TemplateBase): environment: Optional[str] = Field("", description="Docker environment variables") console_type: Optional[ConsoleType] = Field("telnet", description="Console type") aux_type: Optional[AuxType] = Field("none", description="Auxiliary console type") - console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started") - console_http_port: Optional[int] = Field(80, gt=0, le=65535, description="Internal port in the container for the HTTP server") - console_http_path: Optional[str] = Field("/", description="Path of the web interface",) - console_resolution: Optional[str] = Field("1024x768", regex="^[0-9]+x[0-9]+$", description="Console resolution for VNC") + console_auto_start: Optional[bool] = Field( + False, description="Automatically start the console when the node has started" + ) + console_http_port: Optional[int] = Field( + 80, gt=0, le=65535, description="Internal port in the container for the HTTP server" + ) + console_http_path: Optional[str] = Field( + "/", + description="Path of the web interface", + ) + console_resolution: Optional[str] = Field( + "1024x768", regex="^[0-9]+x[0-9]+$", description="Console resolution for VNC" + ) extra_hosts: Optional[str] = Field("", description="Docker extra hosts (added to /etc/hosts)") extra_volumes: Optional[List] = Field([], description="Additional directories to make persistent") memory: Optional[int] = Field(0, description="Maximum amount of memory the container can use in MB") diff --git a/gns3server/schemas/dynamips_nodes.py b/gns3server/schemas/dynamips_nodes.py index aa9c0734..1289c013 100644 --- a/gns3server/schemas/dynamips_nodes.py +++ b/gns3server/schemas/dynamips_nodes.py @@ -112,7 +112,7 @@ class DynamipsMidplane(str, Enum): vxr = "vxr" -#TODO: improve schema for Dynamips (match platform specific options, e.g. NPE allowd only for c7200) +# TODO: improve schema for Dynamips (match platform specific options, e.g. NPE allowd only for c7200) class DynamipsBase(BaseModel): """ Common Dynamips node properties. @@ -144,7 +144,9 @@ class DynamipsBase(BaseModel): console_type: Optional[DynamipsConsoleType] = Field(None, description="Console type") aux: Optional[int] = Field(None, gt=0, le=65535, description="Auxiliary console TCP port") aux_type: Optional[DynamipsConsoleType] = Field(None, description="Auxiliary console type") - mac_addr: Optional[str] = Field(None, description="Base MAC address", regex="^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$") + mac_addr: Optional[str] = Field( + None, description="Base MAC address", regex="^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$" + ) system_id: Optional[str] = Field(None, description="System ID") slot0: Optional[DynamipsAdapters] = Field(None, description="Network module slot 0") slot1: Optional[DynamipsAdapters] = Field(None, description="Network module slot 1") diff --git a/gns3server/schemas/dynamips_templates.py b/gns3server/schemas/dynamips_templates.py index 255fd9db..308a90f5 100644 --- a/gns3server/schemas/dynamips_templates.py +++ b/gns3server/schemas/dynamips_templates.py @@ -21,7 +21,7 @@ from .dynamips_nodes import ( DynamipsAdapters, DynamipsWics, DynamipsNPE, - DynamipsMidplane + DynamipsMidplane, ) from pydantic import Field @@ -38,7 +38,9 @@ class DynamipsTemplate(TemplateBase): image: str = Field(..., description="Path to the IOS image") exec_area: Optional[int] = Field(64, description="Exec area value") mmap: Optional[bool] = Field(True, description="MMAP feature") - mac_addr: Optional[str] = Field("", description="Base MAC address", regex="^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$|^$") + mac_addr: Optional[str] = Field( + "", description="Base MAC address", regex="^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$|^$" + ) system_id: Optional[str] = Field("FTX0945W0MY", description="System ID") startup_config: Optional[str] = Field("ios_base_startup-config.txt", description="IOS startup configuration file") private_config: Optional[str] = Field("", description="IOS private configuration file") @@ -49,7 +51,9 @@ class DynamipsTemplate(TemplateBase): disk1: Optional[int] = Field(0, description="Disk1 size in MB") auto_delete_disks: Optional[bool] = Field(False, description="Automatically delete nvram and disk files") console_type: Optional[DynamipsConsoleType] = Field("telnet", description="Console type") - console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started") + console_auto_start: Optional[bool] = Field( + False, description="Automatically start the console when the node has started" + ) aux_type: Optional[DynamipsConsoleType] = Field("none", description="Auxiliary console type") slot0: Optional[DynamipsAdapters] = Field(None, description="Network module slot 0") slot1: Optional[DynamipsAdapters] = Field(None, description="Network module slot 1") diff --git a/gns3server/schemas/ethernet_hub_templates.py b/gns3server/schemas/ethernet_hub_templates.py index 396f5dcc..947d41db 100644 --- a/gns3server/schemas/ethernet_hub_templates.py +++ b/gns3server/schemas/ethernet_hub_templates.py @@ -30,7 +30,7 @@ DEFAULT_PORTS = [ dict(port_number=4, name="Ethernet4"), dict(port_number=5, name="Ethernet5"), dict(port_number=6, name="Ethernet6"), - dict(port_number=7, name="Ethernet7") + dict(port_number=7, name="Ethernet7"), ] diff --git a/gns3server/schemas/ethernet_switch_templates.py b/gns3server/schemas/ethernet_switch_templates.py index e434fc00..51373e31 100644 --- a/gns3server/schemas/ethernet_switch_templates.py +++ b/gns3server/schemas/ethernet_switch_templates.py @@ -30,7 +30,7 @@ DEFAULT_PORTS = [ dict(port_number=4, name="Ethernet4", vlan=1, type="access", ethertype="0x8100"), dict(port_number=5, name="Ethernet5", vlan=1, type="access", ethertype="0x8100"), dict(port_number=6, name="Ethernet6", vlan=1, type="access", ethertype="0x8100"), - dict(port_number=7, name="Ethernet7", vlan=1, type="access", ethertype="0x8100") + dict(port_number=7, name="Ethernet7", vlan=1, type="access", ethertype="0x8100"), ] diff --git a/gns3server/schemas/iou_templates.py b/gns3server/schemas/iou_templates.py index bee305d3..0c7100b9 100644 --- a/gns3server/schemas/iou_templates.py +++ b/gns3server/schemas/iou_templates.py @@ -37,5 +37,6 @@ class IOUTemplate(TemplateBase): private_config: Optional[str] = Field("", description="Private-config of IOU") l1_keepalives: Optional[bool] = Field(False, description="Always keep up Ethernet interface (does not always work)") console_type: Optional[ConsoleType] = Field("telnet", description="Console type") - console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started") - + console_auto_start: Optional[bool] = Field( + False, description="Automatically start the console when the node has started" + ) diff --git a/gns3server/schemas/links.py b/gns3server/schemas/links.py index c5d3b43f..98efba28 100644 --- a/gns3server/schemas/links.py +++ b/gns3server/schemas/links.py @@ -53,7 +53,13 @@ class Link(BaseModel): suspend: Optional[bool] = None filters: Optional[dict] = None capturing: Optional[bool] = Field(None, description="Read only property. True if a capture running on the link") - capture_file_name: Optional[str] = Field(None, description="Read only property. The name of the capture file if a capture is running") - capture_file_path: Optional[str] = Field(None, description="Read only property. The full path of the capture file if a capture is running") - capture_compute_id: Optional[str] = Field(None, description="Read only property. The compute identifier where a capture is running") + capture_file_name: Optional[str] = Field( + None, description="Read only property. The name of the capture file if a capture is running" + ) + capture_file_path: Optional[str] = Field( + None, description="Read only property. The full path of the capture file if a capture is running" + ) + capture_compute_id: Optional[str] = Field( + None, description="Read only property. The compute identifier where a capture is running" + ) link_type: Optional[LinkType] = None diff --git a/gns3server/schemas/nat_nodes.py b/gns3server/schemas/nat_nodes.py index fb3f805d..d2edabf2 100644 --- a/gns3server/schemas/nat_nodes.py +++ b/gns3server/schemas/nat_nodes.py @@ -93,7 +93,9 @@ class NATBase(BaseModel): name: str node_id: Optional[UUID] = None usage: Optional[str] = None - ports_mapping: Optional[List[Union[EthernetPort, TAPPort, UDPPort]]] = Field(None, description="List of port mappings") + ports_mapping: Optional[List[Union[EthernetPort, TAPPort, UDPPort]]] = Field( + None, description="List of port mappings" + ) class NATCreate(NATBase): diff --git a/gns3server/schemas/nios.py b/gns3server/schemas/nios.py index 9e05574f..10514a3b 100644 --- a/gns3server/schemas/nios.py +++ b/gns3server/schemas/nios.py @@ -65,4 +65,3 @@ class TAPNIO(BaseModel): type: TAPNIOType tap_device: str = Field(..., description="TAP device name e.g. tap0") - diff --git a/gns3server/schemas/nodes.py b/gns3server/schemas/nodes.py index 1c45aee6..a80aa05c 100644 --- a/gns3server/schemas/nodes.py +++ b/gns3server/schemas/nodes.py @@ -153,13 +153,20 @@ class Node(BaseModel): node_type: NodeType project_id: Optional[UUID] = None node_id: Optional[UUID] = None - template_id: Optional[UUID] = Field(None, description="Template UUID from which the node has been created. Read only") + template_id: Optional[UUID] = Field( + None, description="Template UUID from which the node has been created. Read only" + ) node_directory: Optional[str] = Field(None, description="Working directory of the node. Read only") command_line: Optional[str] = Field(None, description="Command line use to start the node") console: Optional[int] = Field(None, gt=0, le=65535, description="Console TCP port") - console_host: Optional[str] = Field(None, description="Console host. Warning if the host is 0.0.0.0 or :: (listen on all interfaces) you need to use the same address you use to connect to the controller") + console_host: Optional[str] = Field( + None, + description="Console host. Warning if the host is 0.0.0.0 or :: (listen on all interfaces) you need to use the same address you use to connect to the controller", + ) console_type: Optional[ConsoleType] = None - console_auto_start: Optional[bool] = Field(None, description="Automatically start the console when the node has started") + console_auto_start: Optional[bool] = Field( + None, description="Automatically start the console when the node has started" + ) aux: Optional[int] = Field(None, gt=0, le=65535, description="Auxiliary console TCP port") aux_type: Optional[ConsoleType] properties: Optional[dict] = Field(None, description="Properties specific to an emulator") @@ -172,7 +179,9 @@ class Node(BaseModel): y: Optional[int] = None z: Optional[int] = None locked: Optional[bool] = Field(None, description="Whether the element locked or not") - port_name_format: Optional[str] = Field(None, description="Formatting for port name {0} will be replace by port number") + port_name_format: Optional[str] = Field( + None, description="Formatting for port name {0} will be replace by port number" + ) port_segment_size: Optional[int] = Field(None, description="Size of the port segment") first_port_name: Optional[str] = Field(None, description="Name of the first port") custom_adapters: Optional[List[CustomAdapter]] = None diff --git a/gns3server/schemas/qemu_nodes.py b/gns3server/schemas/qemu_nodes.py index c63f8748..6433f5d9 100644 --- a/gns3server/schemas/qemu_nodes.py +++ b/gns3server/schemas/qemu_nodes.py @@ -45,7 +45,7 @@ class QemuPlatform(str, Enum): s390x = "s390x" sh4 = "sh4" sh4eb = "sh4eb" - sparc = "sparc" + sparc = "sparc" sparc64 = "sparc64" tricore = "tricore" unicore32 = "unicore32" @@ -192,10 +192,16 @@ class QemuBase(BaseModel): maxcpus: Optional[int] = Field(None, ge=1, le=255, description="Maximum number of hotpluggable vCPUs") adapters: Optional[int] = Field(None, ge=0, le=275, description="Number of adapters") adapter_type: Optional[QemuAdapterType] = Field(None, description="QEMU adapter type") - mac_address: Optional[str] = Field(None, description="QEMU MAC address", regex="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$") + mac_address: Optional[str] = Field( + None, description="QEMU MAC address", regex="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$" + ) legacy_networking: Optional[bool] = Field(None, description="Use QEMU legagy networking commands (-net syntax)") - replicate_network_connection_state: Optional[bool] = Field(None, description="Replicate the network connection state for links in Qemu") - create_config_disk: Optional[bool] = Field(None, description="Automatically create a config disk on HDD disk interface (secondary slave)") + replicate_network_connection_state: Optional[bool] = Field( + None, description="Replicate the network connection state for links in Qemu" + ) + create_config_disk: Optional[bool] = Field( + None, description="Automatically create a config disk on HDD disk interface (secondary slave)" + ) on_close: Optional[QemuOnCloseAction] = Field(None, description="Action to execute on the VM is closed") cpu_throttling: Optional[int] = Field(None, ge=0, le=800, description="Percentage of CPU allowed for QEMU") process_priority: Optional[QemuProcessPriority] = Field(None, description="Process priority for QEMU") diff --git a/gns3server/schemas/qemu_templates.py b/gns3server/schemas/qemu_templates.py index 5b3baa25..8ab0d452 100644 --- a/gns3server/schemas/qemu_templates.py +++ b/gns3server/schemas/qemu_templates.py @@ -24,7 +24,7 @@ from .qemu_nodes import ( QemuBootPriority, QemuDiskInterfaceType, QemuProcessPriority, - CustomAdapter + CustomAdapter, ) from pydantic import Field @@ -44,12 +44,21 @@ class QemuTemplate(TemplateBase): maxcpus: Optional[int] = Field(1, ge=1, le=255, description="Maximum number of hotpluggable vCPUs") adapters: Optional[int] = Field(1, ge=0, le=275, description="Number of adapters") adapter_type: Optional[QemuAdapterType] = Field("e1000", description="QEMU adapter type") - mac_address: Optional[str] = Field("", description="QEMU MAC address", regex="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$|^$") + mac_address: Optional[str] = Field( + "", description="QEMU MAC address", regex="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$|^$" + ) first_port_name: Optional[str] = Field("", description="Optional name of the first networking port example: eth0") - port_name_format: Optional[str] = Field("Ethernet{0}", description="Optional formatting of the networking port example: eth{0}") - port_segment_size: Optional[int] = Field(0, description="Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2") + port_name_format: Optional[str] = Field( + "Ethernet{0}", description="Optional formatting of the networking port example: eth{0}" + ) + port_segment_size: Optional[int] = Field( + 0, + description="Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2", + ) console_type: Optional[QemuConsoleType] = Field("telnet", description="Console type") - console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started") + console_auto_start: Optional[bool] = Field( + False, description="Automatically start the console when the node has started" + ) aux_type: Optional[QemuConsoleType] = Field("none", description="Auxiliary console type") boot_priority: Optional[QemuBootPriority] = Field("c", description="QEMU boot priority") hda_disk_image: Optional[str] = Field("", description="QEMU hda disk image path") @@ -66,8 +75,12 @@ class QemuTemplate(TemplateBase): bios_image: Optional[str] = Field("", description="QEMU bios image path") kernel_command_line: Optional[str] = Field("", description="QEMU kernel command line") legacy_networking: Optional[bool] = Field(False, description="Use QEMU legagy networking commands (-net syntax)") - replicate_network_connection_state: Optional[bool] = Field(True, description="Replicate the network connection state for links in Qemu") - create_config_disk: Optional[bool] = Field(False, description="Automatically create a config disk on HDD disk interface (secondary slave)") + replicate_network_connection_state: Optional[bool] = Field( + True, description="Replicate the network connection state for links in Qemu" + ) + create_config_disk: Optional[bool] = Field( + False, description="Automatically create a config disk on HDD disk interface (secondary slave)" + ) on_close: Optional[QemuOnCloseAction] = Field("power_off", description="Action to execute on the VM is closed") cpu_throttling: Optional[int] = Field(0, ge=0, le=800, description="Percentage of CPU allowed for QEMU") process_priority: Optional[QemuProcessPriority] = Field("normal", description="Process priority for QEMU") diff --git a/gns3server/schemas/topology.py b/gns3server/schemas/topology.py index 9cf882dc..ee0c69a1 100644 --- a/gns3server/schemas/topology.py +++ b/gns3server/schemas/topology.py @@ -24,10 +24,7 @@ from .drawings import Drawing from .links import Link from .nodes import Node -from .projects import ( - Supplier, - Variable -) +from .projects import Supplier, Variable from pydantic import BaseModel, Field from typing import Optional, List @@ -81,5 +78,5 @@ def main(): Topology.parse_obj(data) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/gns3server/schemas/virtualbox_nodes.py b/gns3server/schemas/virtualbox_nodes.py index 6a785a8f..72fc1144 100644 --- a/gns3server/schemas/virtualbox_nodes.py +++ b/gns3server/schemas/virtualbox_nodes.py @@ -43,11 +43,11 @@ class VirtualBoxOnCloseAction(str, Enum): class VirtualBoxAdapterType(str, Enum): - pcnet_pci_ii = "PCnet-PCI II (Am79C970A)", - pcnet_fast_iii = "PCNet-FAST III (Am79C973)", - intel_pro_1000_mt_desktop = "Intel PRO/1000 MT Desktop (82540EM)", - intel_pro_1000_t_server = "Intel PRO/1000 T Server (82543GC)", - intel_pro_1000_mt_server = "Intel PRO/1000 MT Server (82545EM)", + pcnet_pci_ii = ("PCnet-PCI II (Am79C970A)",) + pcnet_fast_iii = ("PCNet-FAST III (Am79C973)",) + intel_pro_1000_mt_desktop = ("Intel PRO/1000 MT Desktop (82540EM)",) + intel_pro_1000_t_server = ("Intel PRO/1000 T Server (82543GC)",) + intel_pro_1000_mt_server = ("Intel PRO/1000 MT Server (82545EM)",) paravirtualized_network = "Paravirtualized Network (virtio-net)" diff --git a/gns3server/schemas/virtualbox_templates.py b/gns3server/schemas/virtualbox_templates.py index fdb386b8..967bea3d 100644 --- a/gns3server/schemas/virtualbox_templates.py +++ b/gns3server/schemas/virtualbox_templates.py @@ -15,12 +15,7 @@ # along with this program. If not, see . from .templates import Category, TemplateBase -from .virtualbox_nodes import ( - VirtualBoxConsoleType, - VirtualBoxAdapterType, - VirtualBoxOnCloseAction, - CustomAdapter -) +from .virtualbox_nodes import VirtualBoxConsoleType, VirtualBoxAdapterType, VirtualBoxOnCloseAction, CustomAdapter from pydantic import Field from typing import Optional, List @@ -34,14 +29,27 @@ class VirtualBoxTemplate(TemplateBase): vmname: str = Field(..., description="VirtualBox VM name (in VirtualBox itself)") ram: Optional[int] = Field(256, gt=0, description="Amount of RAM in MB") linked_clone: Optional[bool] = Field(False, description="Whether the VM is a linked clone or not") - adapters: Optional[int] = Field(1, ge=0, le=36, description="Number of adapters") # 36 is the maximum given by the ICH9 chipset in VirtualBox + adapters: Optional[int] = Field( + 1, ge=0, le=36, description="Number of adapters" + ) # 36 is the maximum given by the ICH9 chipset in VirtualBox use_any_adapter: Optional[bool] = Field(False, description="Allow GNS3 to use any VirtualBox adapter") - adapter_type: Optional[VirtualBoxAdapterType] = Field("Intel PRO/1000 MT Desktop (82540EM)", description="VirtualBox adapter type") + adapter_type: Optional[VirtualBoxAdapterType] = Field( + "Intel PRO/1000 MT Desktop (82540EM)", description="VirtualBox adapter type" + ) first_port_name: Optional[str] = Field("", description="Optional name of the first networking port example: eth0") - port_name_format: Optional[str] = Field("Ethernet{0}", description="Optional formatting of the networking port example: eth{0}") - port_segment_size: Optional[int] = Field(0, description="Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2") + port_name_format: Optional[str] = Field( + "Ethernet{0}", description="Optional formatting of the networking port example: eth{0}" + ) + port_segment_size: Optional[int] = Field( + 0, + description="Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2", + ) headless: Optional[bool] = Field(False, description="Headless mode") - on_close: Optional[VirtualBoxOnCloseAction] = Field("power_off", description="Action to execute on the VM is closed") + on_close: Optional[VirtualBoxOnCloseAction] = Field( + "power_off", description="Action to execute on the VM is closed" + ) console_type: Optional[VirtualBoxConsoleType] = Field("none", description="Console type") - console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started") + console_auto_start: Optional[bool] = Field( + False, description="Automatically start the console when the node has started" + ) custom_adapters: Optional[List[CustomAdapter]] = Field(default_factory=list, description="Custom adapters") diff --git a/gns3server/schemas/vmware_templates.py b/gns3server/schemas/vmware_templates.py index cecb576d..e0d6cef0 100644 --- a/gns3server/schemas/vmware_templates.py +++ b/gns3server/schemas/vmware_templates.py @@ -16,12 +16,7 @@ from .templates import Category, TemplateBase -from .vmware_nodes import ( - VMwareConsoleType, - VMwareAdapterType, - VMwareOnCloseAction, - CustomAdapter -) +from .vmware_nodes import VMwareConsoleType, VMwareAdapterType, VMwareOnCloseAction, CustomAdapter from pydantic import Field from typing import Optional, List @@ -35,13 +30,22 @@ class VMwareTemplate(TemplateBase): vmx_path: str = Field(..., description="Path to the vmx file") linked_clone: Optional[bool] = Field(False, description="Whether the VM is a linked clone or not") first_port_name: Optional[str] = Field("", description="Optional name of the first networking port example: eth0") - port_name_format: Optional[str] = Field("Ethernet{0}", description="Optional formatting of the networking port example: eth{0}") - port_segment_size: Optional[int] = Field(0, description="Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2") - adapters: Optional[int] = Field(1, ge=0, le=10, description="Number of adapters") # 10 is the maximum adapters support by VMware VMs + port_name_format: Optional[str] = Field( + "Ethernet{0}", description="Optional formatting of the networking port example: eth{0}" + ) + port_segment_size: Optional[int] = Field( + 0, + description="Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2", + ) + adapters: Optional[int] = Field( + 1, ge=0, le=10, description="Number of adapters" + ) # 10 is the maximum adapters support by VMware VMs adapter_type: Optional[VMwareAdapterType] = Field("e1000", description="VMware adapter type") use_any_adapter: Optional[bool] = Field(False, description="Allow GNS3 to use any VMware adapter") headless: Optional[bool] = Field(False, description="Headless mode") on_close: Optional[VMwareOnCloseAction] = Field("power_off", description="Action to execute on the VM is closed") console_type: Optional[VMwareConsoleType] = Field("none", description="Console type") - console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started") + console_auto_start: Optional[bool] = Field( + False, description="Automatically start the console when the node has started" + ) custom_adapters: Optional[List[CustomAdapter]] = Field(default_factory=list, description="Custom adapters") diff --git a/gns3server/schemas/vpcs_templates.py b/gns3server/schemas/vpcs_templates.py index 80cd18f9..18291c89 100644 --- a/gns3server/schemas/vpcs_templates.py +++ b/gns3server/schemas/vpcs_templates.py @@ -29,4 +29,6 @@ class VPCSTemplate(TemplateBase): symbol: Optional[str] = ":/symbols/vpcs_guest.svg" base_script_file: Optional[str] = Field("vpcs_base_config.txt", description="Script file") console_type: Optional[ConsoleType] = Field("telnet", description="Console type") - console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started") + console_auto_start: Optional[bool] = Field( + False, description="Automatically start the console when the node has started" + ) diff --git a/gns3server/server.py b/gns3server/server.py index 2acaf4f6..409d35cb 100644 --- a/gns3server/server.py +++ b/gns3server/server.py @@ -42,6 +42,7 @@ from gns3server.api.server import app from pydantic import ValidationError import logging + log = logging.getLogger(__name__) @@ -104,16 +105,19 @@ class Server: parser.add_argument("--certfile", help="SSL cert file") parser.add_argument("--certkey", help="SSL key file") parser.add_argument("-L", "--local", action="store_true", help="local mode (allows some insecure operations)") - parser.add_argument("-A", "--allow", action="store_true", - help="allow remote connections to local console ports") + parser.add_argument( + "-A", "--allow", action="store_true", help="allow remote connections to local console ports" + ) parser.add_argument("-q", "--quiet", default=False, action="store_true", help="do not show logs on stdout") parser.add_argument("-d", "--debug", default=False, action="store_true", help="show debug logs") parser.add_argument("--logfile", help="send output to logfile instead of console") parser.add_argument("--logmaxsize", default=10000000, help="maximum logfile size in bytes (default is 10MB)") - parser.add_argument("--logbackupcount", default=10, - help="number of historical log files to keep (default is 10)") - parser.add_argument("--logcompression", default=False, action="store_true", - help="compress inactive (historical) logs") + parser.add_argument( + "--logbackupcount", default=10, help="number of historical log files to keep (default is 10)" + ) + parser.add_argument( + "--logcompression", default=False, action="store_true", help="compress inactive (historical) logs" + ) parser.add_argument("--daemon", action="store_true", help="start as a daemon") parser.add_argument("--pid", help="store process pid") parser.add_argument("--profile", help="Settings profile (blank will use default settings files)") @@ -123,12 +127,14 @@ class Server: if args.debug: level = logging.DEBUG - self._stream_handler = init_logger(level, - logfile=args.logfile, - max_bytes=int(args.logmaxsize), - backup_count=int(args.logbackupcount), - compression=args.logcompression, - quiet=args.quiet) + self._stream_handler = init_logger( + level, + logfile=args.logfile, + max_bytes=int(args.logmaxsize), + backup_count=int(args.logbackupcount), + compression=args.logcompression, + quiet=args.quiet, + ) try: if args.config: @@ -146,7 +152,7 @@ class Server: "certfile": config.Server.certfile, "certkey": config.Server.certkey, "local": config.Server.local, - "allow": config.Server.allow_remote_console + "allow": config.Server.allow_remote_console, } parser.set_defaults(**defaults) @@ -172,7 +178,6 @@ class Server: await Controller.instance().reload() def _signal_handling(self): - def signal_handler(signame, *args): try: @@ -240,7 +245,7 @@ class Server: sys.exit(1) try: - with open(path, 'w+') as f: + with open(path, "w+") as f: f.write(str(os.getpid())) except OSError as e: log.critical("Can't write pid file %s: %s", path, str(e)) @@ -278,10 +283,11 @@ class Server: if sys.version_info < (3, 6, 0): raise SystemExit("Python 3.6 or higher is required") - log.info("Running with Python {major}.{minor}.{micro} and has PID {pid}".format(major=sys.version_info[0], - minor=sys.version_info[1], - micro=sys.version_info[2], - pid=os.getpid())) + log.info( + "Running with Python {major}.{minor}.{micro} and has PID {pid}".format( + major=sys.version_info[0], minor=sys.version_info[1], micro=sys.version_info[2], pid=os.getpid() + ) + ) # check for the correct locale (UNIX/Linux only) self._locale_check() @@ -313,12 +319,14 @@ class Server: raise SystemExit log.info("SSL is enabled") - config = uvicorn.Config(app, - host=host, - port=port, - access_log=access_log, - ssl_certfile=config.Server.certfile, - ssl_keyfile=config.Server.certkey) + config = uvicorn.Config( + app, + host=host, + port=port, + access_log=access_log, + ssl_certfile=config.Server.certfile, + ssl_keyfile=config.Server.certkey, + ) # overwrite uvicorn loggers with our own logger for uvicorn_logger_name in ("uvicorn", "uvicorn.error"): diff --git a/gns3server/services/__init__.py b/gns3server/services/__init__.py index 9e828880..f9be542e 100644 --- a/gns3server/services/__init__.py +++ b/gns3server/services/__init__.py @@ -15,4 +15,5 @@ # along with this program. If not, see . from .authentication import AuthService + auth_service = AuthService() diff --git a/gns3server/services/authentication.py b/gns3server/services/authentication.py index 5bc9aed7..e2a4b64f 100644 --- a/gns3server/services/authentication.py +++ b/gns3server/services/authentication.py @@ -26,6 +26,7 @@ from gns3server.config import Config from pydantic import ValidationError import logging + log = logging.getLogger(__name__) pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") @@ -34,7 +35,6 @@ DEFAULT_JWT_SECRET_KEY = "efd08eccec3bd0a1be2e086670e5efa90969c68d07e072d7354a76 class AuthService: - def hash_password(self, password: str) -> str: return pwd_context.hash(password) @@ -43,12 +43,7 @@ class AuthService: return pwd_context.verify(password, hashed_password) - def create_access_token( - self, - username, - secret_key: str = None, - expires_in: int = 0 - ) -> str: + def create_access_token(self, username, secret_key: str = None, expires_in: int = 0) -> str: if not expires_in: expires_in = Config.instance().settings.Controller.jwt_access_token_expire_minutes diff --git a/gns3server/services/computes.py b/gns3server/services/computes.py index 63e8e6a4..737fc13f 100644 --- a/gns3server/services/computes.py +++ b/gns3server/services/computes.py @@ -26,12 +26,11 @@ from gns3server.controller import Controller from gns3server.controller.controller_error import ( ControllerBadRequestError, ControllerNotFoundError, - ControllerForbiddenError + ControllerForbiddenError, ) class ComputesService: - def __init__(self, computes_repo: ComputesRepository): self._computes_repo = computes_repo @@ -47,9 +46,11 @@ class ComputesService: if await self._computes_repo.get_compute(compute_create.compute_id): raise ControllerBadRequestError(f"Compute '{compute_create.compute_id}' is already registered") db_compute = await self._computes_repo.create_compute(compute_create) - await self._controller.add_compute(compute_id=str(db_compute.compute_id), - connect=False, - **compute_create.dict(exclude_unset=True, exclude={"compute_id"})) + await self._controller.add_compute( + compute_id=str(db_compute.compute_id), + connect=False, + **compute_create.dict(exclude_unset=True, exclude={"compute_id"}), + ) self._controller.notification.controller_emit("compute.created", db_compute.asjson()) return db_compute @@ -61,9 +62,7 @@ class ComputesService: return db_compute async def update_compute( - self, - compute_id: Union[str, UUID], - compute_update: schemas.ComputeUpdate + self, compute_id: Union[str, UUID], compute_update: schemas.ComputeUpdate ) -> models.Compute: compute = self._controller.get_compute(str(compute_id)) diff --git a/gns3server/services/templates.py b/gns3server/services/templates.py index f48d58b0..c36bfeb6 100644 --- a/gns3server/services/templates.py +++ b/gns3server/services/templates.py @@ -27,7 +27,7 @@ from gns3server.controller import Controller from gns3server.controller.controller_error import ( ControllerBadRequestError, ControllerNotFoundError, - ControllerForbiddenError + ControllerForbiddenError, ) TEMPLATE_TYPE_TO_SHEMA = { @@ -40,7 +40,7 @@ TEMPLATE_TYPE_TO_SHEMA = { "virtualbox": schemas.VirtualBoxTemplate, "vmware": schemas.VMwareTemplate, "iou": schemas.IOUTemplate, - "qemu": schemas.QemuTemplate + "qemu": schemas.QemuTemplate, } DYNAMIPS_PLATFORM_TO_SHEMA = { @@ -50,7 +50,7 @@ DYNAMIPS_PLATFORM_TO_SHEMA = { "c3600": schemas.C3600DynamipsTemplate, "c2691": schemas.C2691DynamipsTemplate, "c2600": schemas.C2600DynamipsTemplate, - "c1700": schemas.C1700DynamipsTemplate + "c1700": schemas.C1700DynamipsTemplate, } # built-in templates have their compute_id set to None to tell clients to select a compute @@ -63,7 +63,7 @@ BUILTIN_TEMPLATES = [ "category": "guest", "symbol": ":/symbols/cloud.svg", "compute_id": None, - "builtin": True + "builtin": True, }, { "template_id": uuid.uuid3(uuid.NAMESPACE_DNS, "nat"), @@ -73,7 +73,7 @@ BUILTIN_TEMPLATES = [ "category": "guest", "symbol": ":/symbols/cloud.svg", "compute_id": None, - "builtin": True + "builtin": True, }, { "template_id": uuid.uuid3(uuid.NAMESPACE_DNS, "vpcs"), @@ -84,7 +84,7 @@ BUILTIN_TEMPLATES = [ "symbol": ":/symbols/vpcs_guest.svg", "base_script_file": "vpcs_base_config.txt", "compute_id": None, - "builtin": True + "builtin": True, }, { "template_id": uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_switch"), @@ -95,7 +95,7 @@ BUILTIN_TEMPLATES = [ "category": "switch", "symbol": ":/symbols/ethernet_switch.svg", "compute_id": None, - "builtin": True + "builtin": True, }, { "template_id": uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_hub"), @@ -105,7 +105,7 @@ BUILTIN_TEMPLATES = [ "category": "switch", "symbol": ":/symbols/hub.svg", "compute_id": None, - "builtin": True + "builtin": True, }, { "template_id": uuid.uuid3(uuid.NAMESPACE_DNS, "frame_relay_switch"), @@ -115,7 +115,7 @@ BUILTIN_TEMPLATES = [ "category": "switch", "symbol": ":/symbols/frame_relay_switch.svg", "compute_id": None, - "builtin": True + "builtin": True, }, { "template_id": uuid.uuid3(uuid.NAMESPACE_DNS, "atm_switch"), @@ -125,13 +125,12 @@ BUILTIN_TEMPLATES = [ "category": "switch", "symbol": ":/symbols/atm_switch.svg", "compute_id": None, - "builtin": True + "builtin": True, }, ] class TemplatesService: - def __init__(self, templates_repo: TemplatesRepository): self._templates_repo = templates_repo diff --git a/gns3server/utils/__init__.py b/gns3server/utils/__init__.py index c571582d..76635765 100644 --- a/gns3server/utils/__init__.py +++ b/gns3server/utils/__init__.py @@ -60,10 +60,10 @@ def parse_version(version): """ release_type_found = False - version_infos = re.split(r'(\.|[a-z]+)', version) + version_infos = re.split(r"(\.|[a-z]+)", version) version = [] for info in version_infos: - if info == '.' or len(info) == 0: + if info == "." or len(info) == 0: continue try: info = int(info) @@ -77,8 +77,8 @@ def parse_version(version): if len(version) == 2: version.append("000000") # We want rc to be at lower level than dev version - if info == 'rc': - info = 'c' + if info == "rc": + info = "c" version.append(info) release_type_found = True if release_type_found is False: @@ -97,6 +97,6 @@ def shlex_quote(s): """ if sys.platform.startswith("win"): - return s if re.match(r'^[-_\w./]+$', s) else '"%s"' % s.replace('"', '\\"') + return s if re.match(r"^[-_\w./]+$", s) else '"%s"' % s.replace('"', '\\"') else: return shlex.quote(s) diff --git a/gns3server/utils/application_id.py b/gns3server/utils/application_id.py index 39042626..9c9d8b34 100644 --- a/gns3server/utils/application_id.py +++ b/gns3server/utils/application_id.py @@ -17,6 +17,7 @@ from gns3server.controller.controller_error import ControllerError import logging + log = logging.getLogger(__name__) @@ -43,4 +44,6 @@ def get_next_application_id(projects, computes): application_id = (pool - used).pop() return application_id except KeyError: - raise ControllerError("Cannot create a new IOU node (limit of 512 nodes across all opened projects using the same computes)") + raise ControllerError( + "Cannot create a new IOU node (limit of 512 nodes across all opened projects using the same computes)" + ) diff --git a/gns3server/utils/asyncio/__init__.py b/gns3server/utils/asyncio/__init__.py index 7abb30ea..bd2d65e1 100644 --- a/gns3server/utils/asyncio/__init__.py +++ b/gns3server/utils/asyncio/__init__.py @@ -50,7 +50,7 @@ async def cancellable_wait_run_in_executor(func, *args, **kwargs): :returns: Return the result of the function """ stopped_event = threading.Event() - kwargs['stopped_event'] = stopped_event + kwargs["stopped_event"] = stopped_event try: await wait_run_in_executor(func, *args, **kwargs) except asyncio.CancelledError: @@ -81,6 +81,7 @@ async def subprocess_check_output(*args, cwd=None, env=None, stderr=False): # and the code of VPCS, dynamips... Will detect it's not the correct binary return output.decode("utf-8", errors="ignore") + async def wait_for_process_termination(process, timeout=10): """ Wait for a process terminate, and raise asyncio.TimeoutError in case of @@ -152,7 +153,6 @@ async def wait_for_named_pipe_creation(pipe_path, timeout=60): def locking(f): - @functools.wraps(f) async def wrapper(oself, *args, **kwargs): lock_name = "__" + f.__name__ + "_lock" @@ -160,4 +160,5 @@ def locking(f): setattr(oself, lock_name, asyncio.Lock()) async with getattr(oself, lock_name): return await f(oself, *args, **kwargs) + return wrapper diff --git a/gns3server/utils/asyncio/aiozipstream.py b/gns3server/utils/asyncio/aiozipstream.py index 9071896d..451a3165 100644 --- a/gns3server/utils/asyncio/aiozipstream.py +++ b/gns3server/utils/asyncio/aiozipstream.py @@ -32,10 +32,18 @@ import asyncio import aiofiles from concurrent import futures -from zipfile import (structCentralDir, structEndArchive64, structEndArchive, structEndArchive64Locator, - stringCentralDir, stringEndArchive64, stringEndArchive, stringEndArchive64Locator) +from zipfile import ( + structCentralDir, + structEndArchive64, + structEndArchive, + structEndArchive64Locator, + stringCentralDir, + stringEndArchive64, + stringEndArchive, + stringEndArchive64Locator, +) -stringDataDescriptor = b'PK\x07\x08' # magic number for data descriptor +stringDataDescriptor = b"PK\x07\x08" # magic number for data descriptor def _get_compressor(compress_type): @@ -45,21 +53,23 @@ def _get_compressor(compress_type): if compress_type == zipfile.ZIP_DEFLATED: from zipfile import zlib + return zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) elif compress_type == zipfile.ZIP_BZIP2: from zipfile import bz2 + return bz2.BZ2Compressor() elif compress_type == zipfile.ZIP_LZMA: from zipfile import LZMACompressor + return LZMACompressor() else: return None class PointerIO: - - def __init__(self, mode='wb'): - if mode not in ('wb', ): + def __init__(self, mode="wb"): + if mode not in ("wb",): raise RuntimeError('zipstream.ZipFile() requires mode "wb"') self.data_pointer = 0 self.__mode = mode @@ -90,18 +100,17 @@ class PointerIO: def write(self, data): if self.closed: - raise ValueError('I/O operation on closed file') + raise ValueError("I/O operation on closed file") if isinstance(data, str): - data = data.encode('utf-8') + data = data.encode("utf-8") if not isinstance(data, bytes): - raise TypeError('expected bytes') + raise TypeError("expected bytes") self.data_pointer += len(data) return data class ZipInfo(zipfile.ZipInfo): - def __init__(self, *args, **kwargs): zipfile.ZipInfo.__init__(self, *args, **kwargs) @@ -113,23 +122,22 @@ class ZipInfo(zipfile.ZipInfo): """ if self.compress_size > zipfile.ZIP64_LIMIT or self.file_size > zipfile.ZIP64_LIMIT: - fmt = b'<4sLQQ' + fmt = b"<4sLQQ" else: - fmt = b'<4sLLL' + fmt = b"<4sLLL" return struct.pack(fmt, stringDataDescriptor, self.CRC, self.compress_size, self.file_size) class ZipFile(zipfile.ZipFile): - - def __init__(self, fileobj=None, mode='w', compression=zipfile.ZIP_STORED, allowZip64=True, chunksize=32768): + def __init__(self, fileobj=None, mode="w", compression=zipfile.ZIP_STORED, allowZip64=True, chunksize=32768): """Open the ZIP file with mode write "w".""" - if mode not in ('w', ): + if mode not in ("w",): raise RuntimeError('aiozipstream.ZipFile() requires mode "w"') if fileobj is None: fileobj = PointerIO() - self._comment = b'' + self._comment = b"" zipfile.ZipFile.__init__(self, fileobj, mode=mode, compression=compression, allowZip64=allowZip64) self._chunksize = chunksize self.paths_to_write = [] @@ -156,8 +164,8 @@ class ZipFile(zipfile.ZipFile): # check for valid comment length if len(comment) >= zipfile.ZIP_MAX_COMMENT: if self.debug: - print('Archive comment is too long; truncating to %d bytes' % zipfile.ZIP_MAX_COMMENT) - comment = comment[:zipfile.ZIP_MAX_COMMENT] + print("Archive comment is too long; truncating to %d bytes" % zipfile.ZIP_MAX_COMMENT) + comment = comment[: zipfile.ZIP_MAX_COMMENT] self._comment = comment self._didModify = True @@ -192,7 +200,7 @@ class ZipFile(zipfile.ZipFile): Write a file to the archive under the name `arcname`. """ - kwargs = {'filename': filename, 'arcname': arcname, 'compress_type': compress_type} + kwargs = {"filename": filename, "arcname": arcname, "compress_type": compress_type} self.paths_to_write.append(kwargs) def write_iter(self, arcname, iterable, compress_type=None): @@ -200,7 +208,7 @@ class ZipFile(zipfile.ZipFile): Write the bytes iterable `iterable` to the archive under the name `arcname`. """ - kwargs = {'arcname': arcname, 'iterable': iterable, 'compress_type': compress_type} + kwargs = {"arcname": arcname, "iterable": iterable, "compress_type": compress_type} self.paths_to_write.append(kwargs) def writestr(self, arcname, data, compress_type=None): @@ -210,6 +218,7 @@ class ZipFile(zipfile.ZipFile): def _iterable(): yield data + return self.write_iter(arcname, _iterable(), compress_type=compress_type) async def _write(self, filename=None, iterable=None, arcname=None, compress_type=None): @@ -218,8 +227,7 @@ class ZipFile(zipfile.ZipFile): """ if not self.fp: - raise RuntimeError( - "Attempt to write to ZIP archive that was already closed") + raise RuntimeError("Attempt to write to ZIP archive that was already closed") if (filename is None and iterable is None) or (filename is not None and iterable is not None): raise ValueError("either (exclusively) filename or iterable shall be not None") @@ -237,12 +245,12 @@ class ZipFile(zipfile.ZipFile): while arcname[0] in (os.sep, os.altsep): arcname = arcname[1:] if isdir: - arcname += '/' + arcname += "/" zinfo = ZipInfo(arcname, date_time) if st: - zinfo.external_attr = (st[0] & 0xFFFF) << 16 # Unix attributes + zinfo.external_attr = (st[0] & 0xFFFF) << 16 # Unix attributes else: - zinfo.external_attr = 0o600 << 16 # ?rw------- + zinfo.external_attr = 0o600 << 16 # ?rw------- if compress_type is None: zinfo.compress_type = self.compression else: @@ -253,8 +261,8 @@ class ZipFile(zipfile.ZipFile): else: zinfo.file_size = 0 zinfo.flag_bits = 0x00 - zinfo.flag_bits |= 0x08 # ZIP flag bits, bit 3 indicates presence of data descriptor - zinfo.header_offset = self.fp.tell() # Start of header bytes + zinfo.flag_bits |= 0x08 # ZIP flag bits, bit 3 indicates presence of data descriptor + zinfo.header_offset = self.fp.tell() # Start of header bytes if zinfo.compress_type == zipfile.ZIP_LZMA: # Compressed data includes an end-of-stream (EOS) marker zinfo.flag_bits |= 0x02 @@ -284,15 +292,15 @@ class ZipFile(zipfile.ZipFile): if filename: async for buf in self.data_generator(filename): file_size = file_size + len(buf) - CRC = zipfile.crc32(buf, CRC) & 0xffffffff + CRC = zipfile.crc32(buf, CRC) & 0xFFFFFFFF if cmpr: buf = await self._run_in_executor(cmpr.compress, buf) compress_size = compress_size + len(buf) yield self.fp.write(buf) - else: # we have an iterable + else: # we have an iterable for buf in iterable: file_size = file_size + len(buf) - CRC = zipfile.crc32(buf, CRC) & 0xffffffff + CRC = zipfile.crc32(buf, CRC) & 0xFFFFFFFF if cmpr: buf = await self._run_in_executor(cmpr.compress, buf) compress_size = compress_size + len(buf) @@ -309,9 +317,9 @@ class ZipFile(zipfile.ZipFile): zinfo.file_size = file_size if not zip64 and self._allowZip64: if file_size > zipfile.ZIP64_LIMIT: - raise RuntimeError('File size has increased during compressing') + raise RuntimeError("File size has increased during compressing") if compress_size > zipfile.ZIP64_LIMIT: - raise RuntimeError('Compressed size larger than uncompressed size') + raise RuntimeError("Compressed size larger than uncompressed size") yield self.fp.write(zinfo.DataDescriptor()) self.filelist.append(zinfo) @@ -326,10 +334,10 @@ class ZipFile(zipfile.ZipFile): return try: - if self.mode in ('w', 'a') and self._didModify: # write ending records + if self.mode in ("w", "a") and self._didModify: # write ending records count = 0 pos1 = self.fp.tell() - for zinfo in self.filelist: # write central directory + for zinfo in self.filelist: # write central directory count = count + 1 dt = zinfo.date_time dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] @@ -338,15 +346,15 @@ class ZipFile(zipfile.ZipFile): if zinfo.file_size > zipfile.ZIP64_LIMIT or zinfo.compress_size > zipfile.ZIP64_LIMIT: extra.append(zinfo.file_size) extra.append(zinfo.compress_size) - file_size = 0xffffffff - compress_size = 0xffffffff + file_size = 0xFFFFFFFF + compress_size = 0xFFFFFFFF else: file_size = zinfo.file_size compress_size = zinfo.compress_size if zinfo.header_offset > zipfile.ZIP64_LIMIT: extra.append(zinfo.header_offset) - header_offset = 0xffffffff + header_offset = 0xFFFFFFFF else: header_offset = zinfo.header_offset @@ -354,9 +362,7 @@ class ZipFile(zipfile.ZipFile): min_version = 0 if extra: # Append a ZIP64 field to the extra's - extra_data = struct.pack( - b'= zipfile.ZIP_FILECOUNT_LIMIT or - centDirOffset > zipfile.ZIP64_LIMIT or - centDirSize > zipfile.ZIP64_LIMIT): + if ( + centDirCount >= zipfile.ZIP_FILECOUNT_LIMIT + or centDirOffset > zipfile.ZIP64_LIMIT + or centDirSize > zipfile.ZIP64_LIMIT + ): # Need to write the ZIP64 end-of-archive records zip64endrec = struct.pack( - structEndArchive64, stringEndArchive64, - 44, 45, 45, 0, 0, centDirCount, centDirCount, - centDirSize, centDirOffset) + structEndArchive64, + stringEndArchive64, + 44, + 45, + 45, + 0, + 0, + centDirCount, + centDirCount, + centDirSize, + centDirOffset, + ) yield self.fp.write(zip64endrec) - zip64locrec = struct.pack( - structEndArchive64Locator, - stringEndArchive64Locator, 0, pos2, 1) + zip64locrec = struct.pack(structEndArchive64Locator, stringEndArchive64Locator, 0, pos2, 1) yield self.fp.write(zip64locrec) centDirCount = min(centDirCount, 0xFFFF) centDirSize = min(centDirSize, 0xFFFFFFFF) centDirOffset = min(centDirOffset, 0xFFFFFFFF) - endrec = struct.pack(structEndArchive, stringEndArchive, - 0, 0, centDirCount, centDirCount, - centDirSize, centDirOffset, len(self._comment)) + endrec = struct.pack( + structEndArchive, + stringEndArchive, + 0, + 0, + centDirCount, + centDirCount, + centDirSize, + centDirOffset, + len(self._comment), + ) yield self.fp.write(endrec) yield self.fp.write(self._comment) self.fp.flush() diff --git a/gns3server/utils/asyncio/embed_shell.py b/gns3server/utils/asyncio/embed_shell.py index b93b4f87..a4f3e4c3 100644 --- a/gns3server/utils/asyncio/embed_shell.py +++ b/gns3server/utils/asyncio/embed_shell.py @@ -46,7 +46,7 @@ class EmbedShell: self._loop = loop self._reader = reader self._writer = writer - self._prompt = '> ' + self._prompt = "> " self._welcome_message = welcome_message @property @@ -85,30 +85,30 @@ class EmbedShell: """ Show help """ - res = '' + res = "" if len(args) == 0: - res = 'Help:\n' + res = "Help:\n" for name, value in inspect.getmembers(self): if not inspect.isgeneratorfunction(value): continue - if name.startswith('_') or (len(args) and name != args[0]) or name == 'run': + if name.startswith("_") or (len(args) and name != args[0]) or name == "run": continue doc = inspect.getdoc(value) res += name if len(args) and doc: - res += ': ' + doc + res += ": " + doc elif doc: - res += ': ' + doc.split('\n')[0] - res += '\n' + res += ": " + doc.split("\n")[0] + res += "\n" if len(args) == 0: - res += '\nhelp command for details about a command\n' + res += "\nhelp command for details about a command\n" return res async def _parse_command(self, text): - cmd = text.split(' ') + cmd = text.split(" ") found = False - if cmd[0] == '?': - cmd[0] = 'help' + if cmd[0] == "?": + cmd[0] = "help" # when there is no command specified just return empty result if not cmd[0].strip(): @@ -121,7 +121,7 @@ class EmbedShell: found = True break if not found: - res = (f'Command not found {cmd[0]}\n' + (await self.help())) + res = f"Command not found {cmd[0]}\n" + (await self.help()) return res async def run(self): @@ -130,7 +130,7 @@ class EmbedShell: while True: self._writer.feed_data(self._prompt.encode()) result = await self._reader.readline() - result = result.decode().strip('\n') + result = result.decode().strip("\n") res = await self._parse_command(result) self._writer.feed_data(res.encode()) @@ -143,7 +143,7 @@ class EmbedShell: for name, value in inspect.getmembers(self): if not inspect.isgeneratorfunction(value): continue - if name.startswith('_') or name == 'run': + if name.startswith("_") or name == "run": continue doc = inspect.getdoc(value) commands.append((name, doc)) @@ -156,22 +156,23 @@ class PatchedStdinInput(StdinInput): Fixes issue when PyCharm runs own terminal without emulation. https://github.com/GNS3/gns3-server/issues/1172 """ + def __init__(self, stdin=None): self.stdin = stdin or sys.stdin try: self.stdin.fileno() except io.UnsupportedOperation: - if 'idlelib.run' in sys.modules: - raise io.UnsupportedOperation( - 'Stdin is not a terminal. Running from Idle is not supported.') + if "idlelib.run" in sys.modules: + raise io.UnsupportedOperation("Stdin is not a terminal. Running from Idle is not supported.") else: - raise io.UnsupportedOperation('Stdin is not a terminal.') + raise io.UnsupportedOperation("Stdin is not a terminal.") class UnstoppableEventLoop(EventLoop): """ Partially fake event loop which cannot be stopped by CommandLineInterface """ + def __init__(self, loop): self._loop = loop @@ -202,13 +203,13 @@ class ShellConnection(TelnetConnection): self._cli = None self._cb = None self._size = Size(rows=40, columns=79) - self.encoding = 'utf-8' - + self.encoding = "utf-8" async def connected(self): # prompt_toolkit internally checks if it's on windows during output rendering but # we need to force that we use Vt100_Output not Win32_Output from prompt_toolkit import renderer + renderer.is_windows = lambda: False def get_size(): @@ -218,7 +219,8 @@ class ShellConnection(TelnetConnection): application=create_prompt_application(self._shell.prompt), eventloop=UnstoppableEventLoop(create_asyncio_eventloop(self._loop)), input=PatchedStdinInput(sys.stdin), - output=Vt100_Output(self, get_size)) + output=Vt100_Output(self, get_size), + ) self._cb = self._cli.create_eventloop_callbacks() self._inputstream = InputStream(self._cb.feed_key) @@ -302,19 +304,21 @@ def create_stdin_shell(shell, loop=None): :param loop: The event loop :returns: Telnet server """ + async def feed_stdin(loop, reader, shell): history = InMemoryHistory() completer = WordCompleter([name for name, _ in shell.get_commands()], ignore_case=True) while True: line = await prompt( - ">", patch_stdout=True, return_asyncio_coroutine=True, history=history, completer=completer) - line += '\n' + ">", patch_stdout=True, return_asyncio_coroutine=True, history=history, completer=completer + ) + line += "\n" reader.feed_data(line.encode()) async def read_stdout(writer): while True: c = await writer.read(1) - print(c.decode(), end='') + print(c.decode(), end="") sys.stdout.flush() reader = asyncio.StreamReader() @@ -329,30 +333,31 @@ def create_stdin_shell(shell, loop=None): shell_task = loop.create_task(shell.run()) return asyncio.gather(shell_task, writer_task, reader_task) -if __name__ == '__main__': + +if __name__ == "__main__": loop = asyncio.get_event_loop() class Demo(EmbedShell): - async def hello(self, *args): """ Hello world This command accept arguments: hello tutu will display tutu """ + async def world(): await asyncio.sleep(2) if len(args): - return ' '.join(args) + return " ".join(args) else: - return 'world\n' + return "world\n" return await world() # Demo using telnet shell = Demo(welcome_message="Welcome!\n") server = create_telnet_shell(shell, loop=loop) - coro = asyncio.start_server(server.run, '127.0.0.1', 4444, loop=loop) + coro = asyncio.start_server(server.run, "127.0.0.1", 4444, loop=loop) s = loop.run_until_complete(coro) try: loop.run_forever() diff --git a/gns3server/utils/asyncio/input_stream.py b/gns3server/utils/asyncio/input_stream.py index 3bb9f2ea..e2d936e9 100644 --- a/gns3server/utils/asyncio/input_stream.py +++ b/gns3server/utils/asyncio/input_stream.py @@ -19,160 +19,150 @@ from prompt_toolkit.key_binding.input_processor import KeyPress # ) _DEBUG_RENDERER_INPUT = False -_DEBUG_RENDERER_INPUT_FILENAME = 'prompt-toolkit-render-input.log' +_DEBUG_RENDERER_INPUT_FILENAME = "prompt-toolkit-render-input.log" # Regex matching any CPR response # (Note that we use '\Z' instead of '$', because '$' could include a trailing # newline.) -_cpr_response_re = re.compile('^' + re.escape('\x1b[') + r'\d+;\d+R\Z') +_cpr_response_re = re.compile("^" + re.escape("\x1b[") + r"\d+;\d+R\Z") # Mouse events: # Typical: "Esc[MaB*" Urxvt: "Esc[96;14;13M" and for Xterm SGR: "Esc[<64;85;12M" -_mouse_event_re = re.compile('^' + re.escape('\x1b[') + r'(= 1.9: + if cls._last_measurement is None or (cur_time - cls._last_measurement) >= 1.9: cls._last_cpu_percent = psutil.cpu_percent(interval=None) cls._last_measurement = cur_time diff --git a/gns3server/utils/file_watcher.py b/gns3server/utils/file_watcher.py index d466de77..c4f03759 100644 --- a/gns3server/utils/file_watcher.py +++ b/gns3server/utils/file_watcher.py @@ -29,7 +29,7 @@ class FileWatcher: :param strategy: File change strategy (mtime: modification time, hash: hash compute) """ - def __init__(self, paths, callback, delay=1, strategy='mtime'): + def __init__(self, paths, callback, delay=1, strategy="mtime"): self._paths = [] if not isinstance(paths, list): paths = [paths] @@ -43,7 +43,7 @@ class FileWatcher: self._closed = False self._strategy = strategy - if self._strategy == 'mtime': + if self._strategy == "mtime": # Store modification time self._mtime = {} for path in self._paths: @@ -57,7 +57,7 @@ class FileWatcher: for path in self._paths: try: # Alder32 is a fast but insecure hash algorithm - self._hashed[path] = zlib.adler32(open(path, 'rb').read()) + self._hashed[path] = zlib.adler32(open(path, "rb").read()) except OSError: self._hashed[path] = None asyncio.get_event_loop().call_later(self._delay, self._check_config_file_change) @@ -74,7 +74,7 @@ class FileWatcher: changed = False for path in self._paths: - if self._strategy == 'mtime': + if self._strategy == "mtime": try: mtime = os.stat(path).st_mtime_ns if mtime != self._mtime[path]: @@ -84,7 +84,7 @@ class FileWatcher: self._mtime[path] = None else: try: - hashc = zlib.adler32(open(path, 'rb').read()) + hashc = zlib.adler32(open(path, "rb").read()) if hashc != self._hashed[path]: changed = True self._hashed[path] = hashc diff --git a/gns3server/utils/get_resource.py b/gns3server/utils/get_resource.py index 9201edf0..ac624c05 100644 --- a/gns3server/utils/get_resource.py +++ b/gns3server/utils/get_resource.py @@ -35,6 +35,7 @@ except ValueError: def clean_egg_cache(): try: import shutil + log.debug("Clean egg cache %s", egg_cache_dir) shutil.rmtree(egg_cache_dir) except Exception: diff --git a/gns3server/utils/http_client.py b/gns3server/utils/http_client.py index 279f80af..04eb6f2a 100644 --- a/gns3server/utils/http_client.py +++ b/gns3server/utils/http_client.py @@ -19,6 +19,7 @@ import aiohttp import socket import logging + log = logging.getLogger(__name__) diff --git a/gns3server/utils/images.py b/gns3server/utils/images.py index 2eddf2c7..5a1fdb2d 100644 --- a/gns3server/utils/images.py +++ b/gns3server/utils/images.py @@ -22,6 +22,7 @@ from . import force_unix_path import logging + log = logging.getLogger(__name__) @@ -56,9 +57,11 @@ def list_images(type): if filename not in files: if filename.endswith(".md5sum") or filename.startswith("."): continue - elif ((filename.endswith(".image") or filename.endswith(".bin")) and type == "dynamips") \ - or ((filename.endswith(".bin") or filename.startswith("i86bi")) and type == "iou") \ - or (not filename.endswith(".bin") and not filename.endswith(".image") and type == "qemu"): + elif ( + ((filename.endswith(".image") or filename.endswith(".bin")) and type == "dynamips") + or ((filename.endswith(".bin") or filename.startswith("i86bi")) and type == "iou") + or (not filename.endswith(".bin") and not filename.endswith(".image") and type == "qemu") + ): files.add(filename) # It the image is located in the standard directory the path is relative @@ -73,14 +76,20 @@ def list_images(type): # read the first 7 bytes of the file. elf_header_start = f.read(7) # valid IOS images must start with the ELF magic number, be 32-bit, big endian and have an ELF version of 1 - if not elf_header_start == b'\x7fELF\x01\x02\x01' and not elf_header_start == b'\x7fELF\x01\x01\x01': + if ( + not elf_header_start == b"\x7fELF\x01\x02\x01" + and not elf_header_start == b"\x7fELF\x01\x01\x01" + ): continue - images.append({ - "filename": filename, - "path": force_unix_path(path), - "md5sum": md5sum(os.path.join(root, filename)), - "filesize": os.stat(os.path.join(root, filename)).st_size}) + images.append( + { + "filename": filename, + "path": force_unix_path(path), + "md5sum": md5sum(os.path.join(root, filename)), + "filesize": os.stat(os.path.join(root, filename)).st_size, + } + ) except OSError as e: log.warning(f"Can't add image {path}: {str(e)}") return images @@ -156,7 +165,7 @@ def md5sum(path, stopped_event=None): return None try: - with open(path + '.md5sum') as f: + with open(path + ".md5sum") as f: md5 = f.read().strip() if len(md5) == 32: return md5 @@ -166,7 +175,7 @@ def md5sum(path, stopped_event=None): try: m = hashlib.md5() - with open(path, 'rb') as f: + with open(path, "rb") as f: while True: if stopped_event is not None and stopped_event.is_set(): log.error(f"MD5 sum calculation of `{path}` has stopped due to cancellation") @@ -181,7 +190,7 @@ def md5sum(path, stopped_event=None): return None try: - with open(f'{path}.md5sum', 'w+') as f: + with open(f"{path}.md5sum", "w+") as f: f.write(digest) except OSError as e: log.error("Can't write digest of %s: %s", path, str(e)) @@ -194,6 +203,6 @@ def remove_checksum(path): Remove the checksum of an image from cache if exists """ - path = f'{path}.md5sum' + path = f"{path}.md5sum" if os.path.exists(path): os.remove(path) diff --git a/gns3server/utils/interfaces.py b/gns3server/utils/interfaces.py index fdfee5a6..4557cc56 100644 --- a/gns3server/utils/interfaces.py +++ b/gns3server/utils/interfaces.py @@ -26,9 +26,12 @@ from gns3server.compute.compute_error import ComputeError from gns3server.config import Config if psutil.version_info < (3, 0, 0): - raise Exception("psutil version should >= 3.0.0. If you are under Ubuntu/Debian install gns3 via apt instead of pip") + raise Exception( + "psutil version should >= 3.0.0. If you are under Ubuntu/Debian install gns3 via apt instead of pip" + ) import logging + log = logging.getLogger(__name__) @@ -45,7 +48,10 @@ def _get_windows_interfaces_from_registry(): hkeycard = winreg.OpenKey(hkey, network_card_id) guid, _ = winreg.QueryValueEx(hkeycard, "ServiceName") netcard, _ = winreg.QueryValueEx(hkeycard, "Description") - connection = r"SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}" + fr"\{guid}\Connection" + connection = ( + r"SYSTEM\CurrentControlSet\Control\Network\{4D36E972-E325-11CE-BFC1-08002BE10318}" + + fr"\{guid}\Connection" + ) hkeycon = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, connection) name, _ = winreg.QueryValueEx(hkeycon, "Name") interface = fr"SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces\{guid}" @@ -61,13 +67,17 @@ def _get_windows_interfaces_from_registry(): # get the first IPv4 address only ip_address = ip_address[0] npf_interface = "\\Device\\NPF_{guid}".format(guid=guid) - interfaces.append({"id": npf_interface, - "name": name, - "ip_address": ip_address, - "mac_address": "", # TODO: find MAC address in registry - "netcard": netcard, - "netmask": netmask, - "type": "ethernet"}) + interfaces.append( + { + "id": npf_interface, + "name": name, + "ip_address": ip_address, + "mac_address": "", # TODO: find MAC address in registry + "netcard": netcard, + "netmask": netmask, + "type": "ethernet", + } + ) winreg.CloseKey(hkeyinterface) winreg.CloseKey(hkeycon) winreg.CloseKey(hkeycard) @@ -107,13 +117,17 @@ def get_windows_interfaces(): netmask = network_config.IPSubnet[0] break npf_interface = "\\Device\\NPF_{guid}".format(guid=adapter.GUID) - interfaces.append({"id": npf_interface, - "name": adapter.NetConnectionID, - "ip_address": ip_address, - "mac_address": adapter.MACAddress, - "netcard": adapter.name, - "netmask": netmask, - "type": "ethernet"}) + interfaces.append( + { + "id": npf_interface, + "name": adapter.NetConnectionID, + "ip_address": ip_address, + "mac_address": adapter.MACAddress, + "netcard": adapter.name, + "netmask": netmask, + "type": "ethernet", + } + ) except (AttributeError, pywintypes.com_error): log.warning("Could not use the COM service to retrieve interface info, trying using the registry...") return _get_windows_interfaces_from_registry() @@ -152,11 +166,12 @@ def is_interface_up(interface): return False import fcntl + SIOCGIFFLAGS = 0x8913 try: with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: - result = fcntl.ioctl(s.fileno(), SIOCGIFFLAGS, interface + '\0' * 256) - flags, = struct.unpack('H', result[16:18]) + result = fcntl.ioctl(s.fileno(), SIOCGIFFLAGS, interface + "\0" * 256) + (flags,) = struct.unpack("H", result[16:18]) if flags & 1: # check if the up bit is set return True return False @@ -203,12 +218,16 @@ def interfaces(): if interface.startswith("tap"): # found no way to reliably detect a TAP interface interface_type = "tap" - results.append({"id": interface, - "name": interface, - "ip_address": ip_address, - "netmask": netmask, - "mac_address": mac_address, - "type": interface_type}) + results.append( + { + "id": interface, + "name": interface, + "ip_address": ip_address, + "netmask": netmask, + "mac_address": mac_address, + "type": interface_type, + } + ) else: try: service_installed = True @@ -217,7 +236,9 @@ def interfaces(): else: results = get_windows_interfaces() except ImportError: - message = "pywin32 module is not installed, please install it on the server to get the available interface names" + message = ( + "pywin32 module is not installed, please install it on the server to get the available interface names" + ) raise ComputeError(message) except Exception as e: log.error(f"uncaught exception {type(e)}", exc_info=1) @@ -229,12 +250,25 @@ def interfaces(): # This interface have special behavior for result in results: result["special"] = False - for special_interface in ("lo", "vmnet", "vboxnet", "docker", "lxcbr", - "virbr", "ovs-system", "veth", "fw", "p2p", - "bridge", "vmware", "virtualbox", "gns3"): + for special_interface in ( + "lo", + "vmnet", + "vboxnet", + "docker", + "lxcbr", + "virbr", + "ovs-system", + "veth", + "fw", + "p2p", + "bridge", + "vmware", + "virtualbox", + "gns3", + ): if result["name"].lower().startswith(special_interface): result["special"] = True - for special_interface in ("-nic"): + for special_interface in "-nic": if result["name"].lower().endswith(special_interface): result["special"] = True return results diff --git a/gns3server/utils/notification_queue.py b/gns3server/utils/notification_queue.py index 1afcda84..e5371b0a 100644 --- a/gns3server/utils/notification_queue.py +++ b/gns3server/utils/notification_queue.py @@ -23,6 +23,7 @@ from gns3server.utils.cpu_percent import CpuPercent from gns3server.utils.path import get_default_project_directory import logging + log = logging.getLogger(__name__) @@ -55,9 +56,7 @@ class NotificationQueue(asyncio.Queue): """ Return the content of the ping notification """ - msg = {"cpu_usage_percent": 0, - "memory_usage_percent": 0, - "disk_usage_percent": 0} + msg = {"cpu_usage_percent": 0, "memory_usage_percent": 0, "disk_usage_percent": 0} # Non blocking call in order to get cpu usage. First call will return 0 try: msg["cpu_usage_percent"] = CpuPercent.get(interval=None) diff --git a/gns3server/utils/picture.py b/gns3server/utils/picture.py index 4eb549b0..61df743e 100644 --- a/gns3server/utils/picture.py +++ b/gns3server/utils/picture.py @@ -47,7 +47,7 @@ def get_size(data, default_width=0, default_height=0): size = len(data) # handle GIFs - if size >= 10 and data[:6] in (b'GIF87a', b'GIF89a'): + if size >= 10 and data[:6] in (b"GIF87a", b"GIF89a"): # Check to see if content_type is correct try: width, height = struct.unpack("= 24 and data.startswith(b'\211PNG\r\n\032\n') and data[12:16] == b'IHDR': + elif size >= 24 and data.startswith(b"\211PNG\r\n\032\n") and data[12:16] == b"IHDR": try: width, height = struct.unpack(">LL", data[16:24]) filetype = "png" except struct.error: raise ValueError("Invalid PNG file") # Maybe this is for an older PNG version. - elif size >= 16 and data.startswith(b'\211PNG\r\n\032\n'): + elif size >= 16 and data.startswith(b"\211PNG\r\n\032\n"): # Check to see if we have the right content type try: width, height = struct.unpack(">LL", data[8:16]) @@ -70,29 +70,29 @@ def get_size(data, default_width=0, default_height=0): except struct.error: raise ValueError("Invalid PNG file") # handle JPEGs - elif size >= 2 and data.startswith(b'\377\330'): + elif size >= 2 and data.startswith(b"\377\330"): try: # Not very efficient to copy data to a buffer fhandle = io.BytesIO(data) size = 2 ftype = 0 - while not 0xc0 <= ftype <= 0xcf: + while not 0xC0 <= ftype <= 0xCF: fhandle.seek(size, 1) byte = fhandle.read(1) - while ord(byte) == 0xff: + while ord(byte) == 0xFF: byte = fhandle.read(1) ftype = ord(byte) - size = struct.unpack('>H', fhandle.read(2))[0] - 2 + size = struct.unpack(">H", fhandle.read(2))[0] - 2 # We are at a SOFn block fhandle.seek(1, 1) # Skip `precision' byte. - height, width = struct.unpack('>HH', fhandle.read(4)) + height, width = struct.unpack(">HH", fhandle.read(4)) filetype = "jpg" except struct.error: raise ValueError("Invalid JPEG file") # End of https://github.com/shibukawa/imagesize_py # handle SVG - elif size >= 10 and (data.startswith(b'= 10 and (data.startswith(b"= 3: if size[-2:] in conversion_table: diff --git a/gns3server/utils/vmnet.py b/gns3server/utils/vmnet.py index 0f96b325..c9340fc8 100644 --- a/gns3server/utils/vmnet.py +++ b/gns3server/utils/vmnet.py @@ -47,7 +47,7 @@ def parse_networking_file(): version = f.readline() for line in f.read().splitlines(): try: - _, key, value = line.split(' ', 3) + _, key, value = line.split(" ", 3) key = key.strip() value = value.strip() pairs[key] = value @@ -92,19 +92,20 @@ def parse_vmnet_range(start, end): """ class Range(argparse.Action): - def __call__(self, parser, args, values, option_string=None): if len(values) != 2: raise argparse.ArgumentTypeError("vmnet range must consist of 2 numbers") if not start <= values[0] or not values[1] <= end: raise argparse.ArgumentTypeError(f"vmnet range must be between {start} and {end}") setattr(args, self.dest, values) + return Range def find_vnetlib_registry(regkey): import winreg + try: # default path not used, let's look in the registry hkey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, regkey) @@ -162,6 +163,7 @@ def vmnet_windows(args, vmnet_range_start, vmnet_range_end): if not vnetlib_path: raise SystemExit("VMware is not installed, could not find vnetlib.exe") from win32com.shell import shell + if not shell.IsUserAnAdmin(): raise SystemExit("You must run this script as an administrator") @@ -243,9 +245,15 @@ def main(): Entry point for the VMNET tool. """ - parser = argparse.ArgumentParser(description='%(prog)s add/remove vmnet interfaces') - parser.add_argument('-r', "--range", nargs='+', action=parse_vmnet_range(1, 255), - type=int, help=f"vmnet range to add (default is {DEFAULT_RANGE[0]} {DEFAULT_RANGE[1]})") + parser = argparse.ArgumentParser(description="%(prog)s add/remove vmnet interfaces") + parser.add_argument( + "-r", + "--range", + nargs="+", + action=parse_vmnet_range(1, 255), + type=int, + help=f"vmnet range to add (default is {DEFAULT_RANGE[0]} {DEFAULT_RANGE[1]})", + ) parser.add_argument("-C", "--clean", action="store_true", help="remove all vmnets excepting vmnet1 and vmnet8") parser.add_argument("-l", "--list", action="store_true", help="list all existing vmnets (UNIX only)") @@ -264,5 +272,6 @@ def main(): else: vmnet_unix(args, vmnet_range[0], vmnet_range[1]) -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/gns3server/utils/windows_loopback.py b/gns3server/utils/windows_loopback.py index 66239ff2..f6f79b25 100644 --- a/gns3server/utils/windows_loopback.py +++ b/gns3server/utils/windows_loopback.py @@ -33,7 +33,6 @@ def parse_add_loopback(): """ class Add(argparse.Action): - def __call__(self, parser, args, values, option_string=None): try: ipaddress.IPv4Interface(f"{values[1]}/{values[2]}") @@ -42,6 +41,7 @@ def parse_add_loopback(): except ipaddress.NetmaskValueError as e: raise argparse.ArgumentTypeError(f"Invalid subnet mask: {e}") setattr(args, self.dest, values) + return Add @@ -70,8 +70,8 @@ def add_loopback(devcon_path, name, ip_address, netmask): elif retcode != 0: print('Error while configuring IP/Subnet mask on "{}"') - #FIXME: support gateway? - #network_config.SetGateways(DefaultIPGateway=[""]) + # FIXME: support gateway? + # network_config.SetGateways(DefaultIPGateway=[""]) break # restart winpcap/npcap services to take the new adapter into account @@ -106,8 +106,8 @@ def main(): Entry point for the Windows loopback tool. """ - parser = argparse.ArgumentParser(description='%(prog)s add/remove Windows loopback adapters') - parser.add_argument('-a', "--add", nargs=3, action=parse_add_loopback(), help="add a Windows loopback adapter") + parser = argparse.ArgumentParser(description="%(prog)s add/remove Windows loopback adapters") + parser.add_argument("-a", "--add", nargs=3, action=parse_add_loopback(), help="add a Windows loopback adapter") parser.add_argument("-r", "--remove", action="store", help="remove a Windows loopback adapter") try: args = parser.parse_args() @@ -120,6 +120,7 @@ def main(): raise SystemExit("Could not find devcon.exe") from win32com.shell import shell + if not shell.IsUserAnAdmin(): raise SystemExit("You must run this script as an administrator") @@ -132,5 +133,6 @@ def main(): print(e) os.system("pause") -if __name__ == '__main__': + +if __name__ == "__main__": main() diff --git a/gns3server/version.py b/gns3server/version.py index ebe058ef..57f5e599 100644 --- a/gns3server/version.py +++ b/gns3server/version.py @@ -29,6 +29,7 @@ if "dev" in __version__: try: import os import subprocess + if os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".git")): r = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode().strip() __version__ = f"{__version__}-{r}"