mirror of
https://github.com/GNS3/gns3-server
synced 2025-01-07 06:30:56 +00:00
Merge remote-tracking branch 'origin/3.0' into gh-pages
This commit is contained in:
commit
aed30d4eb4
152
CHANGELOG
152
CHANGELOG
@ -1,5 +1,157 @@
|
||||
# Change Log
|
||||
|
||||
## 3.0.0a1 04/08/2022
|
||||
|
||||
* Bundle gns3-web-ui v3.0.0a1
|
||||
* Use default symbol theme if none is provided when loading appliances
|
||||
* Allow default symbol theme to be configured in config file
|
||||
* Optionally allow Qemu raw images
|
||||
* Ignore image detection for IOU user libraries in image directory
|
||||
* Don't show optional token param in API docs
|
||||
* Fix check for 32-bit in ELF header
|
||||
* Checks for valid hostname on server side for Dynamips, IOU, Qemu and Docker nodes
|
||||
* Only check files (not directories) when looking for new images on file system.
|
||||
* Support user defined loader/libraries to run IOU
|
||||
* Make 'vendor_url' and 'maintainer_email' optional for template validation.
|
||||
* Allow auth token to be passed as a URL param
|
||||
* HTTP middleware create issues when streaming project archive
|
||||
* Add zstandard compression support for project export
|
||||
* Make sure that the temporary image file is removed after uploading an image
|
||||
* Do not cache to md5sum file in some situations
|
||||
* Detect new images added to the default image directory. * Images can be present before the server starts or while it is running * Images are recorded in the database
|
||||
* Support delete Qemu disk image from API Return the real disk image name in the 'hdx_disk_image_backed' property for Qemu VMs
|
||||
* Handle creating Qemu disk images and resizing
|
||||
* Finish to clean up local setting usage. Ref #1460
|
||||
* "Local" command line parameter is only for stopping a server that has been started by the desktop GUI
|
||||
* Fix AsyncSession handling after breaking changes in FastAPI 0.74.0 See https://github.com/tiangolo/fastapi/releases/tag/0.74.0 for details.
|
||||
* Detect image type instead of requesting it from user
|
||||
* Drop Python 3.6 support and require Python >= 3.7
|
||||
* Drop Windows support
|
||||
* Add connect endpoint for computes Param to connect to compute after creation Report compute unauthorized HTTP errors to client
|
||||
* Replace CORS origins by origin regex
|
||||
* Change default config settings
|
||||
* Update Docker image for tests
|
||||
* Fix calls to static methods in server.py
|
||||
* Do not require the local server param to open a .gns3 file. Fixes https://github.com/GNS3/gns3-gui/issues/2421 Ref #1460
|
||||
* Do not automatically install appliance after uploading image
|
||||
* Ignore OSError when closing websocket
|
||||
* Allow empty compute_id. Ref #1657
|
||||
* Secure controller to compute communication using HTTP basic authentication
|
||||
* Secure websocket endpoints
|
||||
* Fix issue when updating a template
|
||||
* Allocate compute when compute_id is unset
|
||||
* Return the current controller hostname/IP from any compute
|
||||
* Remove Qemu legacy networking support
|
||||
* Add a custom version to an appliance
|
||||
* Appliance management refactoring: * Install an appliance based on selected version * Each template have unique name and version * Allow to download an appliance file
|
||||
* Finalize image management refactoring and auto install appliance if possible
|
||||
* Sqlite doesn't allow BigInteger to be used as an primary key with autoincrement
|
||||
* Add isolate and unisolate endpoints. Ref https://github.com/GNS3/gns3-gui/issues/3190
|
||||
* Small db tables adjustments
|
||||
* Add index for "name" field in role table
|
||||
* Allow images to be stored in subdirs and used by templates.
|
||||
* Option to prune images when deleting template.
|
||||
* Associate images when creating or updating a template.
|
||||
* Add prune images endpoint. Use many-to-many relationship between images and templates.
|
||||
* Check if user has the right to add a permission
|
||||
* Only use the necessary HTTP methods for default permissions
|
||||
* Add /permissions/prune to delete orphaned permissions
|
||||
* Check a permission matches an existing route before it is allowed to be created.
|
||||
* Remove busybox and copy system busybox in setup.py
|
||||
* Save image size + start to automatic template creation based on image checksum.
|
||||
* Upgrade dependencies
|
||||
* Fix "-machine accel=tcg" check
|
||||
* Allow logged in user to change some of its data. Administrators can lock users using the `is_active` field.
|
||||
* Symbols endpoints (except upload) don't require authentication.
|
||||
* Update to the udhcpc wrapper script. Ref #1890
|
||||
* Fix link style merge
|
||||
* Start refactoring for images management
|
||||
* Allow controller to be reloaded using the API. Fixes #1743
|
||||
* Use a stock BusyBox for the Docker Integration
|
||||
* Move "/{project_id}/templates/{template_id}" endpoint.
|
||||
* Add last login info for users.
|
||||
* Change RBAC field names from builtin to is_builtin
|
||||
* Add user permissions + RBAC tests.
|
||||
* Add description for user permission.
|
||||
* Save/restore appliances Etag.
|
||||
* Allow to set the initial super admin username / password in server config file. Ref #1857
|
||||
* Require authentication for get_user_memberships endpoint.
|
||||
* Change method to prevent forbidden directory traversal. Ref #1894
|
||||
* Add user groups support.
|
||||
* Add delete cascade on foreign keys for appliance table
|
||||
* Enable SQL foreign key support for SQLite
|
||||
* Add missing CORS origins.
|
||||
* Show topology path when check topology schema fails.
|
||||
* Protect controller notification endpoints. Ref #1888 (WebSocket endpoint is not secured, it takes an optional token).
|
||||
* Use uuid5 to create new compute_id. Fixes #1641 #1887
|
||||
* Protect the API and add alternative authentication endpoint.
|
||||
* Secure users API and handle manual password recovery.
|
||||
* Add default super admin account in controller db.
|
||||
* Complete type annotations for API endpoints.
|
||||
* Detect the app is exiting and avoid reconnecting to computes.
|
||||
* Move schemas between compute and controller subpackages
|
||||
* Remove traceng code.
|
||||
* Move error responses to API routers.
|
||||
* Wait for local compute to be started. Don't reconnect to local compute when server is being stopped.
|
||||
* Rename ssl and auth configuration file settings. Add enable SSL config validator. Strict configuration file validation: any error will prevent the server to start. Core server logic moved to a Server class.
|
||||
* Generate new config for each test. Fixes tests.
|
||||
* Use Pydantic to validate the server config file.
|
||||
* Add symbol dimensions endpoint and SSL support for packet capture with remote HTTPS server.
|
||||
* Save computes to database
|
||||
* Generate a new list in template schema defaults.
|
||||
* Generate JWT secret key if none is configured in the config file. Change location of the database.
|
||||
* User authentication with tests.
|
||||
* Refactor tests and start work on database integration.
|
||||
* Move endpoints to routes & preparations to use a database.
|
||||
* Providing the path to create a project is now deprecated.
|
||||
* Add back script to create a self-signed SSL certificate.
|
||||
* Make sure all HTTP exceptions return JSON with a "message" field instead of "detail"
|
||||
* Make Swagger Ui the default for API documentation
|
||||
* Move to version 3 of the REST API. Rename packet capture endpoints.
|
||||
* Use pydantic for data validation (instead of jsonschema) Fix/improve various pydantic schema models.
|
||||
* Automate API documentation publishing.
|
||||
* Publish API documentation generated by FastAPI.
|
||||
* Overwrite uvicorn loggers.
|
||||
* Do not automatically connect to local compute.
|
||||
* Add HTTP client to reuse the aiohttp session where needed. Remove unnecessary aiohttp exceptions.
|
||||
* Warn not to use the private compute API. Fixes #1593.
|
||||
* Migrate PCAP streaming code to work with FastAPI.
|
||||
* Refactor WebSocket console code to work with FastAPI. Fix endpoint routes.
|
||||
* Use dependencies and group common HTTP responses in endpoints.
|
||||
* Migration to FastAPI
|
||||
* Prioritize the config disk over HD-D for Qemu VMs. Fixes https://github.com/GNS3/gns3-gui/issues/3036
|
||||
* Create config disk property false by default for Qemu templates
|
||||
* Set default disk interface type to "none". Fail-safe: use "ide" if an image is set but no interface type is configured. Use the HDA disk interface type if none has been configured for HDD.
|
||||
* Add explicit option to automatically create or not the config disk. Off by default.
|
||||
* Auxiliary console support for Qemu. Ref #2873 Improvements for auxiliary console support for Docker and Dynamips.
|
||||
* Fix AUX console not allocated for Dynamips IOS routers.
|
||||
* Disallow to rename a running node. Fixes https://github.com/GNS3/gns3-gui/issues/2499
|
||||
* Support to reset all console connections. Ref https://github.com/GNS3/gns3-server/issues/1619
|
||||
* Support to reset links. Fixes https://github.com/GNS3/gns3-server/issues/1620
|
||||
* Add maxcpus property for Qemu VMs. Ref #1674
|
||||
* QEMU config disk support
|
||||
* Read folder structure correctly for custom symbols. Fixes https://github.com/GNS3/gns3-gui/issues/2856
|
||||
* Add total RAM, CPUs and disk size to servers summary as well as disk usage in percent. Fixes https://github.com/GNS3/gns3-server/issues/1532
|
||||
* Resource constraints for Docker VMs.
|
||||
* Update IOUtools. Ref #1627
|
||||
* Use parent directory as working directory for project duplication and snapshots. Fixes https://github.com/GNS3/gns3-gui/issues/2909
|
||||
* Support for "usage" for "Cloud" nodes. Fixes https://github.com/GNS3/gns3-gui/issues/2887 Allow "usage" for all builtin nodes (not exposed in Ui).
|
||||
|
||||
## 2.2.33.1 21/06/2022
|
||||
|
||||
* Add missing file for web-ui v2.2.33
|
||||
|
||||
## 2.2.33 20/06/2022
|
||||
|
||||
* Release web-ui v2.2.33
|
||||
* Upgrade sentry-sdk and psutil
|
||||
* Remove parameter "Name" not useful to create a Docker container
|
||||
* Support to reset all console connections. Ref https://github.com/GNS3/gns3-server/issues/1619
|
||||
* Config option to disable built-in templates
|
||||
* Add hostname entry to sample network config for Docker nodes. Fixes #2039
|
||||
* Run Xtigervnc with MIT-SHM extension disabled for Docker VNC console support. Fixes #2071
|
||||
* Added OpenRC init script
|
||||
|
||||
## 2.2.32 27/04/2022
|
||||
|
||||
* Docker: load custom interface files from /etc/network/interfaces (commented by default). Ref #2052
|
||||
|
@ -49,6 +49,11 @@ symbols_path = /home/gns3/GNS3/symbols
|
||||
; Path where custom configs are stored
|
||||
configs_path = /home/gns3/GNS3/configs
|
||||
|
||||
; Default symbol theme
|
||||
; Currently available themes are "Classic", Affinity-square-blue", "Affinity-square-red"
|
||||
; "Affinity-square-gray", "Affinity-circle-blue", "Affinity-circle-red" and "Affinity-circle-gray"
|
||||
default_symbol_theme = Affinity-square-blue
|
||||
|
||||
; Option to automatically send crash reports to the GNS3 team
|
||||
report_errors = True
|
||||
|
||||
@ -85,6 +90,9 @@ allowed_interfaces = eth0,eth1,virbr0
|
||||
; Default is virbr0 on Linux (requires libvirt) and vmnet8 for other platforms (requires VMware)
|
||||
default_nat_interface = vmnet10
|
||||
|
||||
; Enable the built-in templates
|
||||
enable_builtin_templates = True
|
||||
|
||||
[VPCS]
|
||||
; VPCS executable location, default: search in PATH
|
||||
;vpcs_path = vpcs
|
||||
|
@ -67,7 +67,7 @@ compute_api.state.controller_host = None
|
||||
|
||||
|
||||
@compute_api.exception_handler(ComputeError)
|
||||
async def controller_error_handler(request: Request, exc: ComputeError):
|
||||
async def compute_error_handler(request: Request, exc: ComputeError):
|
||||
log.error(f"Compute error: {exc}")
|
||||
return JSONResponse(
|
||||
status_code=409,
|
||||
@ -76,7 +76,7 @@ async def controller_error_handler(request: Request, exc: ComputeError):
|
||||
|
||||
|
||||
@compute_api.exception_handler(ComputeTimeoutError)
|
||||
async def controller_timeout_error_handler(request: Request, exc: ComputeTimeoutError):
|
||||
async def compute_timeout_error_handler(request: Request, exc: ComputeTimeoutError):
|
||||
log.error(f"Compute timeout error: {exc}")
|
||||
return JSONResponse(
|
||||
status_code=408,
|
||||
@ -85,7 +85,7 @@ async def controller_timeout_error_handler(request: Request, exc: ComputeTimeout
|
||||
|
||||
|
||||
@compute_api.exception_handler(ComputeUnauthorizedError)
|
||||
async def controller_unauthorized_error_handler(request: Request, exc: ComputeUnauthorizedError):
|
||||
async def compute_unauthorized_error_handler(request: Request, exc: ComputeUnauthorizedError):
|
||||
log.error(f"Compute unauthorized error: {exc}")
|
||||
return JSONResponse(
|
||||
status_code=401,
|
||||
@ -94,7 +94,7 @@ async def controller_unauthorized_error_handler(request: Request, exc: ComputeUn
|
||||
|
||||
|
||||
@compute_api.exception_handler(ComputeForbiddenError)
|
||||
async def controller_forbidden_error_handler(request: Request, exc: ComputeForbiddenError):
|
||||
async def compute_forbidden_error_handler(request: Request, exc: ComputeForbiddenError):
|
||||
log.error(f"Compute forbidden error: {exc}")
|
||||
return JSONResponse(
|
||||
status_code=403,
|
||||
@ -103,7 +103,7 @@ async def controller_forbidden_error_handler(request: Request, exc: ComputeForbi
|
||||
|
||||
|
||||
@compute_api.exception_handler(ComputeNotFoundError)
|
||||
async def controller_not_found_error_handler(request: Request, exc: ComputeNotFoundError):
|
||||
async def compute_not_found_error_handler(request: Request, exc: ComputeNotFoundError):
|
||||
log.error(f"Compute not found error: {exc}")
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
@ -112,7 +112,7 @@ async def controller_not_found_error_handler(request: Request, exc: ComputeNotFo
|
||||
|
||||
|
||||
@compute_api.exception_handler(GNS3VMError)
|
||||
async def controller_error_handler(request: Request, exc: GNS3VMError):
|
||||
async def compute_gns3vm_error_handler(request: Request, exc: GNS3VMError):
|
||||
log.error(f"Compute GNS3 VM error: {exc}")
|
||||
return JSONResponse(
|
||||
status_code=409,
|
||||
|
@ -109,43 +109,42 @@ async def update_atm_switch(
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_atm_switch_node(node: ATMSwitch = Depends(dep_node)) -> Response:
|
||||
async def delete_atm_switch_node(node: ATMSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete an ATM switch node.
|
||||
"""
|
||||
|
||||
await Dynamips.instance().delete_node(node.id)
|
||||
return Response(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)) -> Response:
|
||||
def start_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start an ATM switch node.
|
||||
This endpoint results in no action since ATM switch nodes are always on.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def stop_atm_switch(node: ATMSwitch = Depends(dep_node)) -> Response:
|
||||
def stop_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop an ATM switch node.
|
||||
This endpoint results in no action since ATM switch nodes are always on.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)) -> Response:
|
||||
def suspend_atm_switch(node: ATMSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend an ATM switch node.
|
||||
This endpoint results in no action since ATM switch nodes are always on.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -171,7 +170,7 @@ async def create_nio(
|
||||
|
||||
|
||||
@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)) -> Response:
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the switch is always 0.
|
||||
@ -179,7 +178,6 @@ async def delete_nio(adapter_number: int, port_number: int, node: ATMSwitch = De
|
||||
|
||||
nio = await node.remove_nio(port_number)
|
||||
await nio.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
@ -209,14 +207,13 @@ async def stop_capture(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: ATMSwitch = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
await node.stop_capture(port_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
|
@ -99,43 +99,41 @@ def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_cloud(node: Cloud = Depends(dep_node)) -> Response:
|
||||
async def delete_cloud(node: Cloud = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a cloud node.
|
||||
"""
|
||||
|
||||
await Builtin.instance().delete_node(node.id)
|
||||
return Response(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)) -> Response:
|
||||
async def start_cloud(node: Cloud = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a cloud node.
|
||||
"""
|
||||
|
||||
await node.start()
|
||||
return Response(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)) -> Response:
|
||||
async def stop_cloud(node: Cloud = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a cloud node.
|
||||
This endpoint results in no action since cloud nodes cannot be stopped.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def suspend_cloud(node: Cloud = Depends(dep_node)) -> Response:
|
||||
async def suspend_cloud(node: Cloud = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend a cloud node.
|
||||
This endpoint results in no action since cloud nodes cannot be suspended.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -190,14 +188,13 @@ async def delete_cloud_nio(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: Cloud = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
await node.remove_nio(port_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
@ -226,14 +223,13 @@ async def stop_cloud_capture(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: Cloud = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
await node.stop_capture(port_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/pcap")
|
||||
|
@ -132,73 +132,66 @@ async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = D
|
||||
|
||||
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_docker_node(node: DockerVM = Depends(dep_node)) -> Response:
|
||||
async def start_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a Docker node.
|
||||
"""
|
||||
|
||||
await node.start()
|
||||
return Response(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)) -> Response:
|
||||
async def stop_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a Docker node.
|
||||
"""
|
||||
|
||||
await node.stop()
|
||||
return Response(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)) -> Response:
|
||||
async def suspend_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend a Docker node.
|
||||
"""
|
||||
|
||||
await node.pause()
|
||||
return Response(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)) -> Response:
|
||||
async def reload_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Reload a Docker node.
|
||||
"""
|
||||
|
||||
await node.restart()
|
||||
return Response(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)) -> Response:
|
||||
async def pause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Pause a Docker node.
|
||||
"""
|
||||
|
||||
await node.pause()
|
||||
return Response(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)) -> Response:
|
||||
async def unpause_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Unpause a Docker node.
|
||||
"""
|
||||
|
||||
await node.unpause()
|
||||
return Response(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)) -> Response:
|
||||
async def delete_docker_node(node: DockerVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a Docker node.
|
||||
"""
|
||||
|
||||
await node.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.Docker, status_code=status.HTTP_201_CREATED)
|
||||
@ -257,14 +250,13 @@ async def delete_docker_node_nio(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
node: DockerVM = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The port number on the Docker node is always 0.
|
||||
"""
|
||||
|
||||
await node.adapter_remove_nio_binding(adapter_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
@ -292,14 +284,13 @@ async def stop_docker_node_capture(
|
||||
adapter_number: int,
|
||||
port_number: int,
|
||||
node: DockerVM = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The port number on the Docker node is always 0.
|
||||
"""
|
||||
|
||||
await node.stop_capture(adapter_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
@ -328,7 +319,6 @@ async def console_ws(websocket: WebSocket, node: DockerVM = Depends(dep_node)) -
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: DockerVM = Depends(dep_node)) -> Response:
|
||||
async def reset_console(node: DockerVM = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -19,7 +19,6 @@ API routes for Dynamips nodes.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from fastapi import APIRouter, WebSocket, Depends, Response, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
@ -29,7 +28,6 @@ from uuid import UUID
|
||||
|
||||
from gns3server.compute.dynamips import Dynamips
|
||||
from gns3server.compute.dynamips.nodes.router import Router
|
||||
from gns3server.compute.dynamips.dynamips_error import DynamipsError
|
||||
from gns3server import schemas
|
||||
|
||||
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"}}
|
||||
@ -105,17 +103,16 @@ async def update_router(node_data: schemas.DynamipsUpdate, node: Router = Depend
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_router(node: Router = Depends(dep_node)) -> Response:
|
||||
async def delete_router(node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a Dynamips router.
|
||||
"""
|
||||
|
||||
await Dynamips.instance().delete_node(node.id)
|
||||
return Response(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)) -> Response:
|
||||
async def start_router(node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a Dynamips router.
|
||||
"""
|
||||
@ -125,44 +122,39 @@ async def start_router(node: Router = Depends(dep_node)) -> Response:
|
||||
except GeneratorExit:
|
||||
pass
|
||||
await node.start()
|
||||
return Response(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)) -> Response:
|
||||
async def stop_router(node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a Dynamips router.
|
||||
"""
|
||||
|
||||
await node.stop()
|
||||
return Response(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)) -> Response:
|
||||
async def suspend_router(node: Router = Depends(dep_node)) -> None:
|
||||
|
||||
await node.suspend()
|
||||
return Response(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)) -> Response:
|
||||
async def resume_router(node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Resume a suspended Dynamips router.
|
||||
"""
|
||||
|
||||
await node.resume()
|
||||
return Response(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)) -> Response:
|
||||
async def reload_router(node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Reload a suspended Dynamips router.
|
||||
"""
|
||||
|
||||
await node.reload()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -208,14 +200,13 @@ async def update_nio(
|
||||
|
||||
|
||||
@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)) -> Response:
|
||||
async def delete_nio(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
"""
|
||||
|
||||
nio = await node.slot_remove_nio_binding(adapter_number, port_number)
|
||||
await nio.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
@ -237,13 +228,12 @@ async def start_capture(
|
||||
@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)) -> Response:
|
||||
async def stop_capture(adapter_number: int, port_number: int, node: Router = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
"""
|
||||
|
||||
await node.stop_capture(adapter_number, port_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
@ -301,7 +291,6 @@ async def console_ws(websocket: WebSocket, node: Router = Depends(dep_node)) ->
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: Router = Depends(dep_node)) -> Response:
|
||||
async def reset_console(node: Router = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -108,43 +108,42 @@ async def update_ethernet_hub(
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> Response:
|
||||
async def delete_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete an Ethernet hub.
|
||||
"""
|
||||
|
||||
await Dynamips.instance().delete_node(node.id)
|
||||
return Response(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)) -> Response:
|
||||
def start_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start an Ethernet hub.
|
||||
This endpoint results in no action since Ethernet hub nodes are always on.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> Response:
|
||||
def stop_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop an Ethernet hub.
|
||||
This endpoint results in no action since Ethernet hub nodes are always on.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> Response:
|
||||
def suspend_ethernet_hub(node: EthernetHub = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend an Ethernet hub.
|
||||
This endpoint results in no action since Ethernet hub nodes are always on.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -175,7 +174,7 @@ async def delete_nio(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: EthernetHub = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the hub is always 0.
|
||||
@ -183,7 +182,6 @@ async def delete_nio(
|
||||
|
||||
nio = await node.remove_nio(port_number)
|
||||
await nio.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
@ -212,14 +210,13 @@ async def stop_capture(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: EthernetHub = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the hub is always 0.
|
||||
"""
|
||||
|
||||
await node.stop_capture(port_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
|
@ -112,43 +112,42 @@ async def update_ethernet_switch(
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> Response:
|
||||
async def delete_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete an Ethernet switch.
|
||||
"""
|
||||
|
||||
await Dynamips.instance().delete_node(node.id)
|
||||
return Response(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)) -> Response:
|
||||
def start_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start an Ethernet switch.
|
||||
This endpoint results in no action since Ethernet switch nodes are always on.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> Response:
|
||||
def stop_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop an Ethernet switch.
|
||||
This endpoint results in no action since Ethernet switch nodes are always on.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> Response:
|
||||
def suspend_ethernet_switch(node: EthernetSwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend an Ethernet switch.
|
||||
This endpoint results in no action since Ethernet switch nodes are always on.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -175,7 +174,7 @@ async def delete_nio(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: EthernetSwitch = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the switch is always 0.
|
||||
@ -183,7 +182,6 @@ async def delete_nio(
|
||||
|
||||
nio = await node.remove_nio(port_number)
|
||||
await nio.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
@ -212,14 +210,13 @@ async def stop_capture(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: EthernetSwitch = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
await node.stop_capture(port_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
|
@ -112,43 +112,42 @@ async def update_frame_relay_switch(
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> Response:
|
||||
async def delete_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a Frame Relay switch node.
|
||||
"""
|
||||
|
||||
await Dynamips.instance().delete_node(node.id)
|
||||
return Response(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)) -> Response:
|
||||
def start_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a Frame Relay switch node.
|
||||
This endpoint results in no action since Frame Relay switch nodes are always on.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> Response:
|
||||
def stop_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a Frame Relay switch node.
|
||||
This endpoint results in no action since Frame Relay switch nodes are always on.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> Response:
|
||||
def suspend_frame_relay_switch(node: FrameRelaySwitch = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend a Frame Relay switch node.
|
||||
This endpoint results in no action since Frame Relay switch nodes are always on.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -179,7 +178,7 @@ async def delete_nio(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: FrameRelaySwitch = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the switch is always 0.
|
||||
@ -187,7 +186,6 @@ async def delete_nio(
|
||||
|
||||
nio = await node.remove_nio(port_number)
|
||||
await nio.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
@ -216,14 +214,13 @@ async def stop_capture(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: FrameRelaySwitch = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the switch is always 0.
|
||||
"""
|
||||
|
||||
await node.stop_capture(port_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
|
@ -54,14 +54,13 @@ async def get_dynamips_images() -> List[str]:
|
||||
|
||||
|
||||
@router.post("/dynamips/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def upload_dynamips_image(filename: str, request: Request) -> Response:
|
||||
async def upload_dynamips_image(filename: str, request: Request) -> None:
|
||||
"""
|
||||
Upload a Dynamips IOS image.
|
||||
"""
|
||||
|
||||
dynamips_manager = Dynamips.instance()
|
||||
await dynamips_manager.write_image(urllib.parse.unquote(filename), request.stream())
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/dynamips/images/{filename:path}")
|
||||
@ -96,14 +95,13 @@ async def get_iou_images() -> List[str]:
|
||||
|
||||
|
||||
@router.post("/iou/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def upload_iou_image(filename: str, request: Request) -> Response:
|
||||
async def upload_iou_image(filename: str, request: Request) -> None:
|
||||
"""
|
||||
Upload an IOU image.
|
||||
"""
|
||||
|
||||
iou_manager = IOU.instance()
|
||||
await iou_manager.write_image(urllib.parse.unquote(filename), request.stream())
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/iou/images/{filename:path}")
|
||||
@ -134,11 +132,10 @@ async def get_qemu_images() -> List[str]:
|
||||
|
||||
|
||||
@router.post("/qemu/images/{filename:path}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def upload_qemu_image(filename: str, request: Request) -> Response:
|
||||
async def upload_qemu_image(filename: str, request: Request) -> None:
|
||||
|
||||
qemu_manager = Qemu.instance()
|
||||
await qemu_manager.write_image(urllib.parse.unquote(filename), request.stream())
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/qemu/images/{filename:path}")
|
||||
|
@ -20,7 +20,7 @@ API routes for IOU nodes.
|
||||
|
||||
import os
|
||||
|
||||
from fastapi import APIRouter, WebSocket, Depends, Body, Response, status
|
||||
from fastapi import APIRouter, WebSocket, Depends, Body, status
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
from fastapi.responses import StreamingResponse
|
||||
from typing import Union
|
||||
@ -113,13 +113,12 @@ async def update_iou_node(node_data: schemas.IOUUpdate, node: IOUVM = Depends(de
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> Response:
|
||||
async def delete_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete an IOU node.
|
||||
"""
|
||||
|
||||
await IOU.instance().delete_node(node.id)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.IOU, status_code=status.HTTP_201_CREATED)
|
||||
@ -136,7 +135,7 @@ async def duplicate_iou_node(
|
||||
|
||||
|
||||
@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)) -> Response:
|
||||
async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start an IOU node.
|
||||
"""
|
||||
@ -147,37 +146,34 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep
|
||||
setattr(node, name, value)
|
||||
|
||||
await node.start()
|
||||
return Response(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)) -> Response:
|
||||
async def stop_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop an IOU node.
|
||||
"""
|
||||
|
||||
await node.stop()
|
||||
return Response(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)) -> Response:
|
||||
def suspend_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend an IOU node.
|
||||
Does nothing since IOU doesn't support being suspended.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> Response:
|
||||
async def reload_iou_node(node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Reload an IOU node.
|
||||
"""
|
||||
|
||||
await node.reload()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -223,13 +219,12 @@ async def update_iou_node_nio(
|
||||
|
||||
|
||||
@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)) -> Response:
|
||||
async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
"""
|
||||
|
||||
await node.adapter_remove_nio_binding(adapter_number, port_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
@ -251,13 +246,12 @@ async def start_iou_node_capture(
|
||||
@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)) -> Response:
|
||||
async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
"""
|
||||
|
||||
await node.stop_capture(adapter_number, port_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
@ -285,7 +279,6 @@ async def console_ws(websocket: WebSocket, node: IOUVM = Depends(dep_node)) -> N
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: IOUVM = Depends(dep_node)) -> Response:
|
||||
async def reset_console(node: IOUVM = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -94,43 +94,41 @@ def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node))
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_nat_node(node: Nat = Depends(dep_node)) -> Response:
|
||||
async def delete_nat_node(node: Nat = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a cloud node.
|
||||
"""
|
||||
|
||||
await Builtin.instance().delete_node(node.id)
|
||||
return Response(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)) -> Response:
|
||||
async def start_nat_node(node: Nat = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a NAT node.
|
||||
"""
|
||||
|
||||
await node.start()
|
||||
return Response(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)) -> Response:
|
||||
async def stop_nat_node(node: Nat = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a NAT node.
|
||||
This endpoint results in no action since cloud nodes cannot be stopped.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/suspend", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def suspend_nat_node(node: Nat = Depends(dep_node)) -> Response:
|
||||
async def suspend_nat_node(node: Nat = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend a NAT node.
|
||||
This endpoint results in no action since NAT nodes cannot be suspended.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -185,14 +183,13 @@ async def delete_nat_node_nio(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: Nat = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Remove a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
await node.remove_nio(port_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
@ -221,14 +218,13 @@ async def stop_nat_node_capture(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: Nat = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the cloud is always 0.
|
||||
"""
|
||||
|
||||
await node.stop_capture(port_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
|
@ -103,7 +103,7 @@ def get_compute_project(project: Project = Depends(dep_project)) -> schemas.Proj
|
||||
|
||||
|
||||
@router.post("/projects/{project_id}/close", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def close_compute_project(project: Project = Depends(dep_project)) -> Response:
|
||||
async def close_compute_project(project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Close a project on the compute.
|
||||
"""
|
||||
@ -118,18 +118,16 @@ async def close_compute_project(project: Project = Depends(dep_project)) -> Resp
|
||||
pass
|
||||
else:
|
||||
log.warning("Skip project closing, another client is listening for project notifications")
|
||||
return Response(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)) -> Response:
|
||||
async def delete_compute_project(project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Delete project from the compute.
|
||||
"""
|
||||
|
||||
await project.delete()
|
||||
ProjectManager.instance().remove_project(project.id)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
# @Route.get(
|
||||
# r"/projects/{project_id}/notifications",
|
||||
@ -219,7 +217,7 @@ async def write_compute_project_file(
|
||||
file_path: str,
|
||||
request: Request,
|
||||
project: Project = Depends(dep_project)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
|
||||
file_path = urllib.parse.unquote(file_path)
|
||||
path = os.path.normpath(file_path)
|
||||
@ -243,5 +241,3 @@ async def write_compute_project_file(
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
except PermissionError:
|
||||
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN)
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
@ -104,13 +104,12 @@ async def update_qemu_node(node_data: schemas.QemuUpdate, node: QemuVM = Depends
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> Response:
|
||||
async def delete_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a Qemu node.
|
||||
"""
|
||||
|
||||
await Qemu.instance().delete_node(node.id)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.Qemu, status_code=status.HTTP_201_CREATED)
|
||||
@ -134,14 +133,13 @@ async def create_qemu_disk_image(
|
||||
disk_name: str,
|
||||
disk_data: schemas.QemuDiskImageCreate,
|
||||
node: QemuVM = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Create a Qemu disk image.
|
||||
"""
|
||||
|
||||
options = jsonable_encoder(disk_data, exclude_unset=True)
|
||||
await node.create_disk_image(disk_name, options)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.put(
|
||||
@ -152,14 +150,13 @@ async def update_qemu_disk_image(
|
||||
disk_name: str,
|
||||
disk_data: schemas.QemuDiskImageUpdate,
|
||||
node: QemuVM = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Update a Qemu disk image.
|
||||
"""
|
||||
|
||||
if disk_data.extend:
|
||||
await node.resize_disk_image(disk_name, disk_data.extend)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.delete(
|
||||
@ -169,63 +166,57 @@ async def update_qemu_disk_image(
|
||||
async def delete_qemu_disk_image(
|
||||
disk_name: str,
|
||||
node: QemuVM = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a Qemu disk image.
|
||||
"""
|
||||
|
||||
node.delete_disk_image(disk_name)
|
||||
return Response(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)) -> Response:
|
||||
async def start_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a Qemu node.
|
||||
"""
|
||||
|
||||
await node.start()
|
||||
return Response(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)) -> Response:
|
||||
async def stop_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a Qemu node.
|
||||
"""
|
||||
|
||||
await node.stop()
|
||||
return Response(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)) -> Response:
|
||||
async def reload_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Reload a Qemu node.
|
||||
"""
|
||||
|
||||
await node.reload()
|
||||
return Response(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)) -> Response:
|
||||
async def suspend_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend a Qemu node.
|
||||
"""
|
||||
|
||||
await node.suspend()
|
||||
return Response(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)) -> Response:
|
||||
async def resume_qemu_node(node: QemuVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Resume a Qemu node.
|
||||
"""
|
||||
|
||||
await node.resume()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -281,14 +272,13 @@ async def delete_qemu_node_nio(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: QemuVM = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The port number on the Qemu node is always 0.
|
||||
"""
|
||||
|
||||
await node.adapter_remove_nio_binding(adapter_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
@ -316,14 +306,13 @@ async def stop_qemu_node_capture(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: QemuVM = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The port number on the Qemu node is always 0.
|
||||
"""
|
||||
|
||||
await node.stop_capture(adapter_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
@ -351,7 +340,6 @@ async def console_ws(websocket: WebSocket, node: QemuVM = Depends(dep_node)) ->
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: QemuVM = Depends(dep_node)) -> Response:
|
||||
async def reset_console(node: QemuVM = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -137,63 +137,57 @@ async def update_virtualbox_node(
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> Response:
|
||||
async def delete_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a VirtualBox node.
|
||||
"""
|
||||
|
||||
await VirtualBox.instance().delete_node(node.id)
|
||||
return Response(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)) -> Response:
|
||||
async def start_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a VirtualBox node.
|
||||
"""
|
||||
|
||||
await node.start()
|
||||
return Response(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)) -> Response:
|
||||
async def stop_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a VirtualBox node.
|
||||
"""
|
||||
|
||||
await node.stop()
|
||||
return Response(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)) -> Response:
|
||||
async def suspend_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend a VirtualBox node.
|
||||
"""
|
||||
|
||||
await node.suspend()
|
||||
return Response(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)) -> Response:
|
||||
async def resume_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Resume a VirtualBox node.
|
||||
"""
|
||||
|
||||
await node.resume()
|
||||
return Response(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)) -> Response:
|
||||
async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Reload a VirtualBox node.
|
||||
"""
|
||||
|
||||
await node.reload()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -249,14 +243,13 @@ async def delete_virtualbox_node_nio(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: VirtualBoxVM = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The port number on the VirtualBox node is always 0.
|
||||
"""
|
||||
|
||||
await node.adapter_remove_nio_binding(adapter_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
@ -284,14 +277,13 @@ async def stop_virtualbox_node_capture(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: VirtualBoxVM = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The port number on the VirtualBox node is always 0.
|
||||
"""
|
||||
|
||||
await node.stop_capture(adapter_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
@ -320,7 +312,7 @@ async def console_ws(websocket: WebSocket, node: VirtualBoxVM = Depends(dep_node
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: VirtualBoxVM = Depends(dep_node)) -> Response:
|
||||
async def reset_console(node: VirtualBoxVM = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
@ -103,63 +103,57 @@ def update_vmware_node(node_data: schemas.VMwareUpdate, node: VMwareVM = Depends
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> Response:
|
||||
async def delete_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a VMware node.
|
||||
"""
|
||||
|
||||
await VMware.instance().delete_node(node.id)
|
||||
return Response(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)) -> Response:
|
||||
async def start_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a VMware node.
|
||||
"""
|
||||
|
||||
await node.start()
|
||||
return Response(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)) -> Response:
|
||||
async def stop_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a VMware node.
|
||||
"""
|
||||
|
||||
await node.stop()
|
||||
return Response(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)) -> Response:
|
||||
async def suspend_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend a VMware node.
|
||||
"""
|
||||
|
||||
await node.suspend()
|
||||
return Response(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)) -> Response:
|
||||
async def resume_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Resume a VMware node.
|
||||
"""
|
||||
|
||||
await node.resume()
|
||||
return Response(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)) -> Response:
|
||||
async def reload_vmware_node(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Reload a VMware node.
|
||||
"""
|
||||
|
||||
await node.reload()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -213,14 +207,13 @@ async def delete_vmware_node_nio(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: VMwareVM = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The port number on the VMware node is always 0.
|
||||
"""
|
||||
|
||||
await node.adapter_remove_nio_binding(adapter_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
@ -248,14 +241,13 @@ async def stop_vmware_node_capture(
|
||||
adapter_number: int,
|
||||
port_number: int = Path(..., ge=0, le=0),
|
||||
node: VMwareVM = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The port number on the VMware node is always 0.
|
||||
"""
|
||||
|
||||
await node.stop_capture(adapter_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
@ -297,7 +289,7 @@ async def console_ws(websocket: WebSocket, node: VMwareVM = Depends(dep_node)) -
|
||||
|
||||
|
||||
@router.post("/{node_id}/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console(node: VMwareVM = Depends(dep_node)) -> Response:
|
||||
async def reset_console(node: VMwareVM = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
@ -93,13 +93,12 @@ def update_vpcs_node(node_data: schemas.VPCSUpdate, node: VPCSVM = Depends(dep_n
|
||||
|
||||
|
||||
@router.delete("/{node_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> Response:
|
||||
async def delete_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Delete a VPCS node.
|
||||
"""
|
||||
|
||||
await VPCS.instance().delete_node(node.id)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.VPCS, status_code=status.HTTP_201_CREATED)
|
||||
@ -115,43 +114,40 @@ async def duplicate_vpcs_node(
|
||||
|
||||
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> Response:
|
||||
async def start_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a VPCS node.
|
||||
"""
|
||||
|
||||
await node.start()
|
||||
return Response(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)) -> Response:
|
||||
async def stop_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a VPCS node.
|
||||
"""
|
||||
|
||||
await node.stop()
|
||||
return Response(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)) -> Response:
|
||||
async def suspend_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend a VPCS node.
|
||||
Does nothing, suspend is not supported by VPCS.
|
||||
"""
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
pass
|
||||
|
||||
|
||||
@router.post("/{node_id}/reload", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)) -> Response:
|
||||
async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Reload a VPCS node.
|
||||
"""
|
||||
|
||||
await node.reload()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -206,14 +202,13 @@ async def delete_vpcs_node_nio(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: VPCSVM = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a NIO (Network Input/Output) from the node.
|
||||
The adapter number on the VPCS node is always 0.
|
||||
"""
|
||||
|
||||
await node.port_remove_nio_binding(port_number)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start")
|
||||
@ -242,21 +237,19 @@ async def stop_vpcs_node_capture(
|
||||
adapter_number: int = Path(..., ge=0, le=0),
|
||||
port_number: int,
|
||||
node: VPCSVM = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Stop a packet capture on the node.
|
||||
The adapter number on the VPCS node is always 0.
|
||||
"""
|
||||
|
||||
await node.stop_capture(port_number)
|
||||
return Response(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)) -> Response:
|
||||
async def reset_console(node: VPCSVM = Depends(dep_node)) -> None:
|
||||
|
||||
await node.reset_console()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stream")
|
||||
|
@ -47,7 +47,7 @@ router = APIRouter()
|
||||
@router.get("")
|
||||
async def get_appliances(
|
||||
update: Optional[bool] = False,
|
||||
symbol_theme: Optional[str] = "Classic"
|
||||
symbol_theme: Optional[str] = None
|
||||
) -> List[schemas.Appliance]:
|
||||
"""
|
||||
Return all appliances known by the controller.
|
||||
@ -56,7 +56,7 @@ async def get_appliances(
|
||||
controller = Controller.instance()
|
||||
if update:
|
||||
await controller.appliance_manager.download_appliances()
|
||||
controller.appliance_manager.load_appliances(symbol_theme=symbol_theme)
|
||||
controller.appliance_manager.load_appliances(symbol_theme)
|
||||
return [c.asdict() for c in controller.appliance_manager.appliances.values()]
|
||||
|
||||
|
||||
@ -106,7 +106,7 @@ async def install_appliance(
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Install an appliance.
|
||||
"""
|
||||
@ -120,4 +120,3 @@ async def install_appliance(
|
||||
rbac_repo,
|
||||
current_user
|
||||
)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -57,7 +57,7 @@ async def create_compute(
|
||||
|
||||
|
||||
@router.post("/{compute_id}/connect", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def connect_compute(compute_id: Union[str, UUID]) -> Response:
|
||||
async def connect_compute(compute_id: Union[str, UUID]) -> None:
|
||||
"""
|
||||
Connect to compute on the controller.
|
||||
"""
|
||||
@ -65,7 +65,6 @@ async def connect_compute(compute_id: Union[str, UUID]) -> Response:
|
||||
compute = Controller.instance().get_compute(str(compute_id))
|
||||
if not compute.connected:
|
||||
await compute.connect(report_failed_connection=True)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{compute_id}", response_model=schemas.Compute, response_model_exclude_unset=True)
|
||||
@ -106,13 +105,12 @@ async def update_compute(
|
||||
@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))
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a compute from the controller.
|
||||
"""
|
||||
|
||||
await ComputesService(computes_repo).delete_compute(compute_id)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{compute_id}/docker/images", response_model=List[schemas.ComputeDockerImage])
|
||||
|
@ -83,13 +83,12 @@ def check_version(version: schemas.Version) -> dict:
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
)
|
||||
async def reload() -> Response:
|
||||
async def reload() -> None:
|
||||
"""
|
||||
Reload the controller
|
||||
"""
|
||||
|
||||
await Controller.instance().reload()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -98,7 +97,7 @@ async def reload() -> Response:
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
responses={403: {"model": schemas.ErrorMessage, "description": "Server shutdown not allowed"}},
|
||||
)
|
||||
async def shutdown() -> Response:
|
||||
async def shutdown() -> None:
|
||||
"""
|
||||
Shutdown the server
|
||||
"""
|
||||
@ -126,7 +125,6 @@ async def shutdown() -> Response:
|
||||
|
||||
# then shutdown the server itself
|
||||
os.kill(os.getpid(), signal.SIGTERM)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get(
|
||||
|
@ -26,13 +26,26 @@ from gns3server.db.repositories.rbac import RbacRepository
|
||||
from gns3server.services import auth_service
|
||||
from .database import get_repository
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/users/login")
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/users/login", auto_error=False)
|
||||
|
||||
|
||||
async def get_user_from_token(
|
||||
token: str = Depends(oauth2_scheme), user_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
bearer_token: str = Depends(oauth2_scheme),
|
||||
user_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
token: Optional[str] = Query(None, include_in_schema=False)
|
||||
) -> schemas.User:
|
||||
|
||||
if bearer_token:
|
||||
# bearer token is used first, then any token passed as a URL parameter
|
||||
token = bearer_token
|
||||
|
||||
if token is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Not authenticated",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
|
||||
username = auth_service.get_username_from_token(token)
|
||||
user = await user_repo.get_user_by_username(username)
|
||||
if user is None:
|
||||
|
@ -76,11 +76,10 @@ async def update_drawing(project_id: UUID, drawing_id: UUID, drawing_data: schem
|
||||
|
||||
|
||||
@router.delete("/{drawing_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_drawing(project_id: UUID, drawing_id: UUID) -> Response:
|
||||
async def delete_drawing(project_id: UUID, drawing_id: UUID) -> None:
|
||||
"""
|
||||
Delete a drawing.
|
||||
"""
|
||||
|
||||
project = await Controller.instance().get_loaded_project(str(project_id))
|
||||
await project.delete_drawing(str(drawing_id))
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -113,7 +113,7 @@ async def update_user_group(
|
||||
async def delete_user_group(
|
||||
user_group_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete an user group
|
||||
"""
|
||||
@ -129,8 +129,6 @@ async def delete_user_group(
|
||||
if not success:
|
||||
raise ControllerError(f"User group '{user_group_id}' could not be deleted")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{user_group_id}/members", response_model=List[schemas.User])
|
||||
async def get_user_group_members(
|
||||
@ -152,7 +150,7 @@ async def add_member_to_group(
|
||||
user_group_id: UUID,
|
||||
user_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository))
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Add member to an user group.
|
||||
"""
|
||||
@ -165,8 +163,6 @@ async def add_member_to_group(
|
||||
if not user_group:
|
||||
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{user_group_id}/members/{user_id}",
|
||||
@ -176,7 +172,7 @@ async def remove_member_from_group(
|
||||
user_group_id: UUID,
|
||||
user_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Remove member from an user group.
|
||||
"""
|
||||
@ -189,8 +185,6 @@ async def remove_member_from_group(
|
||||
if not user_group:
|
||||
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{user_group_id}/roles", response_model=List[schemas.Role])
|
||||
async def get_user_group_roles(
|
||||
@ -226,8 +220,6 @@ async def add_role_to_group(
|
||||
if not user_group:
|
||||
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{user_group_id}/roles/{role_id}",
|
||||
@ -238,7 +230,7 @@ async def remove_role_from_group(
|
||||
role_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Remove role from an user group.
|
||||
"""
|
||||
@ -250,5 +242,3 @@ async def remove_role_from_group(
|
||||
user_group = await users_repo.remove_role_from_user_group(user_group_id, role)
|
||||
if not user_group:
|
||||
raise ControllerNotFoundError(f"User group '{user_group_id}' not found")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -69,7 +69,8 @@ async def upload_image(
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
current_user: schemas.User = Depends(get_current_active_user),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
install_appliances: Optional[bool] = False
|
||||
install_appliances: Optional[bool] = False,
|
||||
allow_raw_image: Optional[bool] = False
|
||||
) -> schemas.Image:
|
||||
"""
|
||||
Upload an image.
|
||||
@ -90,7 +91,7 @@ async def upload_image(
|
||||
raise ControllerBadRequestError(f"Image '{image_path}' already exists")
|
||||
|
||||
try:
|
||||
image = await write_image(image_path, full_path, request.stream(), images_repo)
|
||||
image = await write_image(image_path, full_path, request.stream(), images_repo, allow_raw_image=allow_raw_image)
|
||||
except (OSError, InvalidImageError, ClientDisconnect) as e:
|
||||
raise ControllerError(f"Could not save image '{image_path}': {e}")
|
||||
|
||||
@ -129,7 +130,7 @@ async def get_image(
|
||||
async def delete_image(
|
||||
image_path: str,
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete an image.
|
||||
"""
|
||||
@ -159,16 +160,13 @@ async def delete_image(
|
||||
if not success:
|
||||
raise ControllerError(f"Image '{image_path}' could not be deleted")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/prune", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def prune_images(
|
||||
images_repo: ImagesRepository = Depends(get_repository(ImagesRepository)),
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Prune images not attached to any template.
|
||||
"""
|
||||
|
||||
await images_repo.prune_images()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -136,14 +136,13 @@ async def update_link(link_data: schemas.LinkUpdate, link: Link = Depends(dep_li
|
||||
|
||||
|
||||
@router.delete("/{link_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_link(project_id: UUID, link: Link = Depends(dep_link)) -> Response:
|
||||
async def delete_link(project_id: UUID, link: Link = Depends(dep_link)) -> None:
|
||||
"""
|
||||
Delete a link.
|
||||
"""
|
||||
|
||||
project = await Controller.instance().get_loaded_project(str(project_id))
|
||||
await project.delete_link(link.id)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{link_id}/reset", response_model=schemas.Link)
|
||||
@ -170,13 +169,12 @@ async def start_capture(capture_data: dict, link: Link = Depends(dep_link)) -> s
|
||||
|
||||
|
||||
@router.post("/{link_id}/capture/stop", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def stop_capture(link: Link = Depends(dep_link)) -> Response:
|
||||
async def stop_capture(link: Link = Depends(dep_link)) -> None:
|
||||
"""
|
||||
Stop packet capture on the link.
|
||||
"""
|
||||
|
||||
await link.stop_capture()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{link_id}/capture/stream")
|
||||
|
@ -130,44 +130,40 @@ async def get_nodes(project: Project = Depends(dep_project)) -> List[schemas.Nod
|
||||
|
||||
|
||||
@router.post("/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_all_nodes(project: Project = Depends(dep_project)) -> Response:
|
||||
async def start_all_nodes(project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Start all nodes belonging to a given project.
|
||||
"""
|
||||
|
||||
await project.start_all()
|
||||
return Response(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)) -> Response:
|
||||
async def stop_all_nodes(project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Stop all nodes belonging to a given project.
|
||||
"""
|
||||
|
||||
await project.stop_all()
|
||||
return Response(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)) -> Response:
|
||||
async def suspend_all_nodes(project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Suspend all nodes belonging to a given project.
|
||||
"""
|
||||
|
||||
await project.suspend_all()
|
||||
return Response(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)) -> Response:
|
||||
async def reload_all_nodes(project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Reload all nodes belonging to a given project.
|
||||
"""
|
||||
|
||||
await project.stop_all()
|
||||
await project.start_all()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}", response_model=schemas.Node)
|
||||
@ -201,13 +197,12 @@ async def update_node(node_data: schemas.NodeUpdate, node: Node = Depends(dep_no
|
||||
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)) -> Response:
|
||||
async def delete_node(node_id: UUID, project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Delete a node from a project.
|
||||
"""
|
||||
|
||||
await project.delete_node(str(node_id))
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/duplicate", response_model=schemas.Node, status_code=status.HTTP_201_CREATED)
|
||||
@ -221,65 +216,59 @@ async def duplicate_node(duplicate_data: schemas.NodeDuplicate, node: Node = Dep
|
||||
|
||||
|
||||
@router.post("/{node_id}/start", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def start_node(start_data: dict, node: Node = Depends(dep_node)) -> Response:
|
||||
async def start_node(start_data: dict, node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Start a node.
|
||||
"""
|
||||
|
||||
await node.start(data=start_data)
|
||||
return Response(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)) -> Response:
|
||||
async def stop_node(node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Stop a node.
|
||||
"""
|
||||
|
||||
await node.stop()
|
||||
return Response(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)) -> Response:
|
||||
async def suspend_node(node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Suspend a node.
|
||||
"""
|
||||
|
||||
await node.suspend()
|
||||
return Response(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)) -> Response:
|
||||
async def reload_node(node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Reload a node.
|
||||
"""
|
||||
|
||||
await node.reload()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/isolate", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def isolate_node(node: Node = Depends(dep_node)) -> Response:
|
||||
async def isolate_node(node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Isolate a node (suspend all attached links).
|
||||
"""
|
||||
|
||||
for link in node.links:
|
||||
await link.update_suspend(True)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{node_id}/unisolate", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def unisolate_node(node: Node = Depends(dep_node)) -> Response:
|
||||
async def unisolate_node(node: Node = Depends(dep_node)) -> None:
|
||||
"""
|
||||
Un-isolate a node (resume all attached suspended links).
|
||||
"""
|
||||
|
||||
for link in node.links:
|
||||
await link.update_suspend(False)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/links", response_model=List[schemas.Link], response_model_exclude_unset=True)
|
||||
@ -321,7 +310,7 @@ async def create_disk_image(
|
||||
disk_name: str,
|
||||
disk_data: schemas.QemuDiskImageCreate,
|
||||
node: Node = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Create a Qemu disk image.
|
||||
"""
|
||||
@ -329,7 +318,6 @@ async def create_disk_image(
|
||||
if node.node_type != "qemu":
|
||||
raise ControllerBadRequestError("Creating a disk image is only supported on a Qemu node")
|
||||
await node.post(f"/disk_image/{disk_name}", data=disk_data.dict(exclude_unset=True))
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.put("/{node_id}/qemu/disk_image/{disk_name}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
@ -337,7 +325,7 @@ async def update_disk_image(
|
||||
disk_name: str,
|
||||
disk_data: schemas.QemuDiskImageUpdate,
|
||||
node: Node = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Update a Qemu disk image.
|
||||
"""
|
||||
@ -345,14 +333,13 @@ async def update_disk_image(
|
||||
if node.node_type != "qemu":
|
||||
raise ControllerBadRequestError("Updating a disk image is only supported on a Qemu node")
|
||||
await node.put(f"/disk_image/{disk_name}", data=disk_data.dict(exclude_unset=True))
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.delete("/{node_id}/qemu/disk_image/{disk_name}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_disk_image(
|
||||
disk_name: str,
|
||||
node: Node = Depends(dep_node)
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a Qemu disk image.
|
||||
"""
|
||||
@ -360,7 +347,6 @@ async def delete_disk_image(
|
||||
if node.node_type != "qemu":
|
||||
raise ControllerBadRequestError("Deleting a disk image is only supported on a Qemu node")
|
||||
await node.delete(f"/disk_image/{disk_name}")
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{node_id}/files/{file_path:path}")
|
||||
@ -451,17 +437,15 @@ async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)) -> No
|
||||
|
||||
|
||||
@router.post("/console/reset", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def reset_console_all_nodes(project: Project = Depends(dep_project)) -> Response:
|
||||
async def reset_console_all_nodes(project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Reset console for all nodes belonging to the project.
|
||||
"""
|
||||
|
||||
await project.reset_console_all()
|
||||
return Response(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)) -> Response:
|
||||
async def console_reset(node: Node = Depends(dep_node)) -> None:
|
||||
|
||||
await node.post("/console/reset")
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -136,7 +136,7 @@ async def update_permission(
|
||||
async def delete_permission(
|
||||
permission_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a permission.
|
||||
"""
|
||||
@ -149,16 +149,13 @@ async def delete_permission(
|
||||
if not success:
|
||||
raise ControllerNotFoundError(f"Permission '{permission_id}' could not be deleted")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/prune", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def prune_permissions(
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Prune orphaned permissions.
|
||||
"""
|
||||
|
||||
await rbac_repo.prune_permissions()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -141,7 +141,7 @@ async def update_project(
|
||||
async def delete_project(
|
||||
project: Project = Depends(dep_project),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a project.
|
||||
"""
|
||||
@ -150,7 +150,6 @@ async def delete_project(
|
||||
await project.delete()
|
||||
controller.remove_project(project)
|
||||
await rbac_repo.delete_all_permissions_with_path(f"/projects/{project.id}")
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{project_id}/stats")
|
||||
@ -167,13 +166,12 @@ def get_project_stats(project: Project = Depends(dep_project)) -> dict:
|
||||
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)) -> Response:
|
||||
async def close_project(project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Close a project.
|
||||
"""
|
||||
|
||||
await project.close()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post(
|
||||
@ -349,33 +347,25 @@ async def export_project(
|
||||
async def import_project(
|
||||
project_id: UUID,
|
||||
request: Request,
|
||||
path: Optional[Path] = None,
|
||||
name: Optional[str] = None
|
||||
) -> schemas.Project:
|
||||
"""
|
||||
Import a project from a portable archive.
|
||||
"""
|
||||
|
||||
#TODO: import project remotely
|
||||
raise NotImplementedError()
|
||||
|
||||
controller = Controller.instance()
|
||||
# We write the content to a temporary location and after we extract it all.
|
||||
# We write the content to a temporary location and then we extract it all.
|
||||
# It could be more optimal to stream this but it is not implemented in Python.
|
||||
try:
|
||||
begin = time.time()
|
||||
# use the parent directory or projects dir as a temporary working dir
|
||||
if path:
|
||||
working_dir = os.path.abspath(os.path.join(path, os.pardir))
|
||||
else:
|
||||
working_dir = controller.projects_directory()
|
||||
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 for chunk in request.stream():
|
||||
await f.write(chunk)
|
||||
with open(temp_project_path, "rb") as f:
|
||||
project = await import_controller_project(controller, str(project_id), f, location=path, name=name)
|
||||
project = await import_controller_project(controller, str(project_id), f, name=name)
|
||||
|
||||
log.info(f"Project '{project.name}' imported in {time.time() - begin:.4f} seconds")
|
||||
except OSError as e:
|
||||
@ -428,7 +418,7 @@ async def get_file(file_path: str, project: Project = Depends(dep_project)) -> F
|
||||
|
||||
|
||||
@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)) -> Response:
|
||||
async def write_file(file_path: str, request: Request, project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Write a file to a project.
|
||||
"""
|
||||
@ -453,8 +443,6 @@ async def write_file(file_path: str, request: Request, project: Project = Depend
|
||||
except OSError as e:
|
||||
raise ControllerError(str(e))
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{project_id}/templates/{template_id}",
|
||||
|
@ -106,7 +106,7 @@ async def update_role(
|
||||
async def delete_role(
|
||||
role_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a role.
|
||||
"""
|
||||
@ -122,8 +122,6 @@ async def delete_role(
|
||||
if not success:
|
||||
raise ControllerError(f"Role '{role_id}' could not be deleted")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("/{role_id}/permissions", response_model=List[schemas.Permission])
|
||||
async def get_role_permissions(
|
||||
@ -145,7 +143,7 @@ async def add_permission_to_role(
|
||||
role_id: UUID,
|
||||
permission_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Add a permission to a role.
|
||||
"""
|
||||
@ -158,8 +156,6 @@ async def add_permission_to_role(
|
||||
if not role:
|
||||
raise ControllerNotFoundError(f"Role '{role_id}' not found")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{role_id}/permissions/{permission_id}",
|
||||
@ -169,7 +165,7 @@ async def remove_permission_from_role(
|
||||
role_id: UUID,
|
||||
permission_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Remove member from an user group.
|
||||
"""
|
||||
@ -181,5 +177,3 @@ async def remove_permission_from_role(
|
||||
role = await rbac_repo.remove_permission_from_role(role_id, permission)
|
||||
if not role:
|
||||
raise ControllerNotFoundError(f"Role '{role_id}' not found")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
@ -69,13 +69,12 @@ def get_snapshots(project: Project = Depends(dep_project)) -> List[schemas.Snaps
|
||||
|
||||
|
||||
@router.delete("/{snapshot_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def delete_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)) -> Response:
|
||||
async def delete_snapshot(snapshot_id: UUID, project: Project = Depends(dep_project)) -> None:
|
||||
"""
|
||||
Delete a snapshot.
|
||||
"""
|
||||
|
||||
await project.delete_snapshot(str(snapshot_id))
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.post("/{snapshot_id}/restore", status_code=status.HTTP_201_CREATED, response_model=schemas.Project)
|
||||
|
@ -95,7 +95,7 @@ def get_default_symbols() -> dict:
|
||||
dependencies=[Depends(get_current_active_user)],
|
||||
status_code=status.HTTP_204_NO_CONTENT
|
||||
)
|
||||
async def upload_symbol(symbol_id: str, request: Request) -> Response:
|
||||
async def upload_symbol(symbol_id: str, request: Request) -> None:
|
||||
"""
|
||||
Upload a symbol file.
|
||||
"""
|
||||
@ -112,4 +112,3 @@ async def upload_symbol(symbol_id: str, request: Request) -> Response:
|
||||
# Reset the symbol list
|
||||
controller.symbols.list()
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -102,7 +102,7 @@ async def delete_template(
|
||||
templates_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository)),
|
||||
images_repo: RbacRepository = Depends(get_repository(ImagesRepository)),
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete a template.
|
||||
"""
|
||||
@ -111,7 +111,6 @@ async def delete_template(
|
||||
await rbac_repo.delete_all_permissions_with_path(f"/templates/{template_id}")
|
||||
if prune_images:
|
||||
await images_repo.prune_images()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get("", response_model=List[schemas.Template], response_model_exclude_unset=True)
|
||||
|
@ -194,7 +194,7 @@ async def update_user(
|
||||
async def delete_user(
|
||||
user_id: UUID,
|
||||
users_repo: UsersRepository = Depends(get_repository(UsersRepository)),
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Delete an user.
|
||||
"""
|
||||
@ -210,8 +210,6 @@ async def delete_user(
|
||||
if not success:
|
||||
raise ControllerError(f"User '{user_id}' could not be deleted")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{user_id}/groups",
|
||||
@ -254,7 +252,7 @@ async def add_permission_to_user(
|
||||
user_id: UUID,
|
||||
permission_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository))
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Add a permission to an user.
|
||||
"""
|
||||
@ -267,8 +265,6 @@ async def add_permission_to_user(
|
||||
if not user:
|
||||
raise ControllerNotFoundError(f"User '{user_id}' not found")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{user_id}/permissions/{permission_id}",
|
||||
@ -279,7 +275,7 @@ async def remove_permission_from_user(
|
||||
user_id: UUID,
|
||||
permission_id: UUID,
|
||||
rbac_repo: RbacRepository = Depends(get_repository(RbacRepository)),
|
||||
) -> Response:
|
||||
) -> None:
|
||||
"""
|
||||
Remove permission from an user.
|
||||
"""
|
||||
@ -291,5 +287,3 @@ async def remove_permission_from_user(
|
||||
user = await rbac_repo.remove_permission_from_user(user_id, permission)
|
||||
if not user:
|
||||
raise ControllerNotFoundError(f"User '{user_id}' not found")
|
||||
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -4,7 +4,7 @@
|
||||
"category": "multilayer_switch",
|
||||
"description": "The ArubaOS-CX Simulation Software is a virtual platform to enable simulation of the ArubaOS-CX Network Operating System. Simulated networks can be created using many of the protocols in the ArubaOS-CX Operating system like OSPF, BGP (inc. EVPN). Key features like the Aruba Network Analytics Engine and the REST API can be simulated, providing a lightweight development platform to building the modern network.",
|
||||
"vendor_name": "HPE Aruba",
|
||||
"vendor_url": "arubanetworks.com",
|
||||
"vendor_url": "https://www.arubanetworks.com",
|
||||
"product_name": "ArubaOS-CX Simulation Software",
|
||||
"registry_version": 4,
|
||||
"status": "stable",
|
||||
@ -30,6 +30,13 @@
|
||||
"process_priority": "normal"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "arubaoscx-disk-image-genericx86-p4-20220616193419.vmdk",
|
||||
"version": "10.10.0002",
|
||||
"md5sum": "ed031aeb6caf92adb408c7603d294fd4",
|
||||
"filesize": 355858944,
|
||||
"download_url": "https://asp.arubanetworks.com/"
|
||||
},
|
||||
{
|
||||
"filename": "arubaoscx-disk-image-genericx86-p4-20220223012712.vmdk",
|
||||
"version": "10.09.1000",
|
||||
@ -88,6 +95,12 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "10.10.0002",
|
||||
"images": {
|
||||
"hda_disk_image": "arubaoscx-disk-image-genericx86-p4-20220616193419.vmdk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "10.09.1000",
|
||||
"images": {
|
||||
|
@ -4,7 +4,7 @@
|
||||
"category": "firewall",
|
||||
"description": "Aruba Virtual Gateways allow customers to bring their public cloud infrastructure to the SD-WAN fabric and facilitate connectivity between branches and the public cloud.",
|
||||
"vendor_name": "HPE Aruba",
|
||||
"vendor_url": "arubanetworks.com",
|
||||
"vendor_url": "https://www.arubanetworks.com",
|
||||
"documentation_url": "https://asp.arubanetworks.com/downloads;products=Aruba%20SD-WAN",
|
||||
"product_name": "Aruba SD-WAN Virtual Gateway",
|
||||
"product_url": "https://www.arubanetworks.com/products/networking/gateways-and-controllers/",
|
||||
|
@ -4,7 +4,7 @@
|
||||
"category": "guest",
|
||||
"description": "Aruba Virtual Mobility Controller",
|
||||
"vendor_name": "HPE Aruba",
|
||||
"vendor_url": "arubanetworks.com",
|
||||
"vendor_url": "https://www.arubanetworks.com",
|
||||
"product_name": "Aruba VMC",
|
||||
"registry_version": 4,
|
||||
"status": "stable",
|
||||
|
@ -27,6 +27,13 @@
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "asav9-16-2.qcow2",
|
||||
"version": "9.16.2 CML",
|
||||
"md5sum": "1f8db97063a7f738fddc81ac880a906c",
|
||||
"filesize": 262078976,
|
||||
"download_url": "https://learningnetworkstore.cisco.com/cisco-modeling-labs-personal/cisco-modeling-labs-personal/CML-PERSONAL.html"
|
||||
},
|
||||
{
|
||||
"filename": "asav9-16-2.qcow2",
|
||||
"version": "9.16.2",
|
||||
"md5sum": "c3aa2b73b029146ec345bf888dd54eab",
|
||||
@ -112,6 +119,12 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "9.16.2 CML",
|
||||
"images": {
|
||||
"hda_disk_image": "asav9-16-2.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9.16.2",
|
||||
"images": {
|
||||
|
@ -24,6 +24,13 @@
|
||||
"kvm": "require"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "c8000v-universalk9_8G_serial.17.06.01a.qcow2",
|
||||
"version": "17.06.01a 8G",
|
||||
"md5sum": "d8b8ae633d953ec1b6d8f18a09a4f4e7",
|
||||
"filesize": 1595277312,
|
||||
"download_url": "https://software.cisco.com/download/home/286327102/type/282046477/release/Bengaluru-17.6.1a"
|
||||
},
|
||||
{
|
||||
"filename": "c8000v-universalk9_8G_serial.17.04.01a.qcow2",
|
||||
"version": "17.04.01a 8G",
|
||||
@ -40,6 +47,12 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "17.06.01a 8G",
|
||||
"images": {
|
||||
"hda_disk_image": "c8000v-universalk9_8G_serial.17.06.01a.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "17.04.01a 8G",
|
||||
"images": {
|
||||
|
@ -18,6 +18,12 @@
|
||||
"startup_config": "iou_l3_base_startup-config.txt"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "i86bi_LinuxL3-AdvEnterpriseK9-M2_157_3_May_2018.bin",
|
||||
"version": "15.7(3)M2",
|
||||
"md5sum": "d6874260c3daeeb96d10fc844ae0b93b",
|
||||
"filesize": 184759244
|
||||
},
|
||||
{
|
||||
"filename": "i86bi-linux-l3-adventerprisek9-ms.155-2.T.bin",
|
||||
"version": "155-2T",
|
||||
@ -32,6 +38,12 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "15.7(3)M2",
|
||||
"images": {
|
||||
"image": "i86bi_LinuxL3-AdvEnterpriseK9-M2_157_3_May_2018.bin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "155-2T",
|
||||
"images": {
|
||||
|
@ -33,6 +33,13 @@
|
||||
"filesize": 1592000512,
|
||||
"download_url": "https://software.cisco.com/download/home/286312239/type/282088129/release/10.1(1)"
|
||||
},
|
||||
{
|
||||
"filename": "nexus9300v.9.3.8.qcow2",
|
||||
"version": "9300v 9.3.8",
|
||||
"md5sum": "f8bd834f8395c134dc98d895c98441af",
|
||||
"filesize": 1976434688,
|
||||
"download_url": "https://software.cisco.com/download/home/286312239/type/282088129/release/9.3(8)"
|
||||
},
|
||||
{
|
||||
"filename": "nexus9500v.9.3.7.qcow2",
|
||||
"version": "9500v 9.3.7",
|
||||
@ -176,6 +183,13 @@
|
||||
"bios_image": "OVMF-20160813.fd",
|
||||
"hda_disk_image": "nexus9500v64.10.1.1.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9300v 9.3.8",
|
||||
"images": {
|
||||
"bios_image": "OVMF-20160813.fd",
|
||||
"hda_disk_image": "nexus9300v.9.3.8.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "9500v 9.3.7",
|
||||
|
@ -12,7 +12,7 @@
|
||||
"status": "stable",
|
||||
"maintainer": "GNS3 Team",
|
||||
"maintainer_email": "developers@gns3.net",
|
||||
"usage": "Default username is cumulus and password is CumulusLinux! in version 4.1 and earlier, and cumulus in version 4.2 and later.",
|
||||
"usage": "Default username/password is cumulus/CumulusLinux! in version 4.1 and earlier, and cumulus/cumulus in version 4.2 and later.",
|
||||
"first_port_name": "eth0",
|
||||
"port_name_format": "swp{port1}",
|
||||
"qemu": {
|
||||
@ -25,6 +25,14 @@
|
||||
"kvm": "require"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "cumulus-linux-5.1.0-vx-amd64-qemu.qcow2",
|
||||
"version": "5.1.0",
|
||||
"md5sum": "b46a68bbb57e77fab5c2927367bead13",
|
||||
"filesize": 4174446592,
|
||||
"download_url": "https://www.nvidia.com/en-us/networking/ethernet-switching/cumulus-vx/download/",
|
||||
"direct_download_url": "https://d2cd9e7ca6hntp.cloudfront.net/public/CumulusLinux-5.1.0/cumulus-linux-5.1.0-vx-amd64-qemu.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "cumulus-linux-4.3.0-vx-amd64-qemu.qcow2",
|
||||
"version": "4.3.0",
|
||||
@ -231,6 +239,12 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "5.1.0",
|
||||
"images": {
|
||||
"hda_disk_image": "cumulus-linux-5.1.0-vx-amd64-qemu.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "4.3.0",
|
||||
"images": {
|
||||
|
@ -24,12 +24,12 @@
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "debian-11-genericcloud-amd64-20220328-962.qcow2",
|
||||
"version": "11.3",
|
||||
"md5sum": "7cf51e23747898485971a656ac2eb96d",
|
||||
"filesize": 253296640,
|
||||
"filename": "debian-11-genericcloud-amd64-20220711-1073.qcow2",
|
||||
"version": "11.4",
|
||||
"md5sum": "e8fadf4bbf7324a2e2875a5ba00588e7",
|
||||
"filesize": 253231104,
|
||||
"download_url": "https://cloud.debian.org/images/cloud/bullseye/",
|
||||
"direct_download_url": "https://cloud.debian.org/images/cloud/bullseye/20220328-962/debian-11-genericcloud-amd64-20220328-962.qcow2"
|
||||
"direct_download_url": "https://cloud.debian.org/images/cloud/bullseye/20220711-1073/debian-11-genericcloud-amd64-20220711-1073.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "debian-10-genericcloud-amd64-20220328-962.qcow2",
|
||||
@ -49,9 +49,9 @@
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "11.3",
|
||||
"name": "11.4",
|
||||
"images": {
|
||||
"hda_disk_image": "debian-11-genericcloud-amd64-20220328-962.qcow2",
|
||||
"hda_disk_image": "debian-11-genericcloud-amd64-20220711-1073.qcow2",
|
||||
"cdrom_image": "debian-cloud-init-data.iso"
|
||||
}
|
||||
},
|
||||
|
@ -2,7 +2,7 @@
|
||||
"appliance_id": "1cfdf900-7c30-4cb7-8f03-3f61d2581633",
|
||||
"name": "Empty VM",
|
||||
"category": "guest",
|
||||
"description": "An empty VM with empty hard disks 8G, 30G & 100G.",
|
||||
"description": "A empty VM with empty hard disks 8G, 30G, 100G & 200G.",
|
||||
"vendor_name": "GNS3",
|
||||
"vendor_url": "https://gns3.com",
|
||||
"documentation_url": "",
|
||||
@ -20,7 +20,7 @@
|
||||
"ram": 1024,
|
||||
"arch": "x86_64",
|
||||
"console_type": "vnc",
|
||||
"hda_disk_interface": "sata",
|
||||
"hda_disk_interface": "sata",
|
||||
"boot_priority": "d",
|
||||
"kvm": "allow"
|
||||
},
|
||||
@ -48,6 +48,14 @@
|
||||
"filesize": 198656,
|
||||
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
|
||||
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty100G.qcow2/download"
|
||||
},
|
||||
{
|
||||
"filename": "empty200G.qcow2",
|
||||
"version": "200G",
|
||||
"md5sum": "d1686d2f25695dee32eab9a6f4652c7c",
|
||||
"filesize": 200192,
|
||||
"download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/",
|
||||
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Empty%20Qemu%20disk/empty200G.qcow2/download"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
@ -63,11 +71,17 @@
|
||||
"hda_disk_image": "empty30G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
{
|
||||
"name": "100G",
|
||||
"images": {
|
||||
"hda_disk_image": "empty100G.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "200G",
|
||||
"images": {
|
||||
"hda_disk_image": "empty200G.qcow2"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -27,6 +27,20 @@
|
||||
"options": "-cpu core2duo"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "EXOS-VM_v32.1.1.6.qcow2",
|
||||
"version": "32.1.1.6",
|
||||
"md5sum": "48868bbcb4255d6365049b5941dd2af7",
|
||||
"filesize": 231211008,
|
||||
"direct_download_url": "https://akamai-ep.extremenetworks.com/Extreme_P/github-en/Virtual_EXOS/EXOS-VM_v32.1.1.6.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "EXOS-VM_v31.7.1.4.qcow2",
|
||||
"version": "31.7.1.4",
|
||||
"md5sum": "a70e4fa3bc361434237ad12937aaf0fb",
|
||||
"filesize": 458227712,
|
||||
"direct_download_url": "https://akamai-ep.extremenetworks.com/Extreme_P/github-en/Virtual_EXOS/EXOS-VM_v31.7.1.4.qcow2"
|
||||
},
|
||||
{
|
||||
"filename": "EXOS-VM_v31.1.1.3.qcow2",
|
||||
"version": "31.1.1.3",
|
||||
@ -99,6 +113,18 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "32.1.1.6",
|
||||
"images": {
|
||||
"hda_disk_image": "EXOS-VM_v32.1.1.6.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "31.7.1.4",
|
||||
"images": {
|
||||
"hda_disk_image": "EXOS-VM_v31.7.1.4.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "31.1.1.3",
|
||||
"images": {
|
||||
|
@ -24,125 +24,35 @@
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "FreeBSD-12.1-RELEASE-amd64.qcow2",
|
||||
"version": "12.1",
|
||||
"md5sum": "0079b4ca99f64bb825cfbaefcca10fd4",
|
||||
"filesize": 3043229696,
|
||||
"filename": "FreeBSD-13.0-RELEASE-amd64.qcow2",
|
||||
"version": "13.0",
|
||||
"md5sum": "e8e598959da456c03260421b5f9890de",
|
||||
"filesize": 3466854400,
|
||||
"download_url": "https://www.freebsd.org/where.html",
|
||||
"direct_download_url": "https://download.freebsd.org/ftp/releases/VM-IMAGES/12.1-RELEASE/amd64/Latest/FreeBSD-12.1-RELEASE-amd64.qcow2.xz",
|
||||
"direct_download_url": "https://download.freebsd.org/ftp/releases/VM-IMAGES/13.0-RELEASE/amd64/Latest/FreeBSD-13.0-RELEASE-amd64.qcow2.xz",
|
||||
"compression": "xz"
|
||||
},
|
||||
{
|
||||
"filename": "FreeBSD-12.0-RELEASE-amd64.qcow2",
|
||||
"version": "12.0",
|
||||
"md5sum": "4d2126ba79dad224628be6f25a908bd8",
|
||||
"filesize": 2644836352,
|
||||
"filename": "FreeBSD-12.3-RELEASE-amd64.qcow2",
|
||||
"version": "12.3",
|
||||
"md5sum": "3d7d5396f3d89ed30c2bfa2ee2e6b013",
|
||||
"filesize": 3412000768,
|
||||
"download_url": "https://www.freebsd.org/where.html",
|
||||
"direct_download_url": "https://download.freebsd.org/ftp/releases/VM-IMAGES/12.0-RELEASE/amd64/Latest/FreeBSD-12.0-RELEASE-amd64.qcow2.xz",
|
||||
"compression": "xz"
|
||||
},
|
||||
{
|
||||
"filename": "FreeBSD-11.3-RELEASE-amd64.qcow2",
|
||||
"version": "11.3",
|
||||
"md5sum": "cdb35f676571b91584ff88502c48d399",
|
||||
"filesize": 1698037760,
|
||||
"download_url": "https://www.freebsd.org/where.html",
|
||||
"direct_download_url": "https://download.freebsd.org/ftp/releases/VM-IMAGES/11.3-RELEASE/amd64/Latest/FreeBSD-11.3-RELEASE-amd64.qcow2.xz",
|
||||
"compression": "xz"
|
||||
},
|
||||
{
|
||||
"filename": "FreeBSD-11.2-RELEASE-amd64.qcow2",
|
||||
"version": "11.2",
|
||||
"md5sum": "44d37e65be4bb4054f067911c84d074a",
|
||||
"filesize": 1630076928,
|
||||
"download_url": "https://www.freebsd.org/where.html",
|
||||
"direct_download_url": "https://download.freebsd.org/ftp/releases/VM-IMAGES/11.2-RELEASE/amd64/Latest/FreeBSD-11.2-RELEASE-amd64.qcow2.xz",
|
||||
"compression": "xz"
|
||||
},
|
||||
{
|
||||
"filename": "FreeBSD-11.1-RELEASE-amd64.qcow2",
|
||||
"version": "11.1",
|
||||
"md5sum": "d78b2a7d05ec62f799e14ded4817ea69",
|
||||
"filesize": 1533345792,
|
||||
"download_url": "https://www.freebsd.org/where.html",
|
||||
"direct_download_url": "https://download.freebsd.org/ftp/releases/VM-IMAGES/11.1-RELEASE/amd64/Latest/FreeBSD-11.1-RELEASE-amd64.qcow2.xz",
|
||||
"compression": "xz"
|
||||
},
|
||||
{
|
||||
"filename": "FreeBSD-11.0-RELEASE-amd64.qcow2",
|
||||
"version": "11.0",
|
||||
"md5sum": "1b04999198f492afd6dc4935b8c7cc22",
|
||||
"filesize": 1384382464,
|
||||
"download_url": "https://www.freebsd.org/where.html",
|
||||
"direct_download_url": "https://download.freebsd.org/ftp/releases/VM-IMAGES/11.0-RELEASE/amd64/Latest/FreeBSD-11.0-RELEASE-amd64.qcow2.xz",
|
||||
"compression": "xz"
|
||||
},
|
||||
{
|
||||
"filename": "FreeBSD-10.4-RELEASE-amd64.qcow2",
|
||||
"version": "10.4",
|
||||
"md5sum": "ad498873733c57d1f6d890d587a11e3c",
|
||||
"filesize": 1013448704,
|
||||
"download_url": "https://www.freebsd.org/where.html",
|
||||
"direct_download_url": "https://download.freebsd.org/ftp/releases/VM-IMAGES/10.4-RELEASE/amd64/Latest/FreeBSD-10.4-RELEASE-amd64.qcow2.xz",
|
||||
"compression": "xz"
|
||||
},
|
||||
{
|
||||
"filename": "FreeBSD-10.3-RELEASE-amd64.qcow2",
|
||||
"version": "10.3",
|
||||
"md5sum": "1a00cebef520dfac8d2bda10ea16a951",
|
||||
"filesize": 974651392,
|
||||
"download_url": "https://www.freebsd.org/where.html",
|
||||
"direct_download_url": "https://download.freebsd.org/ftp/releases/VM-IMAGES/10.3-RELEASE/amd64/Latest/FreeBSD-10.3-RELEASE-amd64.qcow2.xz",
|
||||
"direct_download_url": "https://download.freebsd.org/ftp/releases/VM-IMAGES/12.3-RELEASE/amd64/Latest/FreeBSD-12.3-RELEASE-amd64.qcow2.xz",
|
||||
"compression": "xz"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "12.1",
|
||||
"name": "13.0",
|
||||
"images": {
|
||||
"hda_disk_image": "FreeBSD-12.1-RELEASE-amd64.qcow2"
|
||||
"hda_disk_image": "FreeBSD-13.0-RELEASE-amd64.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "12.0",
|
||||
"name": "12.3",
|
||||
"images": {
|
||||
"hda_disk_image": "FreeBSD-12.0-RELEASE-amd64.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "11.3",
|
||||
"images": {
|
||||
"hda_disk_image": "FreeBSD-11.3-RELEASE-amd64.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "11.2",
|
||||
"images": {
|
||||
"hda_disk_image": "FreeBSD-11.2-RELEASE-amd64.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "11.1",
|
||||
"images": {
|
||||
"hda_disk_image": "FreeBSD-11.1-RELEASE-amd64.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "11.0",
|
||||
"images": {
|
||||
"hda_disk_image": "FreeBSD-11.0-RELEASE-amd64.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "10.4",
|
||||
"images": {
|
||||
"hda_disk_image": "FreeBSD-10.4-RELEASE-amd64.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "10.3",
|
||||
"images": {
|
||||
"hda_disk_image": "FreeBSD-10.3-RELEASE-amd64.qcow2"
|
||||
"hda_disk_image": "FreeBSD-12.3-RELEASE-amd64.qcow2"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -11,7 +11,7 @@
|
||||
"status": "experimental",
|
||||
"availability": "service-contract",
|
||||
"maintainer": "none",
|
||||
"maintainer_email": "none",
|
||||
"maintainer_email": "",
|
||||
"usage": "Default user is super, default password is super.",
|
||||
"port_name_format": "GigabitEthernet0/0/{0}",
|
||||
"qemu": {
|
||||
@ -26,6 +26,13 @@
|
||||
"options": "-machine type=pc,accel=kvm -vga std -usbdevice tablet -cpu host"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "huaweiar1k-5.170-V300R021C00SPC100T-Auto-update-esn.qcow2",
|
||||
"version": "V300R021C00SPC100T",
|
||||
"md5sum": "9d98b31d400a94af37b5af6e9cfe8d80",
|
||||
"filesize": 673465344,
|
||||
"download_url": "https://support.huawei.com/enterprise/en/routers/ar1000v-pid-21768212/software"
|
||||
},
|
||||
{
|
||||
"filename": "ar1k-V300R019C00SPC300.qcow2",
|
||||
"version": "V300R019C00SPC300",
|
||||
@ -33,13 +40,21 @@
|
||||
"filesize": 534904832,
|
||||
"download_url": "https://support.huawei.com/enterprise/en/routers/ar1000v-pid-21768212/software"
|
||||
}
|
||||
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "V300R021C00SPC100T",
|
||||
"images": {
|
||||
"hda_disk_image": "huaweiar1k-5.170-V300R021C00SPC100T-Auto-update-esn.qcow2"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "V300R019C00SPC300",
|
||||
"images": {
|
||||
"hda_disk_image": "ar1k-V300R019C00SPC300.qcow2"
|
||||
}
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
|
@ -10,7 +10,7 @@
|
||||
"status": "experimental",
|
||||
"availability": "service-contract",
|
||||
"maintainer": "none",
|
||||
"maintainer_email": "none",
|
||||
"maintainer_email": "",
|
||||
"port_name_format": "GE1/0/{0}",
|
||||
"qemu": {
|
||||
"adapter_type": "e1000",
|
||||
|
@ -11,7 +11,7 @@
|
||||
"status": "experimental",
|
||||
"availability": "service-contract",
|
||||
"maintainer": "none",
|
||||
"maintainer_email": "none",
|
||||
"maintainer_email": "",
|
||||
"first_port_name": "eth0",
|
||||
"port_name_format": "Ethernet1/0/{0}",
|
||||
"qemu": {
|
||||
|
@ -11,7 +11,7 @@
|
||||
"status": "experimental",
|
||||
"availability": "service-contract",
|
||||
"maintainer": "none",
|
||||
"maintainer_email": "none",
|
||||
"maintainer_email": "",
|
||||
"usage": "Default password is admin. Default username and password for web is admin/Admin@123.",
|
||||
"first_port_name": "GigabitEthernet0/0/0",
|
||||
"port_name_format": "GigabitEthernet1/0/{0}",
|
||||
|
@ -12,13 +12,13 @@
|
||||
"status": "experimental",
|
||||
"maintainer": "none",
|
||||
"maintainer_email": "developers@gns3.net",
|
||||
"usage": "Initial username is root, password is root.\n",
|
||||
"usage": "Connect VCP by port Eth1.\nData port ge/xe-x/0/0 to ge/xe-x/0/9 mapping to Eth3 to Eth12.\nInitial username is root, password is root.\n",
|
||||
"symbol": "juniper-vmx.svg",
|
||||
"first_port_name": "Eth0",
|
||||
"port_name_format": "Eth{port1}",
|
||||
"qemu": {
|
||||
"adapter_type": "virtio-net-pci",
|
||||
"adapters": 12,
|
||||
"adapters": 13,
|
||||
"ram": 4096,
|
||||
"hda_disk_interface": "ide",
|
||||
"arch": "x86_64",
|
||||
|
@ -28,6 +28,18 @@
|
||||
"options": "-nographic -enable-kvm"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "junos-x86-64-20.4R3.8.img",
|
||||
"version": "20.4R3.8-KVM",
|
||||
"md5sum": "69638ba0ad83d7a99a28b658b1dd8def",
|
||||
"filesize": 2773090304
|
||||
},
|
||||
{
|
||||
"filename": "metadata.img",
|
||||
"version": "20.4R3.8-KVM",
|
||||
"md5sum": "ae4e3562aa389929476d82420c79d511",
|
||||
"filesize": 393216
|
||||
},
|
||||
{
|
||||
"filename": "junos-x86-64-20.3R1.8.img",
|
||||
"version": "20.3R1.8-KVM",
|
||||
@ -42,6 +54,13 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "20.4R3.8-KVM",
|
||||
"images": {
|
||||
"hda_disk_image": "junos-x86-64-20.4R3.8.img",
|
||||
"hdb_disk_image": "metadata.img"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "20.3R1.8-KVM",
|
||||
"images": {
|
||||
|
@ -28,81 +28,81 @@
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "chr-7.1rc7.img",
|
||||
"version": "7.1rc7",
|
||||
"md5sum": "04bc0ae1e5fbbda1522135bc57cf6560",
|
||||
"filename": "chr-7.4rc2.img",
|
||||
"version": "7.4rc2",
|
||||
"md5sum": "ddb107c95cc7d231f8d8bbdb4eebdab6",
|
||||
"filesize": 134217728,
|
||||
"download_url": "http://www.mikrotik.com/download",
|
||||
"direct_download_url": "https://download.mikrotik.com/routeros/7.1rc7/chr-7.1rc7.img.zip",
|
||||
"direct_download_url": "https://download.mikrotik.com/routeros/7.4rc2/chr-7.4rc2.img.zip",
|
||||
"compression": "zip"
|
||||
},
|
||||
{
|
||||
"filename": "chr-7.1.img",
|
||||
"version": "7.1",
|
||||
"md5sum": "41545bc7b55717fe5bb1e489ee39ca45",
|
||||
"filename": "chr-7.3.1.img",
|
||||
"version": "7.3.1",
|
||||
"md5sum": "99f8ea75f8b745a8bf5ca3cc1bd325e3",
|
||||
"filesize": 134217728,
|
||||
"download_url": "http://www.mikrotik.com/download",
|
||||
"direct_download_url": "https://download.mikrotik.com/routeros/7.1/chr-7.1.img.zip",
|
||||
"direct_download_url": "https://download.mikrotik.com/routeros/7.3.1/chr-7.3.1.img.zip",
|
||||
"compression": "zip"
|
||||
},
|
||||
{
|
||||
"filename": "chr-6.49rc2.img",
|
||||
"version": "6.49rc2",
|
||||
"md5sum": "e1088f8f64ac3d6ecf2e56ac96261226",
|
||||
"filesize": 67108864,
|
||||
"filename": "chr-7.1.5.img",
|
||||
"version": "7.1.5",
|
||||
"md5sum": "9c0be05f891df2b1400bdae5e719898e",
|
||||
"filesize": 134217728,
|
||||
"download_url": "http://www.mikrotik.com/download",
|
||||
"direct_download_url": "https://download.mikrotik.com/routeros/6.49rc2/chr-6.49rc2.img.zip",
|
||||
"direct_download_url": "https://download.mikrotik.com/routeros/7.1.5/chr-7.1.5.img.zip",
|
||||
"compression": "zip"
|
||||
},
|
||||
{
|
||||
"filename": "chr-6.49.1.img",
|
||||
"version": "6.49.1",
|
||||
"md5sum": "6c896c4c853de99f2ea77f0f4b102261",
|
||||
"filename": "chr-6.49.6.img",
|
||||
"version": "6.49.6",
|
||||
"md5sum": "ae27d38acc9c4dcd875e0f97bcae8d97",
|
||||
"filesize": 67108864,
|
||||
"download_url": "http://www.mikrotik.com/download",
|
||||
"direct_download_url": "https://download.mikrotik.com/routeros/6.49.1/chr-6.49.1.img.zip",
|
||||
"direct_download_url": "https://download.mikrotik.com/routeros/6.49.6/chr-6.49.6.img.zip",
|
||||
"compression": "zip"
|
||||
},
|
||||
{
|
||||
"filename": "chr-6.48.5.img",
|
||||
"version": "6.48.5",
|
||||
"md5sum": "d14debd4cd989f16f695b5b075960703",
|
||||
"filename": "chr-6.48.6.img",
|
||||
"version": "6.48.6",
|
||||
"md5sum": "875574a561570227ff8f395aabe478c6",
|
||||
"filesize": 67108864,
|
||||
"download_url": "http://www.mikrotik.com/download",
|
||||
"direct_download_url": "https://download.mikrotik.com/routeros/6.48.5/chr-6.48.5.img.zip",
|
||||
"direct_download_url": "https://download.mikrotik.com/routeros/6.48.6/chr-6.48.6.img.zip",
|
||||
"compression": "zip"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "7.1rc7",
|
||||
"name": "7.4rc2",
|
||||
"images": {
|
||||
"hda_disk_image": "chr-7.1rc7.img"
|
||||
"hda_disk_image": "chr-7.4rc2.img"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "7.1",
|
||||
"name": "7.3.1",
|
||||
"images": {
|
||||
"hda_disk_image": "chr-7.1.img"
|
||||
"hda_disk_image": "chr-7.3.1.img"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.49rc2",
|
||||
"name": "7.1.5",
|
||||
"images": {
|
||||
"hda_disk_image": "chr-6.49rc2.img"
|
||||
"hda_disk_image": "chr-7.1.5.img"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.49.1",
|
||||
"name": "6.49.6",
|
||||
"images": {
|
||||
"hda_disk_image": "chr-6.49.1.img"
|
||||
"hda_disk_image": "chr-6.49.6.img"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "6.48.5",
|
||||
"name": "6.48.6",
|
||||
"images": {
|
||||
"hda_disk_image": "chr-6.48.5.img"
|
||||
"hda_disk_image": "chr-6.48.6.img"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -29,8 +29,8 @@
|
||||
"version": "0.4",
|
||||
"md5sum": "e678698c97804901c7a53f6b68c8b861",
|
||||
"filesize": 26476544,
|
||||
"download_url": "https://www.b-ehlers.de/projects/netem/index.html",
|
||||
"direct_download_url": "https://www.b-ehlers.de/projects/netem/NETem-v4.qcow2"
|
||||
"download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/",
|
||||
"direct_download_url": "https://sourceforge.net/projects/gns-3/files/Qemu%20Appliances/NETem-v4.qcow2/download"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
|
@ -15,7 +15,7 @@
|
||||
"usage": "Default password is raspberry",
|
||||
"symbol": "rpi.png",
|
||||
"qemu": {
|
||||
"adapter_type": "virtio",
|
||||
"adapter_type": "e1000",
|
||||
"adapters": 1,
|
||||
"ram": 1024,
|
||||
"hda_disk_interface": "sata",
|
||||
@ -25,6 +25,20 @@
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "2022-07-01-raspios-bullseye-i386.iso",
|
||||
"version": "2022-07-01",
|
||||
"md5sum": "e57f42852306ac5ed0d9e97bdc3513cb",
|
||||
"filesize": 3607101440,
|
||||
"download_url": "https://www.raspberrypi.org/downloads/raspberry-pi-desktop/"
|
||||
},
|
||||
{
|
||||
"filename": "2021-01-11-raspios-buster-i386.iso",
|
||||
"version": "2021-01-11",
|
||||
"md5sum": "14c0b29c393adee45b90d265b3243564",
|
||||
"filesize": 3091660800,
|
||||
"download_url": "https://www.raspberrypi.org/downloads/raspberry-pi-desktop/"
|
||||
},
|
||||
{
|
||||
"filename": "2020-02-12-rpd-x86-buster.iso",
|
||||
"version": "2020-02-12",
|
||||
"md5sum": "98f34fb53086752b4c9c452094f30740",
|
||||
@ -41,6 +55,20 @@
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "2022-07-01",
|
||||
"images": {
|
||||
"hda_disk_image": "empty8G.qcow2",
|
||||
"cdrom_image": "2022-07-01-raspios-bullseye-i386.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2021-01-11",
|
||||
"images": {
|
||||
"hda_disk_image": "empty8G.qcow2",
|
||||
"cdrom_image": "2021-01-11-raspios-buster-i386.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "2020-02-12",
|
||||
"images": {
|
||||
"hda_disk_image": "empty8G.qcow2",
|
||||
|
@ -26,6 +26,13 @@
|
||||
"options": "-nographic"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "ubuntu-22.04-server-cloudimg-amd64.img",
|
||||
"version": "22.04 (LTS)",
|
||||
"md5sum": "ac2351289daa173fa1ed6b2b81d81d7c",
|
||||
"filesize": 624295936,
|
||||
"download_url": "https://cloud-images.ubuntu.com/releases/jammy/release/ubuntu-22.04-server-cloudimg-amd64.img"
|
||||
},
|
||||
{
|
||||
"filename": "ubuntu-20.04-server-cloudimg-amd64.img",
|
||||
"version": "20.04 (LTS)",
|
||||
@ -70,6 +77,13 @@
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "22.04 (LTS)",
|
||||
"images": {
|
||||
"hda_disk_image": "ubuntu-22.04-server-cloudimg-amd64.img",
|
||||
"cdrom_image": "ubuntu-cloud-init-data.iso"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "20.04 (LTS)",
|
||||
"images": {
|
||||
|
@ -23,94 +23,55 @@
|
||||
"console_type": "vnc",
|
||||
"boot_priority": "c",
|
||||
"kvm": "require",
|
||||
"options": "-vga virtio"
|
||||
"options": "-vga qxl"
|
||||
},
|
||||
"images": [
|
||||
{
|
||||
"filename": "Ubuntu 20.10 (64bit).vmdk",
|
||||
"version": "20.10",
|
||||
"md5sum": "d7fb9d7b5f6e55349204d493d00507d2",
|
||||
"filesize": 7512915968,
|
||||
"download_url": "http://www.osboxes.org/ubuntu/"
|
||||
"filename": "Ubuntu 22.04 (64bit).vmdk",
|
||||
"version": "22.04",
|
||||
"md5sum": "208657d5c13fd1e041794794ada32581",
|
||||
"filesize": 9110487040,
|
||||
"download_url": "https://www.osboxes.org/ubuntu/"
|
||||
},
|
||||
{
|
||||
"filename": "Ubuntu 20.04.2 (64bit).vmdk",
|
||||
"version": "20.04.2",
|
||||
"md5sum": "e995e5768c1dbee94bc02072d841bb50",
|
||||
"filesize": 7625179136,
|
||||
"download_url": "http://www.osboxes.org/ubuntu/"
|
||||
"filename": "Ubuntu 20.04.4 (64bit).vmdk",
|
||||
"version": "20.04.4",
|
||||
"md5sum": "0482557da31d29f4f175945333e617f0",
|
||||
"filesize": 8531083264,
|
||||
"download_url": "https://www.osboxes.org/ubuntu/"
|
||||
},
|
||||
{
|
||||
"filename": "Ubuntu 20.04 (64bit).vmdk",
|
||||
"version": "20.04",
|
||||
"md5sum": "cf619dfe9bb8d89e2b18b067f02e57a0",
|
||||
"filesize": 6629883904,
|
||||
"download_url": "http://www.osboxes.org/ubuntu/"
|
||||
},
|
||||
{
|
||||
"filename": "Ubuntu 19.04 (64bit).vmdk",
|
||||
"version": "19.04",
|
||||
"md5sum": "21535675c54507e9325bf8774a7bd73e",
|
||||
"filesize": 5558435840,
|
||||
"download_url": "http://www.osboxes.org/ubuntu/"
|
||||
},
|
||||
{
|
||||
"filename": "Ubuntu 18.10 Cosmic (64Bit).vmdk",
|
||||
"version": "18.10",
|
||||
"md5sum": "7f72be569356baa20863cd354d2efa60",
|
||||
"filesize": 6747389952,
|
||||
"download_url": "http://www.osboxes.org/ubuntu/"
|
||||
},
|
||||
{
|
||||
"filename": "Ubuntu 18.04.2 (64bit).vmdk",
|
||||
"version": "18.04.2",
|
||||
"md5sum": "d57b732d90759e3b3a62594a83f8f196",
|
||||
"filesize": 6003097600,
|
||||
"download_url": "http://www.osboxes.org/ubuntu/"
|
||||
"filename": "Ubuntu 18.04.6 (64bit).vmdk",
|
||||
"version": "18.04.6",
|
||||
"md5sum": "2a41138b36edd3f81b4cb89ea471f3fc",
|
||||
"filesize": 7011631104,
|
||||
"download_url": "https://www.osboxes.org/ubuntu/"
|
||||
},
|
||||
{
|
||||
"filename": "Ubuntu 16.04.6 (64bit).vmdk",
|
||||
"version": "16.04.6",
|
||||
"md5sum": "33b2964cef607c1c9fe748db8a2fa6ea",
|
||||
"filesize": 4780982272,
|
||||
"download_url": "http://www.osboxes.org/ubuntu/"
|
||||
"download_url": "https://www.osboxes.org/ubuntu/"
|
||||
}
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"name": "20.10",
|
||||
"name": "22.04",
|
||||
"images": {
|
||||
"hda_disk_image": "Ubuntu 20.10 (64bit).vmdk"
|
||||
"hda_disk_image": "Ubuntu 22.04 (64bit).vmdk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "20.04.2",
|
||||
"name": "20.04.4",
|
||||
"images": {
|
||||
"hda_disk_image": "Ubuntu 20.04.2 (64bit).vmdk"
|
||||
"hda_disk_image": "Ubuntu 20.04.4 (64bit).vmdk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "20.04",
|
||||
"name": "18.04.6",
|
||||
"images": {
|
||||
"hda_disk_image": "Ubuntu 20.04 (64bit).vmdk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "19.04",
|
||||
"images": {
|
||||
"hda_disk_image": "Ubuntu 19.04 (64bit).vmdk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "18.10",
|
||||
"images": {
|
||||
"hda_disk_image": "Ubuntu 18.10 Cosmic (64Bit).vmdk"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "18.04.2",
|
||||
"images": {
|
||||
"hda_disk_image": "Ubuntu 18.04.2 (64bit).vmdk"
|
||||
"hda_disk_image": "Ubuntu 18.04.6 (64bit).vmdk"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -11,7 +11,7 @@
|
||||
"registry_version": 3,
|
||||
"status": "stable",
|
||||
"maintainer": "Mohamad Siblini",
|
||||
"maintainer_email": "https://www.ictkin.com/contact",
|
||||
"maintainer_email": "info@ictkin.com",
|
||||
"usage": "Username: gns3\nPassword: gns3 | MD5: 435f15a54f7f673e302ad26f05226e0e",
|
||||
"port_name_format": "ens{0}",
|
||||
"qemu": {
|
||||
|
@ -33,6 +33,7 @@ from gns3server.utils.asyncio.raw_command_server import AsyncioRawCommandServer
|
||||
from gns3server.utils.asyncio import wait_for_file_creation
|
||||
from gns3server.utils.asyncio import monitor_process
|
||||
from gns3server.utils.get_resource import get_resource
|
||||
from gns3server.utils.hostname import is_rfc1123_hostname_valid
|
||||
|
||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError, UbridgeNamespaceError
|
||||
from ..base_node import BaseNode
|
||||
@ -89,6 +90,9 @@ class DockerVM(BaseNode):
|
||||
cpus=0,
|
||||
):
|
||||
|
||||
if not is_rfc1123_hostname_valid(name):
|
||||
raise DockerError(f"'{name}' is an invalid name to create a Docker node")
|
||||
|
||||
super().__init__(
|
||||
name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type
|
||||
)
|
||||
@ -171,6 +175,18 @@ class DockerVM(BaseNode):
|
||||
return display
|
||||
display += 1
|
||||
|
||||
@BaseNode.name.setter
|
||||
def name(self, new_name):
|
||||
"""
|
||||
Sets the name of this Qemu VM.
|
||||
|
||||
:param new_name: name
|
||||
"""
|
||||
|
||||
if not is_rfc1123_hostname_valid(new_name):
|
||||
raise DockerError(f"'{new_name}' is an invalid name to rename Docker container '{self._name}'")
|
||||
super(DockerVM, DockerVM).name.__set__(self, new_name)
|
||||
|
||||
@property
|
||||
def ethernet_adapters(self):
|
||||
return self._ethernet_adapters
|
||||
@ -358,7 +374,8 @@ class DockerVM(BaseNode):
|
||||
# DHCP config for eth{adapter}
|
||||
#auto eth{adapter}
|
||||
#iface eth{adapter} inet dhcp
|
||||
""".format(adapter=adapter))
|
||||
#\thostname {hostname}
|
||||
""".format(adapter=adapter, hostname=self._name))
|
||||
return path
|
||||
|
||||
async def create(self):
|
||||
@ -385,7 +402,6 @@ class DockerVM(BaseNode):
|
||||
|
||||
params = {
|
||||
"Hostname": self._name,
|
||||
"Name": self._name,
|
||||
"Image": self._image,
|
||||
"NetworkDisabled": True,
|
||||
"Tty": True,
|
||||
@ -654,50 +670,39 @@ 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,
|
||||
"-extension", "MIT-SHM",
|
||||
"-geometry", self._console_resolution,
|
||||
"-depth", "16",
|
||||
"-interface", self._manager.port_manager.console_host,
|
||||
"-rfbport", str(self.console),
|
||||
"-AlwaysShared",
|
||||
"-SecurityTypes", "None",
|
||||
":{}".format(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",
|
||||
"-extension", "MIT-SHM",
|
||||
":{}".format(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",
|
||||
"-noshm",
|
||||
"-geometry", self._console_resolution,
|
||||
"-display", "WAIT:{}".format(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):
|
||||
"""
|
||||
|
@ -37,6 +37,7 @@ from ..dynamips_error import DynamipsError
|
||||
|
||||
from gns3server.utils.file_watcher import FileWatcher
|
||||
from gns3server.utils.asyncio import wait_run_in_executor, monitor_process
|
||||
from gns3server.utils.hostname import is_ios_hostname_valid
|
||||
from gns3server.utils.images import md5sum
|
||||
|
||||
|
||||
@ -75,6 +76,9 @@ class Router(BaseNode):
|
||||
ghost_flag=False,
|
||||
):
|
||||
|
||||
if not is_ios_hostname_valid(name):
|
||||
raise DynamipsError(f"{name} is an invalid name to create a Dynamips node")
|
||||
|
||||
super().__init__(
|
||||
name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type
|
||||
)
|
||||
@ -1653,6 +1657,9 @@ class Router(BaseNode):
|
||||
:param new_name: new name string
|
||||
"""
|
||||
|
||||
if not is_ios_hostname_valid(new_name):
|
||||
raise DynamipsError(f"{new_name} is an invalid name to rename router '{self._name}'")
|
||||
|
||||
await self._hypervisor.send(f'vm rename "{self._name}" "{new_name}"')
|
||||
|
||||
# change the hostname in the startup-config
|
||||
|
@ -42,6 +42,7 @@ from .utils.iou_export import nvram_export
|
||||
from gns3server.compute.ubridge.ubridge_error import UbridgeError
|
||||
from gns3server.utils.file_watcher import FileWatcher
|
||||
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
|
||||
from gns3server.utils.hostname import is_ios_hostname_valid
|
||||
from gns3server.utils.asyncio import locking
|
||||
import gns3server.utils.asyncio
|
||||
import gns3server.utils.images
|
||||
@ -70,6 +71,9 @@ class IOUVM(BaseNode):
|
||||
self, name, node_id, project, manager, application_id=None, path=None, console=None, console_type="telnet"
|
||||
):
|
||||
|
||||
if not is_ios_hostname_valid(name):
|
||||
raise IOUError(f"'{name}' is an invalid name to create an IOU node")
|
||||
|
||||
super().__init__(name, node_id, project, manager, console=console, console_type=console_type)
|
||||
|
||||
log.info(
|
||||
@ -84,6 +88,8 @@ class IOUVM(BaseNode):
|
||||
self._started = False
|
||||
self._nvram_watcher = None
|
||||
self._path = self.manager.get_abs_image_path(path, project.path)
|
||||
self._lib_base = self.manager.get_images_directory()
|
||||
self._loader = None
|
||||
self._license_check = True
|
||||
|
||||
# IOU settings
|
||||
@ -143,6 +149,7 @@ class IOUVM(BaseNode):
|
||||
"""
|
||||
|
||||
self._path = self.manager.get_abs_image_path(path, self.project.path)
|
||||
self._loader = None
|
||||
log.info(f'IOU "{self._name}" [{self._id}]: IOU image updated to "{self._path}"')
|
||||
|
||||
@property
|
||||
@ -174,9 +181,10 @@ class IOUVM(BaseNode):
|
||||
Finds the default RAM and NVRAM values for the IOU image.
|
||||
"""
|
||||
|
||||
await self._check_requirements()
|
||||
try:
|
||||
output = await gns3server.utils.asyncio.subprocess_check_output(
|
||||
self._path, "-h", cwd=self.working_dir, stderr=True
|
||||
*self._loader, self._path, "-h", cwd=self.working_dir, stderr=True
|
||||
)
|
||||
match = re.search(r"-n <n>\s+Size of nvram in Kb \(default ([0-9]+)KB\)", output)
|
||||
if match:
|
||||
@ -191,11 +199,13 @@ class IOUVM(BaseNode):
|
||||
|
||||
await self.update_default_iou_values()
|
||||
|
||||
def _check_requirements(self):
|
||||
async def _check_requirements(self):
|
||||
"""
|
||||
Checks the IOU image.
|
||||
"""
|
||||
|
||||
if self._loader is not None:
|
||||
return # image already checked
|
||||
if not self._path:
|
||||
raise IOUError("IOU image is not configured")
|
||||
if not os.path.isfile(self._path) or not os.path.exists(self._path):
|
||||
@ -219,6 +229,28 @@ class IOUVM(BaseNode):
|
||||
if not os.access(self._path, os.X_OK):
|
||||
raise IOUError(f"IOU image '{self._path}' is not executable")
|
||||
|
||||
# set loader command
|
||||
if elf_header_start[4] == 1:
|
||||
# 32-bit loader
|
||||
loader = os.path.join(self._lib_base, "lib", "ld-linux.so.2")
|
||||
lib_path = (os.path.join(self._lib_base, "lib"),
|
||||
os.path.join(self._lib_base, "lib", "i386-linux-gnu"))
|
||||
else:
|
||||
# 64-bit loader
|
||||
loader = os.path.join(self._lib_base, "lib64", "ld-linux-x86-64.so.2")
|
||||
lib_path = (os.path.join(self._lib_base, "lib64"),
|
||||
os.path.join(self._lib_base, "lib", "x86_64-linux-gnu"))
|
||||
self._loader = []
|
||||
if os.path.isfile(loader):
|
||||
try:
|
||||
proc = await asyncio.create_subprocess_exec(loader, "--verify", self._path)
|
||||
if await proc.wait() == 0:
|
||||
self._loader = [loader, "--library-path", ":".join(lib_path)]
|
||||
else:
|
||||
log.warning(f"Loader {loader} incompatible with '{self._path}'")
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
log.warning(f"Could not use loader {loader}: {e}")
|
||||
|
||||
def asdict(self):
|
||||
|
||||
iou_vm_info = {
|
||||
@ -334,6 +366,8 @@ class IOUVM(BaseNode):
|
||||
:param new_name: name
|
||||
"""
|
||||
|
||||
if not is_ios_hostname_valid(new_name):
|
||||
raise IOUError(f"'{new_name}' is an invalid name to rename IOU node '{self._name}'")
|
||||
if self.startup_config_file:
|
||||
content = self.startup_config_content
|
||||
content = re.sub(r"hostname .+$", "hostname " + new_name, content, flags=re.MULTILINE)
|
||||
@ -385,8 +419,10 @@ class IOUVM(BaseNode):
|
||||
Checks for missing shared library dependencies in the IOU image.
|
||||
"""
|
||||
|
||||
env = os.environ.copy()
|
||||
env["LD_TRACE_LOADED_OBJECTS"] = "1"
|
||||
try:
|
||||
output = await gns3server.utils.asyncio.subprocess_check_output("ldd", self._path)
|
||||
output = await gns3server.utils.asyncio.subprocess_check_output(*self._loader, self._path, env=env)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
log.warning(f"Could not determine the shared library dependencies for {self._path}: {e}")
|
||||
return
|
||||
@ -513,7 +549,7 @@ class IOUVM(BaseNode):
|
||||
Starts the IOU process.
|
||||
"""
|
||||
|
||||
self._check_requirements()
|
||||
await self._check_requirements()
|
||||
if not self.is_running():
|
||||
|
||||
await self._library_check()
|
||||
@ -560,10 +596,13 @@ class IOUVM(BaseNode):
|
||||
|
||||
command = await self._build_command()
|
||||
try:
|
||||
log.info(f"Starting IOU: {command}")
|
||||
if self._loader:
|
||||
log.info(f"Starting IOU: {command} with loader {self._loader}")
|
||||
else:
|
||||
log.info(f"Starting IOU: {command}")
|
||||
self.command_line = " ".join(command)
|
||||
self._iou_process = await asyncio.create_subprocess_exec(
|
||||
*command,
|
||||
*self._loader, *command,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stdin=asyncio.subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
@ -1125,7 +1164,7 @@ class IOUVM(BaseNode):
|
||||
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
|
||||
*self._loader, 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"])
|
||||
|
@ -22,7 +22,6 @@ order to run a QEMU VM.
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import shlex
|
||||
import math
|
||||
import shutil
|
||||
import struct
|
||||
@ -47,6 +46,7 @@ from ..base_node import BaseNode
|
||||
from ...utils.asyncio import monitor_process
|
||||
from ...utils.images import md5sum
|
||||
from ...utils import macaddress_to_int, int_to_macaddress
|
||||
from ...utils.hostname import is_rfc1123_hostname_valid
|
||||
|
||||
from gns3server.schemas.compute.qemu_nodes import Qemu, QemuPlatform
|
||||
|
||||
@ -86,6 +86,9 @@ class QemuVM(BaseNode):
|
||||
platform=None,
|
||||
):
|
||||
|
||||
if not is_rfc1123_hostname_valid(name):
|
||||
raise QemuError(f"'{name}' is an invalid name to create a Qemu node")
|
||||
|
||||
super().__init__(
|
||||
name,
|
||||
node_id,
|
||||
@ -172,6 +175,18 @@ class QemuVM(BaseNode):
|
||||
|
||||
log.info(f'QEMU VM "{self._name}" [{self._id}] has been created')
|
||||
|
||||
@BaseNode.name.setter
|
||||
def name(self, new_name):
|
||||
"""
|
||||
Sets the name of this Qemu VM.
|
||||
|
||||
:param new_name: name
|
||||
"""
|
||||
|
||||
if not is_rfc1123_hostname_valid(new_name):
|
||||
raise QemuError(f"'{new_name}' is an invalid name to rename Qemu node '{self._name}'")
|
||||
super(QemuVM, QemuVM).name.__set__(self, new_name)
|
||||
|
||||
@property
|
||||
def guest_cid(self):
|
||||
"""
|
||||
|
@ -123,7 +123,7 @@ class ApplianceManager:
|
||||
async with HTTPClient.get(image_url) as response:
|
||||
if response.status != 200:
|
||||
raise ControllerError(f"Could not download '{image_name}' due to HTTP error code {response.status}")
|
||||
await write_image(image_name, image_path, response.content.iter_any(), images_repo)
|
||||
await write_image(image_name, image_path, response.content.iter_any(), images_repo, allow_raw_image=True)
|
||||
except (OSError, InvalidImageError) as e:
|
||||
raise ControllerError(f"Could not save {image_type} image '{image_path}': {e}")
|
||||
except ClientError as e:
|
||||
@ -162,7 +162,7 @@ class ApplianceManager:
|
||||
cache_to_md5file=False
|
||||
) == image_checksum:
|
||||
async with aiofiles.open(image_path, "rb") as f:
|
||||
await write_image(appliance_file, image_path, f, images_repo)
|
||||
await write_image(appliance_file, image_path, f, images_repo, allow_raw_image=True)
|
||||
else:
|
||||
# download the image if there is a direct download URL
|
||||
direct_download_url = image.get("direct_download_url")
|
||||
@ -281,7 +281,7 @@ class ApplianceManager:
|
||||
template_data = await self._appliance_to_template(appliance)
|
||||
await self._create_template(template_data, templates_repo, rbac_repo, current_user)
|
||||
|
||||
def load_appliances(self, symbol_theme: str = "Classic") -> None:
|
||||
def load_appliances(self, symbol_theme: str = None) -> None:
|
||||
"""
|
||||
Loads appliance files from disk.
|
||||
"""
|
||||
@ -326,6 +326,8 @@ class ApplianceManager:
|
||||
from . import Controller
|
||||
|
||||
controller = Controller.instance()
|
||||
if not symbol_theme:
|
||||
symbol_theme = controller.symbols.theme
|
||||
category = appliance["category"]
|
||||
if category == "guest":
|
||||
if "docker" in appliance:
|
||||
|
@ -23,7 +23,7 @@ import socket
|
||||
import json
|
||||
import sys
|
||||
import io
|
||||
from operator import itemgetter
|
||||
from fastapi import HTTPException
|
||||
from aiohttp import web
|
||||
|
||||
from ..utils import parse_version
|
||||
@ -576,12 +576,13 @@ class Compute:
|
||||
# If the 409 doesn't come from a GNS3 server
|
||||
except ValueError:
|
||||
raise ControllerError(msg)
|
||||
elif response.status == 500:
|
||||
raise aiohttp.web.HTTPInternalServerError(text=f"Internal server error {url}")
|
||||
elif response.status == 503:
|
||||
raise aiohttp.web.HTTPServiceUnavailable(text=f"Service unavailable {url} {body}")
|
||||
else:
|
||||
raise NotImplementedError(f"{response.status} status code is not supported for {method} '{url}'\n{body}")
|
||||
raise HTTPException(
|
||||
status_code=response.status,
|
||||
detail=f"HTTP error {response.status} received from compute "
|
||||
f"'{self.name}' for request {method} {path}: {msg}"
|
||||
)
|
||||
|
||||
if body and len(body):
|
||||
if raw:
|
||||
response.body = body
|
||||
|
@ -427,6 +427,7 @@ class Node:
|
||||
# When updating properties used only on controller we don't need to call the compute
|
||||
update_compute = False
|
||||
old_json = self.asdict()
|
||||
old_name = self._name
|
||||
|
||||
compute_properties = None
|
||||
# Update node properties with additional elements
|
||||
@ -454,7 +455,13 @@ class Node:
|
||||
self._list_ports()
|
||||
if update_compute:
|
||||
data = self._node_data(properties=compute_properties)
|
||||
response = await self.put(None, data=data)
|
||||
try:
|
||||
response = await self.put(None, data=data)
|
||||
except ComputeConflictError:
|
||||
if old_name != self.name:
|
||||
# special case when the new name is already updated on controller but refused by the compute
|
||||
self.name = old_name
|
||||
raise
|
||||
await self.parse_node_response(response.json)
|
||||
elif old_json != self.asdict():
|
||||
# We send notif only if object has changed
|
||||
|
@ -19,7 +19,7 @@
|
||||
CLASSIC_SYMBOL_THEME = {
|
||||
"cloud": ":/symbols/classic/cloud.svg",
|
||||
"ethernet_switch": ":/symbols/classic/ethernet_switch.svg",
|
||||
"ethernet_hub": ":/symbols/classic/hub.svg",
|
||||
"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",
|
||||
@ -36,8 +36,8 @@ CLASSIC_SYMBOL_THEME = {
|
||||
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",
|
||||
"hub": ":/symbols/affinity/square/blue/hub.svg",
|
||||
"frame_relay_switch": ":/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",
|
||||
@ -53,7 +53,7 @@ AFFINITY_SQUARE_BLUE_SYMBOL_THEME = {
|
||||
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",
|
||||
"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",
|
||||
@ -70,7 +70,7 @@ AFFINITY_SQUARE_RED_SYMBOL_THEME = {
|
||||
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",
|
||||
"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",
|
||||
@ -87,7 +87,7 @@ AFFINITY_SQUARE_GRAY_SYMBOL_THEME = {
|
||||
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",
|
||||
"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",
|
||||
@ -104,7 +104,7 @@ AFFINITY_CIRCLE_BLUE_SYMBOL_THEME = {
|
||||
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",
|
||||
"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",
|
||||
@ -121,7 +121,7 @@ AFFINITY_CIRCLE_RED_SYMBOL_THEME = {
|
||||
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",
|
||||
"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",
|
||||
|
@ -43,7 +43,9 @@ class Symbols:
|
||||
|
||||
# Keep a cache of symbols size
|
||||
self._symbol_size_cache = {}
|
||||
self._current_theme = "Classic"
|
||||
|
||||
self._server_config = Config.instance().settings.Server
|
||||
self._current_theme = self._server_config.default_symbol_theme
|
||||
self._themes = BUILTIN_SYMBOL_THEMES
|
||||
|
||||
@property
|
||||
@ -66,10 +68,11 @@ class Symbols:
|
||||
|
||||
theme = self._themes.get(symbol_theme, None)
|
||||
if not theme:
|
||||
raise ControllerNotFoundError(f"Could not find symbol theme '{symbol_theme}'")
|
||||
log.warning(f"Could not find symbol theme '{symbol_theme}'")
|
||||
return None
|
||||
symbol_path = theme.get(symbol)
|
||||
if symbol_path not in self._symbols_path:
|
||||
log.warning(f"Default symbol {symbol_path} was not found")
|
||||
log.warning(f"Default symbol {symbol} was not found")
|
||||
return None
|
||||
return symbol_path
|
||||
|
||||
@ -125,7 +128,17 @@ class Symbols:
|
||||
|
||||
return self._symbols_path.get(symbol_id)
|
||||
|
||||
def resolve_symbol(self, symbol_name):
|
||||
|
||||
if not symbol_name.startswith(":/"):
|
||||
symbol = self.get_default_symbol(symbol_name, self._current_theme)
|
||||
if symbol:
|
||||
return symbol
|
||||
return symbol_name
|
||||
|
||||
def get_path(self, symbol_id):
|
||||
|
||||
symbol_id = self.resolve_symbol(symbol_id)
|
||||
try:
|
||||
return self._symbols_path[symbol_id]
|
||||
except KeyError:
|
||||
|
@ -59,7 +59,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "https://57f6b1102b6a4985a8e93aed51e19b8b@o19455.ingest.sentry.io/38482"
|
||||
DSN = "https://db7d5c538c3642b281fd27bb2fb6349f@o19455.ingest.sentry.io/38482"
|
||||
_instance = None
|
||||
|
||||
def __init__(self):
|
||||
|
@ -90,9 +90,12 @@ async def get_computes(app: FastAPI) -> List[dict]:
|
||||
|
||||
def image_filter(change: Change, path: str) -> bool:
|
||||
|
||||
if change == Change.added:
|
||||
if change == Change.added and os.path.isfile(path):
|
||||
if path.endswith(".tmp") or path.endswith(".md5sum") or path.startswith("."):
|
||||
return False
|
||||
if "/lib/" in path or "/lib64/" in path:
|
||||
# ignore custom IOU libraries
|
||||
return False
|
||||
header_magic_len = 7
|
||||
with open(path, "rb") as f:
|
||||
image_header = f.read(header_magic_len) # read the first 7 bytes of the file
|
||||
@ -100,10 +103,10 @@ def image_filter(change: Change, path: str) -> bool:
|
||||
try:
|
||||
check_valid_image_header(image_header)
|
||||
except InvalidImageError as e:
|
||||
log.debug(f"New image '{path}' added: {e}")
|
||||
log.debug(f"New image '{path}': {e}")
|
||||
return False
|
||||
else:
|
||||
log.debug(f"New image '{path}' added: size is too small to be valid")
|
||||
log.debug(f"New image '{path}': size is too small to be valid")
|
||||
return False
|
||||
return True
|
||||
# FIXME: should we support image deletion?
|
||||
|
@ -22,7 +22,7 @@ from uuid import UUID
|
||||
from ..common import NodeStatus
|
||||
|
||||
|
||||
class HostInterfaceType(Enum):
|
||||
class HostInterfaceType(str, Enum):
|
||||
|
||||
ethernet = "ethernet"
|
||||
tap = "tap"
|
||||
@ -38,7 +38,7 @@ class HostInterface(BaseModel):
|
||||
special: bool = Field(..., description="Whether the interface is non standard")
|
||||
|
||||
|
||||
class EthernetType(Enum):
|
||||
class EthernetType(str, Enum):
|
||||
ethernet = "ethernet"
|
||||
|
||||
|
||||
@ -53,7 +53,7 @@ class EthernetPort(BaseModel):
|
||||
interface: str
|
||||
|
||||
|
||||
class TAPType(Enum):
|
||||
class TAPType(str, Enum):
|
||||
tap = "tap"
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ class TAPPort(BaseModel):
|
||||
interface: str
|
||||
|
||||
|
||||
class UDPType(Enum):
|
||||
class UDPType(str, Enum):
|
||||
udp = "udp"
|
||||
|
||||
|
||||
@ -85,7 +85,7 @@ class UDPPort(BaseModel):
|
||||
rport: int = Field(..., gt=0, le=65535, description="Remote port")
|
||||
|
||||
|
||||
class CloudConsoleType(Enum):
|
||||
class CloudConsoleType(str, Enum):
|
||||
|
||||
telnet = "telnet"
|
||||
vnc = "vnc"
|
||||
|
@ -22,14 +22,14 @@ from enum import Enum
|
||||
from ..common import NodeStatus
|
||||
|
||||
|
||||
class EthernetSwitchPortType(Enum):
|
||||
class EthernetSwitchPortType(str, Enum):
|
||||
|
||||
access = "access"
|
||||
dot1q = "dot1q"
|
||||
qinq = "qinq"
|
||||
|
||||
|
||||
class EthernetSwitchEtherType(Enum):
|
||||
class EthernetSwitchEtherType(str, Enum):
|
||||
|
||||
ethertype_8021q = "0x8100"
|
||||
ethertype_qinq = "0x88A8"
|
||||
|
@ -22,7 +22,7 @@ from uuid import UUID
|
||||
from ..common import NodeStatus
|
||||
|
||||
|
||||
class HostInterfaceType(Enum):
|
||||
class HostInterfaceType(str, Enum):
|
||||
|
||||
ethernet = "ethernet"
|
||||
tap = "tap"
|
||||
@ -38,7 +38,7 @@ class HostInterface(BaseModel):
|
||||
special: bool = Field(..., description="Whether the interface is non standard")
|
||||
|
||||
|
||||
class EthernetType(Enum):
|
||||
class EthernetType(str, Enum):
|
||||
ethernet = "ethernet"
|
||||
|
||||
|
||||
@ -53,7 +53,7 @@ class EthernetPort(BaseModel):
|
||||
interface: str
|
||||
|
||||
|
||||
class TAPType(Enum):
|
||||
class TAPType(str, Enum):
|
||||
tap = "tap"
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ class TAPPort(BaseModel):
|
||||
interface: str
|
||||
|
||||
|
||||
class UDPType(Enum):
|
||||
class UDPType(str, Enum):
|
||||
udp = "udp"
|
||||
|
||||
|
||||
|
@ -20,7 +20,7 @@ from typing import Optional
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class UDPNIOType(Enum):
|
||||
class UDPNIOType(str, Enum):
|
||||
|
||||
udp = "nio_udp"
|
||||
|
||||
@ -38,7 +38,7 @@ class UDPNIO(BaseModel):
|
||||
filters: Optional[dict] = Field(None, description="Packet filters")
|
||||
|
||||
|
||||
class EthernetNIOType(Enum):
|
||||
class EthernetNIOType(str, Enum):
|
||||
|
||||
ethernet = "nio_ethernet"
|
||||
|
||||
@ -52,7 +52,7 @@ class EthernetNIO(BaseModel):
|
||||
ethernet_device: str = Field(..., description="Ethernet device name e.g. eth0")
|
||||
|
||||
|
||||
class TAPNIOType(Enum):
|
||||
class TAPNIOType(str, Enum):
|
||||
|
||||
tap = "nio_tap"
|
||||
|
||||
|
@ -109,6 +109,17 @@ class ServerProtocol(str, Enum):
|
||||
https = "https"
|
||||
|
||||
|
||||
class BuiltinSymbolTheme(str, Enum):
|
||||
|
||||
classic = "Classic"
|
||||
affinity_square_blue = "Affinity-square-blue"
|
||||
affinity_square_red = "Affinity-square-red"
|
||||
affinity_square_gray = "Affinity-square-gray"
|
||||
affinity_circle_blue = "Affinity-circle-blue"
|
||||
affinity_circle_red = "Affinity-circle-red"
|
||||
affinity_circle_gray = "Affinity-circle-gray"
|
||||
|
||||
|
||||
class ServerSettings(BaseModel):
|
||||
|
||||
local: bool = False
|
||||
@ -124,6 +135,7 @@ class ServerSettings(BaseModel):
|
||||
appliances_path: str = "~/GNS3/appliances"
|
||||
symbols_path: str = "~/GNS3/symbols"
|
||||
configs_path: str = "~/GNS3/configs"
|
||||
default_symbol_theme: BuiltinSymbolTheme = BuiltinSymbolTheme.affinity_square_blue
|
||||
report_errors: bool = True
|
||||
additional_images_paths: List[str] = Field(default_factory=list)
|
||||
console_start_port_range: int = Field(5000, gt=0, le=65535)
|
||||
@ -138,6 +150,7 @@ class ServerSettings(BaseModel):
|
||||
allowed_interfaces: List[str] = Field(default_factory=list)
|
||||
default_nat_interface: str = None
|
||||
allow_remote_console: bool = False
|
||||
enable_builtin_templates: bool = True
|
||||
|
||||
@validator("additional_images_paths", pre=True)
|
||||
def split_additional_images_paths(cls, v):
|
||||
|
@ -22,7 +22,7 @@ from uuid import UUID
|
||||
from pydantic import AnyUrl, BaseModel, EmailStr, Field, confloat, conint, constr
|
||||
|
||||
|
||||
class Category(Enum):
|
||||
class Category(str, Enum):
|
||||
|
||||
router = 'router'
|
||||
multilayer_switch = 'multilayer_switch'
|
||||
@ -31,7 +31,7 @@ class Category(Enum):
|
||||
guest = 'guest'
|
||||
|
||||
|
||||
class RegistryVersion(Enum):
|
||||
class RegistryVersion(int, Enum):
|
||||
|
||||
version1 = 1
|
||||
version2 = 2
|
||||
@ -41,14 +41,14 @@ class RegistryVersion(Enum):
|
||||
version6 = 6
|
||||
|
||||
|
||||
class Status(Enum):
|
||||
class Status(str, Enum):
|
||||
|
||||
stable = 'stable'
|
||||
experimental = 'experimental'
|
||||
broken = 'broken'
|
||||
|
||||
|
||||
class Availability(Enum):
|
||||
class Availability(str, Enum):
|
||||
|
||||
free = 'free'
|
||||
with_registration = 'with-registration'
|
||||
@ -56,7 +56,7 @@ class Availability(Enum):
|
||||
service_contract = 'service-contract'
|
||||
|
||||
|
||||
class ConsoleType(Enum):
|
||||
class ConsoleType(str, Enum):
|
||||
|
||||
telnet = 'telnet'
|
||||
vnc = 'vnc'
|
||||
@ -101,7 +101,7 @@ class Iou(BaseModel):
|
||||
startup_config: str = Field(..., title='Config loaded at startup')
|
||||
|
||||
|
||||
class Chassis(Enum):
|
||||
class Chassis(str, Enum):
|
||||
|
||||
chassis_1720 = '1720'
|
||||
chassis_1721 = '1721'
|
||||
@ -122,7 +122,7 @@ class Chassis(Enum):
|
||||
chassis_3660 = '3660'
|
||||
|
||||
|
||||
class Platform(Enum):
|
||||
class Platform(str, Enum):
|
||||
|
||||
c1700 = 'c1700'
|
||||
c2600 = 'c2600'
|
||||
@ -133,13 +133,13 @@ class Platform(Enum):
|
||||
c7200 = 'c7200'
|
||||
|
||||
|
||||
class Midplane(Enum):
|
||||
class Midplane(str, Enum):
|
||||
|
||||
std = 'std'
|
||||
vxr = 'vxr'
|
||||
|
||||
|
||||
class Npe(Enum):
|
||||
class Npe(str, Enum):
|
||||
|
||||
npe_100 = 'npe-100'
|
||||
npe_150 = 'npe-150'
|
||||
@ -151,7 +151,7 @@ class Npe(Enum):
|
||||
npe_g2 = 'npe-g2'
|
||||
|
||||
|
||||
class AdapterType(Enum):
|
||||
class AdapterType(str, Enum):
|
||||
|
||||
e1000 = 'e1000'
|
||||
e1000_82544gc = 'e1000-82544gc'
|
||||
@ -179,7 +179,7 @@ class AdapterType(Enum):
|
||||
vmxnet3 = 'vmxnet3'
|
||||
|
||||
|
||||
class DiskInterface(Enum):
|
||||
class DiskInterface(str, Enum):
|
||||
|
||||
ide = 'ide'
|
||||
sata = 'sata'
|
||||
@ -193,7 +193,7 @@ class DiskInterface(Enum):
|
||||
none = 'none'
|
||||
|
||||
|
||||
class Arch(Enum):
|
||||
class Arch(str, Enum):
|
||||
|
||||
aarch64 = 'aarch64'
|
||||
alpha = 'alpha'
|
||||
@ -225,7 +225,7 @@ class Arch(Enum):
|
||||
xtensaeb = 'xtensaeb'
|
||||
|
||||
|
||||
class ConsoleType1(Enum):
|
||||
class ConsoleType1(str, Enum):
|
||||
|
||||
telnet = 'telnet'
|
||||
vnc = 'vnc'
|
||||
@ -234,7 +234,7 @@ class ConsoleType1(Enum):
|
||||
none = 'none'
|
||||
|
||||
|
||||
class BootPriority(Enum):
|
||||
class BootPriority(str, Enum):
|
||||
|
||||
c = 'c'
|
||||
d = 'd'
|
||||
@ -247,14 +247,14 @@ class BootPriority(Enum):
|
||||
nd = 'nd'
|
||||
|
||||
|
||||
class Kvm(Enum):
|
||||
class Kvm(str, Enum):
|
||||
|
||||
require = 'require'
|
||||
allow = 'allow'
|
||||
disable = 'disable'
|
||||
|
||||
|
||||
class ProcessPriority(Enum):
|
||||
class ProcessPriority(str, Enum):
|
||||
|
||||
realtime = 'realtime'
|
||||
very_high = 'very high'
|
||||
@ -306,7 +306,7 @@ class Qemu(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class Compression(Enum):
|
||||
class Compression(str, Enum):
|
||||
|
||||
bzip2 = 'bzip2'
|
||||
gzip = 'gzip'
|
||||
@ -355,7 +355,7 @@ class ApplianceVersion(BaseModel):
|
||||
images: Optional[ApplianceVersionImages] = Field(None, title='Images used for this version')
|
||||
|
||||
|
||||
class DynamipsSlot(Enum):
|
||||
class DynamipsSlot(str, Enum):
|
||||
|
||||
C7200_IO_2FE = 'C7200-IO-2FE'
|
||||
C7200_IO_FE = 'C7200-IO-FE'
|
||||
@ -385,7 +385,7 @@ class DynamipsSlot(Enum):
|
||||
_ = ''
|
||||
|
||||
|
||||
class DynamipsWic(Enum):
|
||||
class DynamipsWic(str, Enum):
|
||||
|
||||
WIC_1ENET = 'WIC-1ENET'
|
||||
WIC_1T = 'WIC-1T'
|
||||
@ -422,7 +422,7 @@ class Appliance(BaseModel):
|
||||
..., title='Description of the appliance. Could be a marketing description'
|
||||
)
|
||||
vendor_name: str = Field(..., title='Name of the vendor')
|
||||
vendor_url: Union[AnyUrl, constr(max_length=0)] = Field(..., title='Website of the vendor')
|
||||
vendor_url: Optional[Union[AnyUrl, constr(max_length=0)]] = Field(None, title='Website of the vendor')
|
||||
documentation_url: Optional[Union[AnyUrl, constr(max_length=0)]] = Field(
|
||||
None,
|
||||
title='An optional documentation for using the appliance on vendor website',
|
||||
@ -440,7 +440,7 @@ class Appliance(BaseModel):
|
||||
title='About image availability: can be downloaded directly; download requires a free registration; paid but a trial version (time or feature limited) is available; not available publicly',
|
||||
)
|
||||
maintainer: str = Field(..., title='Maintainer name')
|
||||
maintainer_email: Union[EmailStr, constr(max_length=0)] = Field(..., title='Maintainer email')
|
||||
maintainer_email: Optional[Union[EmailStr, constr(max_length=0)]] = Field(None, title='Maintainer email')
|
||||
usage: Optional[str] = Field(None, title='How to use the appliance')
|
||||
symbol: Optional[str] = Field(None, title='An optional symbol for the appliance')
|
||||
first_port_name: Optional[str] = Field(
|
||||
|
@ -163,6 +163,7 @@ class ComputeVMwareVM(BaseModel):
|
||||
"""
|
||||
|
||||
vmname: str = Field(..., description="VMware VM name")
|
||||
vmx_path: str = Field(..., description="Path to the vmx file")
|
||||
|
||||
|
||||
class ComputeDockerImage(BaseModel):
|
||||
|
@ -31,7 +31,7 @@ class CloudTemplate(TemplateBase):
|
||||
|
||||
category: Optional[Category] = "guest"
|
||||
default_name_format: Optional[str] = "Cloud{0}"
|
||||
symbol: Optional[str] = ":/symbols/cloud.svg"
|
||||
symbol: Optional[str] = "cloud"
|
||||
ports_mapping: List[Union[EthernetPort, TAPPort, UDPPort]] = Field(default_factory=list)
|
||||
remote_console_host: Optional[str] = Field("127.0.0.1", description="Remote console host or IP")
|
||||
remote_console_port: Optional[int] = Field(23, gt=0, le=65535, description="Remote console TCP port")
|
||||
|
@ -26,7 +26,7 @@ class DockerTemplate(TemplateBase):
|
||||
|
||||
category: Optional[Category] = "guest"
|
||||
default_name_format: Optional[str] = "{name}-{0}"
|
||||
symbol: Optional[str] = ":/symbols/docker_guest.svg"
|
||||
symbol: Optional[str] = "docker_guest"
|
||||
image: str = Field(..., description="Docker image name")
|
||||
adapters: Optional[int] = Field(1, ge=0, le=100, description="Number of adapters")
|
||||
start_command: Optional[str] = Field("", description="Docker CMD entry")
|
||||
|
@ -34,7 +34,7 @@ class DynamipsTemplate(TemplateBase):
|
||||
|
||||
category: Optional[Category] = "router"
|
||||
default_name_format: Optional[str] = "R{0}"
|
||||
symbol: Optional[str] = ":/symbols/router.svg"
|
||||
symbol: Optional[str] = "router"
|
||||
platform: DynamipsPlatform = Field(..., description="Cisco router platform")
|
||||
image: str = Field(..., description="Path to the IOS image")
|
||||
exec_area: Optional[int] = Field(64, description="Exec area value")
|
||||
|
@ -37,7 +37,7 @@ class EthernetHubTemplate(TemplateBase):
|
||||
|
||||
category: Optional[Category] = "switch"
|
||||
default_name_format: Optional[str] = "Hub{0}"
|
||||
symbol: Optional[str] = ":/symbols/hub.svg"
|
||||
symbol: Optional[str] = "hub"
|
||||
ports_mapping: Optional[List[EthernetHubPort]] = Field(DEFAULT_PORTS, description="Ports")
|
||||
|
||||
|
||||
|
@ -47,7 +47,7 @@ class EthernetSwitchTemplate(TemplateBase):
|
||||
|
||||
category: Optional[Category] = "switch"
|
||||
default_name_format: Optional[str] = "Switch{0}"
|
||||
symbol: Optional[str] = ":/symbols/ethernet_switch.svg"
|
||||
symbol: Optional[str] = "ethernet_switch"
|
||||
ports_mapping: Optional[List[EthernetSwitchPort]] = Field(DEFAULT_PORTS, description="Ports")
|
||||
console_type: Optional[ConsoleType] = Field("none", description="Console type")
|
||||
|
||||
|
@ -26,7 +26,7 @@ class IOUTemplate(TemplateBase):
|
||||
|
||||
category: Optional[Category] = "router"
|
||||
default_name_format: Optional[str] = "IOU{0}"
|
||||
symbol: Optional[str] = ":/symbols/multilayer_switch.svg"
|
||||
symbol: Optional[str] = "multilayer_switch"
|
||||
path: str = Field(..., description="Path of IOU executable")
|
||||
ethernet_adapters: Optional[int] = Field(2, description="Number of ethernet adapters")
|
||||
serial_adapters: Optional[int] = Field(2, description="Number of serial adapters")
|
||||
|
@ -35,7 +35,7 @@ class QemuTemplate(TemplateBase):
|
||||
|
||||
category: Optional[Category] = "guest"
|
||||
default_name_format: Optional[str] = "{name}-{0}"
|
||||
symbol: Optional[str] = ":/symbols/qemu_guest.svg"
|
||||
symbol: Optional[str] = "qemu_guest"
|
||||
qemu_path: Optional[str] = Field("", description="Qemu executable path")
|
||||
platform: Optional[QemuPlatform] = Field("x86_64", description="Platform to emulate")
|
||||
linked_clone: Optional[bool] = Field(True, description="Whether the VM is a linked clone or not")
|
||||
|
@ -30,7 +30,7 @@ class VirtualBoxTemplate(TemplateBase):
|
||||
|
||||
category: Optional[Category] = "guest"
|
||||
default_name_format: Optional[str] = "{name}-{0}"
|
||||
symbol: Optional[str] = ":/symbols/vbox_guest.svg"
|
||||
symbol: Optional[str] = "vbox_guest"
|
||||
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")
|
||||
|
@ -31,7 +31,7 @@ class VMwareTemplate(TemplateBase):
|
||||
|
||||
category: Optional[Category] = "guest"
|
||||
default_name_format: Optional[str] = "{name}-{0}"
|
||||
symbol: Optional[str] = ":/symbols/vmware_guest.svg"
|
||||
symbol: Optional[str] = "vmware_guest"
|
||||
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")
|
||||
|
@ -26,7 +26,7 @@ class VPCSTemplate(TemplateBase):
|
||||
|
||||
category: Optional[Category] = "guest"
|
||||
default_name_format: Optional[str] = "PC{0}"
|
||||
symbol: Optional[str] = ":/symbols/vpcs_guest.svg"
|
||||
symbol: Optional[str] = "vpcs_guest"
|
||||
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(
|
||||
|
@ -23,6 +23,7 @@ from fastapi.encoders import jsonable_encoder
|
||||
from typing import List
|
||||
|
||||
from gns3server import schemas
|
||||
from gns3server.config import Config
|
||||
import gns3server.db.models as models
|
||||
from gns3server.db.repositories.templates import TemplatesRepository
|
||||
from gns3server.controller.controller_error import (
|
||||
@ -85,7 +86,7 @@ BUILTIN_TEMPLATES = [
|
||||
"name": "Cloud",
|
||||
"default_name_format": "Cloud{0}",
|
||||
"category": "guest",
|
||||
"symbol": ":/symbols/cloud.svg",
|
||||
"symbol": "cloud",
|
||||
"compute_id": None,
|
||||
"builtin": True,
|
||||
},
|
||||
@ -95,7 +96,7 @@ BUILTIN_TEMPLATES = [
|
||||
"name": "NAT",
|
||||
"default_name_format": "NAT{0}",
|
||||
"category": "guest",
|
||||
"symbol": ":/symbols/cloud.svg",
|
||||
"symbol": "cloud",
|
||||
"compute_id": None,
|
||||
"builtin": True,
|
||||
},
|
||||
@ -105,7 +106,7 @@ BUILTIN_TEMPLATES = [
|
||||
"name": "VPCS",
|
||||
"default_name_format": "PC{0}",
|
||||
"category": "guest",
|
||||
"symbol": ":/symbols/vpcs_guest.svg",
|
||||
"symbol": "vpcs_guest",
|
||||
"base_script_file": "vpcs_base_config.txt",
|
||||
"compute_id": None,
|
||||
"builtin": True,
|
||||
@ -117,7 +118,7 @@ BUILTIN_TEMPLATES = [
|
||||
"console_type": "none",
|
||||
"default_name_format": "Switch{0}",
|
||||
"category": "switch",
|
||||
"symbol": ":/symbols/ethernet_switch.svg",
|
||||
"symbol": "ethernet_switch",
|
||||
"compute_id": None,
|
||||
"builtin": True,
|
||||
},
|
||||
@ -127,7 +128,7 @@ BUILTIN_TEMPLATES = [
|
||||
"name": "Ethernet hub",
|
||||
"default_name_format": "Hub{0}",
|
||||
"category": "switch",
|
||||
"symbol": ":/symbols/hub.svg",
|
||||
"symbol": "hub",
|
||||
"compute_id": None,
|
||||
"builtin": True,
|
||||
},
|
||||
@ -137,7 +138,7 @@ BUILTIN_TEMPLATES = [
|
||||
"name": "Frame Relay switch",
|
||||
"default_name_format": "FRSW{0}",
|
||||
"category": "switch",
|
||||
"symbol": ":/symbols/frame_relay_switch.svg",
|
||||
"symbol": "frame_relay_switch",
|
||||
"compute_id": None,
|
||||
"builtin": True,
|
||||
},
|
||||
@ -147,7 +148,7 @@ BUILTIN_TEMPLATES = [
|
||||
"name": "ATM switch",
|
||||
"default_name_format": "ATMSW{0}",
|
||||
"category": "switch",
|
||||
"symbol": ":/symbols/atm_switch.svg",
|
||||
"symbol": "atm_switch",
|
||||
"compute_id": None,
|
||||
"builtin": True,
|
||||
},
|
||||
@ -162,6 +163,10 @@ class TemplatesService:
|
||||
from gns3server.controller import Controller
|
||||
self._controller = Controller.instance()
|
||||
|
||||
# resolve built-in template symbols
|
||||
for builtin_template in BUILTIN_TEMPLATES:
|
||||
builtin_template["symbol"] = self._controller.symbols.resolve_symbol(builtin_template["symbol"])
|
||||
|
||||
def get_builtin_template(self, template_id: UUID) -> dict:
|
||||
|
||||
for builtin_template in BUILTIN_TEMPLATES:
|
||||
@ -174,8 +179,9 @@ class TemplatesService:
|
||||
db_templates = await self._templates_repo.get_templates()
|
||||
for db_template in db_templates:
|
||||
templates.append(db_template.asjson())
|
||||
for builtin_template in BUILTIN_TEMPLATES:
|
||||
templates.append(jsonable_encoder(builtin_template))
|
||||
if Config.instance().settings.Server.enable_builtin_templates:
|
||||
for builtin_template in BUILTIN_TEMPLATES:
|
||||
templates.append(jsonable_encoder(builtin_template))
|
||||
return templates
|
||||
|
||||
async def _find_image(self, image_path: str):
|
||||
@ -239,6 +245,8 @@ class TemplatesService:
|
||||
except pydantic.ValidationError as e:
|
||||
raise ControllerBadRequestError(f"JSON schema error received while creating new template: {e}")
|
||||
|
||||
# resolve the template symbol
|
||||
template_settings["symbol"] = self._controller.symbols.resolve_symbol(template_settings["symbol"])
|
||||
images_to_add_to_template = await self._find_images(template_create.template_type, template_settings)
|
||||
db_template = await self._templates_repo.create_template(template_create.template_type, template_settings)
|
||||
for image in images_to_add_to_template:
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,28 +1,3 @@
|
||||
@angular-devkit/build-angular
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2017 Google, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
@angular/animations
|
||||
MIT
|
||||
|
||||
@ -30,7 +5,7 @@ MIT
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2021 Google LLC.
|
||||
Copyright (c) 2022 Google LLC.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@ -67,7 +42,7 @@ MIT
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2021 Google LLC.
|
||||
Copyright (c) 2022 Google LLC.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@ -419,32 +394,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
core-js
|
||||
MIT
|
||||
Copyright (c) 2014-2021 Denis Pushkarev
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
css-tree
|
||||
MIT
|
||||
Copyright (C) 2016-2019 by Roman Dvornov
|
||||
Copyright (C) 2016-2022 by Roman Dvornov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@ -1542,124 +1494,52 @@ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
|
||||
mdn-data
|
||||
CC0-1.0
|
||||
CC0 1.0 Universal
|
||||
marked
|
||||
MIT
|
||||
# License information
|
||||
|
||||
Statement of Purpose
|
||||
## Contribution License Agreement
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator and
|
||||
subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
If you contribute code to this project, you are implicitly allowing your code
|
||||
to be distributed under the MIT license. You are also implicitly verifying that
|
||||
all code is your original work. `</legalese>`
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for the
|
||||
purpose of contributing to a commons of creative, cultural and scientific
|
||||
works ("Commons") that the public can reliably and without fear of later
|
||||
claims of infringement build upon, modify, incorporate in other works, reuse
|
||||
and redistribute as freely as possible in any form whatsoever and for any
|
||||
purposes, including without limitation commercial purposes. These owners may
|
||||
contribute to the Commons to promote the ideal of a free culture and the
|
||||
further production of creative, cultural and scientific works, or to gain
|
||||
reputation or greater distribution for their Work in part through the use and
|
||||
efforts of others.
|
||||
## Marked
|
||||
|
||||
For these and/or other purposes and motivations, and without any expectation
|
||||
of additional consideration or compensation, the person associating CC0 with a
|
||||
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
|
||||
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
|
||||
and publicly distribute the Work under its terms, with knowledge of his or her
|
||||
Copyright and Related Rights in the Work and the meaning and intended legal
|
||||
effect of CC0 on those rights.
|
||||
Copyright (c) 2018+, MarkedJS (https://github.com/markedjs/)
|
||||
Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not limited
|
||||
to, the following:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display, communicate,
|
||||
and translate a Work;
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
iii. publicity and privacy rights pertaining to a person's image or likeness
|
||||
depicted in a Work;
|
||||
## Markdown
|
||||
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
Copyright © 2004, John Gruber
|
||||
http://daringfireball.net/
|
||||
All rights reserved.
|
||||
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data in
|
||||
a Work;
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation thereof,
|
||||
including any amended or successor version of such directive); and
|
||||
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
* Neither the name “Markdown” nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
||||
|
||||
vii. other similar, equivalent or corresponding rights throughout the world
|
||||
based on applicable law or treaty, and any national implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention of,
|
||||
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
|
||||
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
|
||||
and Related Rights and associated claims and causes of action, whether now
|
||||
known or unknown (including existing as well as future claims and causes of
|
||||
action), in the Work (i) in all territories worldwide, (ii) for the maximum
|
||||
duration provided by applicable law or treaty (including future time
|
||||
extensions), (iii) in any current or future medium and for any number of
|
||||
copies, and (iv) for any purpose whatsoever, including without limitation
|
||||
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
|
||||
the Waiver for the benefit of each member of the public at large and to the
|
||||
detriment of Affirmer's heirs and successors, fully intending that such Waiver
|
||||
shall not be subject to revocation, rescission, cancellation, termination, or
|
||||
any other legal or equitable action to disrupt the quiet enjoyment of the Work
|
||||
by the public as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason be
|
||||
judged legally invalid or ineffective under applicable law, then the Waiver
|
||||
shall be preserved to the maximum extent permitted taking into account
|
||||
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
|
||||
is so judged Affirmer hereby grants to each affected person a royalty-free,
|
||||
non transferable, non sublicensable, non exclusive, irrevocable and
|
||||
unconditional license to exercise Affirmer's Copyright and Related Rights in
|
||||
the Work (i) in all territories worldwide, (ii) for the maximum duration
|
||||
provided by applicable law or treaty (including future time extensions), (iii)
|
||||
in any current or future medium and for any number of copies, and (iv) for any
|
||||
purpose whatsoever, including without limitation commercial, advertising or
|
||||
promotional purposes (the "License"). The License shall be deemed effective as
|
||||
of the date CC0 was applied by Affirmer to the Work. Should any part of the
|
||||
License for any reason be judged legally invalid or ineffective under
|
||||
applicable law, such partial invalidity or ineffectiveness shall not
|
||||
invalidate the remainder of the License, and in such case Affirmer hereby
|
||||
affirms that he or she will not (i) exercise any of his or her remaining
|
||||
Copyright and Related Rights in the Work or (ii) assert any associated claims
|
||||
and causes of action with respect to the Work, in either case contrary to
|
||||
Affirmer's express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
|
||||
b. Affirmer offers the Work as-is and makes no representations or warranties
|
||||
of any kind concerning the Work, express, implied, statutory or otherwise,
|
||||
including without limitation warranties of title, merchantability, fitness
|
||||
for a particular purpose, non infringement, or the absence of latent or
|
||||
other defects, accuracy, or the present or absence of errors, whether or not
|
||||
discoverable, all to the greatest extent permissible under applicable law.
|
||||
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without limitation
|
||||
any person's Copyright and Related Rights in the Work. Further, Affirmer
|
||||
disclaims responsibility for obtaining any necessary consents, permissions
|
||||
or other rights required for any use of the Work.
|
||||
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to this
|
||||
CC0 or use of the Work.
|
||||
|
||||
For more information, please see
|
||||
<http://creativecommons.org/publicdomain/zero/1.0/>
|
||||
This software is provided by the copyright holders and contributors “as is” and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner or contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage.
|
||||
|
||||
|
||||
mousetrap
|
||||
@ -2079,31 +1959,6 @@ Apache-2.0
|
||||
limitations under the License.
|
||||
|
||||
|
||||
regenerator-runtime
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014-present, Facebook, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
rxjs
|
||||
Apache-2.0
|
||||
Apache License
|
||||
@ -2542,7 +2397,7 @@ THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
source-map
|
||||
source-map-js
|
||||
BSD-3-Clause
|
||||
|
||||
Copyright (c) 2009-2011, Mozilla Foundation and contributors
|
||||
@ -2621,25 +2476,13 @@ uuid
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2010-2016 Robert Kieffer and other contributors
|
||||
Copyright (c) 2010-2020 Robert Kieffer and other contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
|
||||
xterm
|
||||
@ -2717,7 +2560,7 @@ zone.js
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2010-2020 Google LLC. https://angular.io/license
|
||||
Copyright (c) 2010-2022 Google LLC. https://angular.io/license
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
1
gns3server/static/web-ui/465.1c1bfd214c8e7f59.js
Normal file
1
gns3server/static/web-ui/465.1c1bfd214c8e7f59.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
Binary file not shown.
BIN
gns3server/static/web-ui/NotoSans-Bold.635e9291df1a5a00.woff2
Normal file
BIN
gns3server/static/web-ui/NotoSans-Bold.635e9291df1a5a00.woff2
Normal file
Binary file not shown.
Binary file not shown.
BIN
gns3server/static/web-ui/NotoSans-Bold.885427cced6d8c94.woff
Normal file
BIN
gns3server/static/web-ui/NotoSans-Bold.885427cced6d8c94.woff
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user