Refactor tests and start work on database integration.

pull/1906/head
grossmj 4 years ago
parent ae55c0ec9c
commit bf7cf862af

@ -1,34 +1,22 @@
FROM ubuntu:20.04
FROM python:3.6-alpine3.11
ENV DEBIAN_FRONTEND noninteractive
WORKDIR /gns3server
# Set the locale
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONBUFFERED 1
RUN apt-get update && apt-get install -y software-properties-common
RUN add-apt-repository ppa:gns3/ppa
RUN apt-get update && apt-get install -y \
git \
locales \
python3-pip \
python3-dev \
qemu-system-x86 \
qemu-kvm \
libvirt-daemon-system \
x11vnc
COPY ./requirements.txt /gns3server/requirements.txt
RUN locale-gen en_US.UTF-8
RUN set -eux \
&& apk add --no-cache --virtual .build-deps build-base \
gcc libc-dev musl-dev linux-headers python3-dev \
vpcs qemu libvirt ubridge \
&& pip install --upgrade pip setuptools wheel \
&& pip install -r /gns3server/requirements.txt \
&& rm -rf /root/.cache/pip
# Install uninstall to install dependencies
RUN apt-get install -y vpcs ubridge
ADD . /server
WORKDIR /server
RUN pip3 install -r /server/requirements.txt
EXPOSE 3080
CMD python3 -m gns3server
COPY . /gns3server
RUN python3 setup.py install

@ -4,5 +4,6 @@ pytest==6.1.2
flake8==3.8.4
pytest-timeout==1.4.2
pytest-asyncio==0.14.0
asgi-lifespan==1.0.1
requests==2.24.0
httpx==0.16.1

@ -0,0 +1,15 @@
version: '3.7'
services:
gns3server:
privileged: true
build:
context: .
dockerfile: Dockerfile
volumes:
- ./gns3server:/server/
- /var/run/docker.sock:/var/run/docker.sock
command: python3 -m gns3server --local --port 3080
ports:
- 3080:3080
- 5000-5100:5000-5100

@ -35,7 +35,7 @@ router = APIRouter()
@router.get("/capabilities",
response_model=schemas.Capabilities
)
def get_compute_capabilities():
def get_capabilities():
node_types = []
for module in MODULES:

@ -104,7 +104,7 @@ def update_cloud(node_data: schemas.CloudUpdate, node: Cloud = Depends(dep_node)
@router.delete("/{node_id}",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def delete_node(node: Cloud = Depends(dep_node)):
async def delete_cloud(node: Cloud = Depends(dep_node)):
"""
Delete a cloud node.
"""
@ -151,10 +151,10 @@ async def suspend_cloud(node: Cloud = Depends(dep_node)):
status_code=status.HTTP_201_CREATED,
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
responses=responses)
async def create_nio(adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Cloud = Depends(dep_node)):
async def create_cloud_nio(adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Cloud = Depends(dep_node)):
"""
Add a NIO (Network Input/Output) to the node.
The adapter number on the cloud is always 0.
@ -169,10 +169,10 @@ async def create_nio(adapter_number: int,
status_code=status.HTTP_201_CREATED,
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
responses=responses)
async def update_nio(adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Cloud = Depends(dep_node)):
async def update_cloud_nio(adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Cloud = Depends(dep_node)):
"""
Update a NIO (Network Input/Output) to the node.
The adapter number on the cloud is always 0.
@ -188,7 +188,7 @@ async def update_nio(adapter_number: int,
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def delete_nio(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)):
async def delete_cloud_nio(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)):
"""
Remove a NIO (Network Input/Output) from the node.
The adapter number on the cloud is always 0.
@ -199,10 +199,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: Cloud = Depend
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
responses=responses)
async def start_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: Cloud = Depends(dep_node)):
async def start_cloud_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: Cloud = Depends(dep_node)):
"""
Start a packet capture on the node.
The adapter number on the cloud is always 0.
@ -216,7 +216,7 @@ async def start_capture(adapter_number: int,
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def stop_capture(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)):
async def stop_cloud_capture(adapter_number: int, port_number: int, node: Cloud = Depends(dep_node)):
"""
Stop a packet capture on the node.
The adapter number on the cloud is always 0.

@ -78,7 +78,7 @@ def network_ports() -> dict:
@router.get("/version")
def version() -> dict:
def compute_version() -> dict:
"""
Retrieve the server version number.
"""
@ -89,7 +89,7 @@ def version() -> dict:
@router.get("/statistics")
def statistics() -> dict:
def compute_statistics() -> dict:
"""
Retrieve the server version number.
"""
@ -124,19 +124,19 @@ def statistics() -> dict:
@router.get("/qemu/binaries")
async def get_binaries(archs: Optional[List[str]] = Body(None, embed=True)):
async def get_qemu_binaries(archs: Optional[List[str]] = Body(None, embed=True)):
return await Qemu.binary_list(archs)
@router.get("/qemu/img-binaries")
async def get_img_binaries():
async def get_image_binaries():
return await Qemu.img_binary_list()
@router.get("/qemu/capabilities")
async def get_capabilities() -> dict:
async def get_qemu_capabilities() -> dict:
capabilities = {"kvm": []}
kvms = await Qemu.get_kvm_archs()
if kvms:
@ -147,7 +147,7 @@ async def get_capabilities() -> dict:
@router.post("/qemu/img",
status_code=status.HTTP_204_NO_CONTENT,
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to create Qemu image"}})
async def create_img(image_data: schemas.QemuImageCreate):
async def create_qemu_image(image_data: schemas.QemuImageCreate):
"""
Create a Qemu image.
"""
@ -163,7 +163,7 @@ async def create_img(image_data: schemas.QemuImageCreate):
@router.put("/qemu/img",
status_code=status.HTTP_204_NO_CONTENT,
responses={403: {"model": schemas.ErrorMessage, "description": "Forbidden to update Qemu image"}})
async def update_img(image_data: schemas.QemuImageUpdate):
async def update_qemu_image(image_data: schemas.QemuImageUpdate):
"""
Update a Qemu image.
"""

@ -98,7 +98,7 @@ def get_docker_node(node: DockerVM = Depends(dep_node)):
@router.put("/{node_id}",
response_model=schemas.Docker,
responses=responses)
async def update_docker(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)):
async def update_docker_node(node_data: schemas.DockerUpdate, node: DockerVM = Depends(dep_node)):
"""
Update a Docker node.
"""
@ -217,10 +217,10 @@ async def duplicate_docker_node(destination_node_id: UUID = Body(..., embed=True
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
responses=responses)
async def create_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: DockerVM = Depends(dep_node)):
async def create_docker_node_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: DockerVM = Depends(dep_node)):
"""
Add a NIO (Network Input/Output) to the node.
The port number on the Docker node is always 0.
@ -235,9 +235,9 @@ async def create_nio(adapter_number: int,
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
responses=responses)
async def update_nio(adapter_number: int,
port_number: int, nio_data: schemas.UDPNIO,
node: DockerVM = Depends(dep_node)):
async def update_docker_node_nio(adapter_number: int,
port_number: int, nio_data: schemas.UDPNIO,
node: DockerVM = Depends(dep_node)):
"""
Update a NIO (Network Input/Output) on the node.
The port number on the Docker node is always 0.
@ -253,7 +253,7 @@ async def update_nio(adapter_number: int,
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def delete_nio(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)):
async def delete_docker_node_nio(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)):
"""
Delete a NIO (Network Input/Output) from the node.
The port number on the Docker node is always 0.
@ -264,10 +264,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: DockerVM = Dep
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
responses=responses)
async def start_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: DockerVM = Depends(dep_node)):
async def start_docker_node_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: DockerVM = Depends(dep_node)):
"""
Start a packet capture on the node.
The port number on the Docker node is always 0.
@ -281,7 +281,7 @@ async def start_capture(adapter_number: int,
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def stop_capture(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)):
async def stop_docker_node_capture(adapter_number: int, port_number: int, node: DockerVM = Depends(dep_node)):
"""
Stop a packet capture on the node.
The port number on the Docker node is always 0.

@ -159,7 +159,7 @@ async def start_iou_node(start_data: schemas.IOUStart, node: IOUVM = Depends(dep
@router.post("/{node_id}/stop",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def stop(node: IOUVM = Depends(dep_node)):
async def stop_iou_node(node: IOUVM = Depends(dep_node)):
"""
Stop an IOU node.
"""
@ -194,10 +194,10 @@ async def reload_iou_node(node: IOUVM = Depends(dep_node)):
status_code=status.HTTP_201_CREATED,
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
responses=responses)
async def create_nio(adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: IOUVM = Depends(dep_node)):
async def create_iou_node_nio(adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: IOUVM = Depends(dep_node)):
"""
Add a NIO (Network Input/Output) to the node.
"""
@ -211,10 +211,10 @@ async def create_nio(adapter_number: int,
status_code=status.HTTP_201_CREATED,
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
responses=responses)
async def update_nio(adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: IOUVM = Depends(dep_node)):
async def update_iou_node_nio(adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: IOUVM = Depends(dep_node)):
"""
Update a NIO (Network Input/Output) on the node.
"""
@ -229,7 +229,7 @@ async def update_nio(adapter_number: int,
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def delete_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)):
async def delete_iou_node_nio(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)):
"""
Delete a NIO (Network Input/Output) from the node.
"""
@ -239,10 +239,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: IOUVM = Depend
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
responses=responses)
async def start_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: IOUVM = Depends(dep_node)):
async def start_iou_node_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: IOUVM = Depends(dep_node)):
"""
Start a packet capture on the node.
"""
@ -255,7 +255,7 @@ async def start_capture(adapter_number: int,
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def stop_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)):
async def stop_iou_node_capture(adapter_number: int, port_number: int, node: IOUVM = Depends(dep_node)):
"""
Stop a packet capture on the node.
"""

@ -52,7 +52,7 @@ def dep_node(project_id: UUID, node_id: UUID):
response_model=schemas.NAT,
status_code=status.HTTP_201_CREATED,
responses={409: {"model": schemas.ErrorMessage, "description": "Could not create NAT node"}})
async def create_nat(project_id: UUID, node_data: schemas.NATCreate):
async def create_nat_node(project_id: UUID, node_data: schemas.NATCreate):
"""
Create a new NAT node.
"""
@ -72,7 +72,7 @@ async def create_nat(project_id: UUID, node_data: schemas.NATCreate):
@router.get("/{node_id}",
response_model=schemas.NAT,
responses=responses)
def get_nat(node: Nat = Depends(dep_node)):
def get_nat_node(node: Nat = Depends(dep_node)):
"""
Return a NAT node.
"""
@ -83,7 +83,7 @@ def get_nat(node: Nat = Depends(dep_node)):
@router.put("/{node_id}",
response_model=schemas.NAT,
responses=responses)
def update_nat(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)):
def update_nat_node(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)):
"""
Update a NAT node.
"""
@ -99,7 +99,7 @@ def update_nat(node_data: schemas.NATUpdate, node: Nat = Depends(dep_node)):
@router.delete("/{node_id}",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def delete_nat(node: Nat = Depends(dep_node)):
async def delete_nat_node(node: Nat = Depends(dep_node)):
"""
Delete a cloud node.
"""
@ -110,7 +110,7 @@ async def delete_nat(node: Nat = Depends(dep_node)):
@router.post("/{node_id}/start",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def start_nat(node: Nat = Depends(dep_node)):
async def start_nat_node(node: Nat = Depends(dep_node)):
"""
Start a NAT node.
"""
@ -121,7 +121,7 @@ async def start_nat(node: Nat = Depends(dep_node)):
@router.post("/{node_id}/stop",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def stop_nat(node: Nat = Depends(dep_node)):
async def stop_nat_node(node: Nat = Depends(dep_node)):
"""
Stop a NAT node.
This endpoint results in no action since cloud nodes cannot be stopped.
@ -133,7 +133,7 @@ async def stop_nat(node: Nat = Depends(dep_node)):
@router.post("/{node_id}/suspend",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def suspend_nat(node: Nat = Depends(dep_node)):
async def suspend_nat_node(node: Nat = Depends(dep_node)):
"""
Suspend a NAT node.
This endpoint results in no action since NAT nodes cannot be suspended.
@ -146,10 +146,10 @@ async def suspend_nat(node: Nat = Depends(dep_node)):
status_code=status.HTTP_201_CREATED,
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
responses=responses)
async def create_nio(adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Nat = Depends(dep_node)):
async def create_nat_node_nio(adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Nat = Depends(dep_node)):
"""
Add a NIO (Network Input/Output) to the node.
The adapter number on the cloud is always 0.
@ -164,10 +164,10 @@ async def create_nio(adapter_number: int,
status_code=status.HTTP_201_CREATED,
response_model=Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
responses=responses)
async def update_nio(adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Nat = Depends(dep_node)):
async def update_nat_node_nio(adapter_number: int,
port_number: int,
nio_data: Union[schemas.EthernetNIO, schemas.TAPNIO, schemas.UDPNIO],
node: Nat = Depends(dep_node)):
"""
Update a NIO (Network Input/Output) to the node.
The adapter number on the cloud is always 0.
@ -183,7 +183,7 @@ async def update_nio(adapter_number: int,
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def delete_nio(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)):
async def delete_nat_node_nio(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)):
"""
Remove a NIO (Network Input/Output) from the node.
The adapter number on the cloud is always 0.
@ -194,10 +194,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: Nat = Depends(
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
responses=responses)
async def start_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: Nat = Depends(dep_node)):
async def start_nat_node_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: Nat = Depends(dep_node)):
"""
Start a packet capture on the node.
The adapter number on the cloud is always 0.
@ -211,7 +211,7 @@ async def start_capture(adapter_number: int,
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def stop_capture(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)):
async def stop_nat_node_capture(adapter_number: int, port_number: int, node: Nat = Depends(dep_node)):
"""
Stop a packet capture on the node.
The adapter number on the cloud is always 0.

@ -52,7 +52,7 @@ def dep_project(project_id: UUID):
@router.get("/projects", response_model=List[schemas.Project])
def get_projects():
def get_compute_projects():
"""
Get all projects opened on the compute.
"""
@ -64,7 +64,7 @@ def get_projects():
@router.post("/projects",
status_code=status.HTTP_201_CREATED,
response_model=schemas.Project)
def create_project(project_data: schemas.ProjectCreate):
def create_compute_project(project_data: schemas.ProjectCreate):
"""
Create a new project on the compute.
"""
@ -80,7 +80,7 @@ def create_project(project_data: schemas.ProjectCreate):
@router.put("/projects/{project_id}",
response_model=schemas.Project)
async def update_project(project_data: schemas.ProjectUpdate, project: Project = Depends(dep_project)):
async def update_compute_project(project_data: schemas.ProjectUpdate, project: Project = Depends(dep_project)):
"""
Update project on the compute.
"""
@ -91,7 +91,7 @@ async def update_project(project_data: schemas.ProjectUpdate, project: Project =
@router.get("/projects/{project_id}",
response_model=schemas.Project)
def get_project(project: Project = Depends(dep_project)):
def get_compute_project(project: Project = Depends(dep_project)):
"""
Return a project from the compute.
"""
@ -101,7 +101,7 @@ def get_project(project: Project = Depends(dep_project)):
@router.post("/projects/{project_id}/close",
status_code=status.HTTP_204_NO_CONTENT)
async def close_project(project: Project = Depends(dep_project)):
async def close_compute_project(project: Project = Depends(dep_project)):
"""
Close a project on the compute.
"""
@ -120,7 +120,7 @@ async def close_project(project: Project = Depends(dep_project)):
@router.delete("/projects/{project_id}",
status_code=status.HTTP_204_NO_CONTENT)
async def delete_project(project: Project = Depends(dep_project)):
async def delete_compute_project(project: Project = Depends(dep_project)):
"""
Delete project from the compute.
"""
@ -184,7 +184,7 @@ async def delete_project(project: Project = Depends(dep_project)):
@router.get("/projects/{project_id}/files",
response_model=List[schemas.ProjectFile])
async def get_project_files(project: Project = Depends(dep_project)):
async def get_compute_project_files(project: Project = Depends(dep_project)):
"""
Return files belonging to a project.
"""
@ -193,7 +193,7 @@ async def get_project_files(project: Project = Depends(dep_project)):
@router.get("/projects/{project_id}/files/{file_path:path}")
async def get_file(file_path: str, project: Project = Depends(dep_project)):
async def get_compute_project_file(file_path: str, project: Project = Depends(dep_project)):
"""
Get a file from a project.
"""
@ -213,7 +213,7 @@ async def get_file(file_path: str, project: Project = Depends(dep_project)):
@router.post("/projects/{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)):
async def write_compute_project_file(file_path: str, request: Request, project: Project = Depends(dep_project)):
path = os.path.normpath(file_path)

@ -210,7 +210,10 @@ async def resume_qemu_node(node: QemuVM = Depends(dep_node)):
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
responses=responses)
async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: QemuVM = Depends(dep_node)):
async def create_qemu_node_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: QemuVM = Depends(dep_node)):
"""
Add a NIO (Network Input/Output) to the node.
The port number on the Qemu node is always 0.
@ -225,7 +228,10 @@ async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UD
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
responses=responses)
async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: QemuVM = Depends(dep_node)):
async def update_qemu_node_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: QemuVM = Depends(dep_node)):
"""
Update a NIO (Network Input/Output) on the node.
The port number on the Qemu node is always 0.
@ -243,7 +249,9 @@ async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UD
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def delete_nio(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)):
async def delete_qemu_node_nio(adapter_number: int,
port_number: int,
node: QemuVM = Depends(dep_node)):
"""
Delete a NIO (Network Input/Output) from the node.
The port number on the Qemu node is always 0.
@ -254,10 +262,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: QemuVM = Depen
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
responses=responses)
async def start_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: QemuVM = Depends(dep_node)):
async def start_qemu_node_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: QemuVM = Depends(dep_node)):
"""
Start a packet capture on the node.
The port number on the Qemu node is always 0.
@ -271,7 +279,7 @@ async def start_capture(adapter_number: int,
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def stop_capture(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)):
async def stop_qemu_node_capture(adapter_number: int, port_number: int, node: QemuVM = Depends(dep_node)):
"""
Stop a packet capture on the node.
The port number on the Qemu node is always 0.

@ -212,10 +212,10 @@ async def reload_virtualbox_node(node: VirtualBoxVM = Depends(dep_node)):
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
responses=responses)
async def create_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: VirtualBoxVM = Depends(dep_node)):
async def create_virtualbox_node_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: VirtualBoxVM = Depends(dep_node)):
"""
Add a NIO (Network Input/Output) to the node.
The port number on the VirtualBox node is always 0.
@ -230,10 +230,10 @@ async def create_nio(adapter_number: int,
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
responses=responses)
async def update_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: VirtualBoxVM = Depends(dep_node)):
async def update_virtualbox_node_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: VirtualBoxVM = Depends(dep_node)):
"""
Update a NIO (Network Input/Output) on the node.
The port number on the VirtualBox node is always 0.
@ -251,7 +251,7 @@ async def update_nio(adapter_number: int,
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def delete_nio(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)):
async def delete_virtualbox_node_nio(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)):
"""
Delete a NIO (Network Input/Output) from the node.
The port number on the VirtualBox node is always 0.
@ -262,10 +262,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: VirtualBoxVM =
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
responses=responses)
async def start_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: VirtualBoxVM = Depends(dep_node)):
async def start_virtualbox_node_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: VirtualBoxVM = Depends(dep_node)):
"""
Start a packet capture on the node.
The port number on the VirtualBox node is always 0.
@ -279,7 +279,7 @@ async def start_capture(adapter_number: int,
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def stop_capture(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)):
async def stop_virtualbox_node_capture(adapter_number: int, port_number: int, node: VirtualBoxVM = Depends(dep_node)):
"""
Stop a packet capture on the node.
The port number on the VirtualBox node is always 0.

@ -180,10 +180,10 @@ async def reload_vmware_node(node: VMwareVM = Depends(dep_node)):
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
responses=responses)
async def create_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: VMwareVM = Depends(dep_node)):
async def create_vmware_node_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: VMwareVM = Depends(dep_node)):
"""
Add a NIO (Network Input/Output) to the node.
The port number on the VMware node is always 0.
@ -198,9 +198,9 @@ async def create_nio(adapter_number: int,
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
responses=responses)
async def update_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO, node: VMwareVM = Depends(dep_node)):
async def update_vmware_node_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO, node: VMwareVM = Depends(dep_node)):
"""
Update a NIO (Network Input/Output) on the node.
The port number on the VMware node is always 0.
@ -216,7 +216,7 @@ async def update_nio(adapter_number: int,
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def delete_nio(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)):
async def delete_vmware_node_nio(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)):
"""
Delete a NIO (Network Input/Output) from the node.
The port number on the VMware node is always 0.
@ -227,10 +227,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: VMwareVM = Dep
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
responses=responses)
async def start_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: VMwareVM = Depends(dep_node)):
async def start_vmware_node_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: VMwareVM = Depends(dep_node)):
"""
Start a packet capture on the node.
The port number on the VMware node is always 0.
@ -244,7 +244,7 @@ async def start_capture(adapter_number: int,
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def stop_capture(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)):
async def stop_vmware_node_capture(adapter_number: int, port_number: int, node: VMwareVM = Depends(dep_node)):
"""
Stop a packet capture on the node.
The port number on the VMware node is always 0.

@ -168,7 +168,10 @@ async def reload_vpcs_node(node: VPCSVM = Depends(dep_node)):
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
responses=responses)
async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VPCSVM = Depends(dep_node)):
async def create_vpcs_node_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: VPCSVM = Depends(dep_node)):
"""
Add a NIO (Network Input/Output) to the node.
The adapter number on the VPCS node is always 0.
@ -183,7 +186,10 @@ async def create_nio(adapter_number: int, port_number: int, nio_data: schemas.UD
status_code=status.HTTP_201_CREATED,
response_model=schemas.UDPNIO,
responses=responses)
async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UDPNIO, node: VPCSVM = Depends(dep_node)):
async def update_vpcs_node_nio(adapter_number: int,
port_number: int,
nio_data: schemas.UDPNIO,
node: VPCSVM = Depends(dep_node)):
"""
Update a NIO (Network Input/Output) on the node.
The adapter number on the VPCS node is always 0.
@ -199,7 +205,9 @@ async def update_nio(adapter_number: int, port_number: int, nio_data: schemas.UD
@router.delete("/{node_id}/adapters/{adapter_number}/ports/{port_number}/nio",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def delete_nio(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)):
async def delete_vpcs_node_nio(adapter_number: int,
port_number: int,
node: VPCSVM = Depends(dep_node)):
"""
Delete a NIO (Network Input/Output) from the node.
The adapter number on the VPCS node is always 0.
@ -210,10 +218,10 @@ async def delete_nio(adapter_number: int, port_number: int, node: VPCSVM = Depen
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/start",
responses=responses)
async def start_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: VPCSVM = Depends(dep_node)):
async def start_vpcs_node_capture(adapter_number: int,
port_number: int,
node_capture_data: schemas.NodeCapture,
node: VPCSVM = Depends(dep_node)):
"""
Start a packet capture on the node.
The adapter number on the VPCS node is always 0.
@ -227,7 +235,7 @@ async def start_capture(adapter_number: int,
@router.post("/{node_id}/adapters/{adapter_number}/ports/{port_number}/capture/stop",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def stop_capture(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)):
async def stop_vpcs_node_capture(adapter_number: int, port_number: int, node: VPCSVM = Depends(dep_node)):
"""
Stop a packet capture on the node.
The adapter number on the VPCS node is always 0.

@ -28,9 +28,11 @@ from . import projects
from . import snapshots
from . import symbols
from . import templates
from . import users
router = APIRouter()
router.include_router(controller.router, tags=["Controller"])
router.include_router(users.router, prefix="/users", tags=["Users"])
router.include_router(appliances.router, prefix="/appliances", tags=["Appliances"])
router.include_router(computes.router, prefix="/computes", tags=["Computes"])
router.include_router(drawings.router, prefix="/projects/{project_id}/drawings", tags=["Drawings"])

@ -71,7 +71,7 @@ async def shutdown():
@router.get("/version",
response_model=schemas.Version)
def version():
def get_version():
"""
Return the server version number.
"""

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from gns3server import schemas
from gns3server.db.repositories.users import UsersRepository
from gns3server.services import auth_service
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v3/users/login") # FIXME: URL prefix
async def get_user_from_token(token: str = Depends(oauth2_scheme),
user_repo: UsersRepository = Depends()) -> schemas.User:
username = auth_service.get_username_from_token(token)
user = await user_repo.get_user_by_username(username)
if user is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"}
)
return user
async def get_current_active_user(current_user: schemas.User = Depends(get_user_from_token)) -> schemas.User:
if not current_user.is_active:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Not an active user",
headers={"WWW-Authenticate": "Bearer"}
)
return current_user

@ -416,7 +416,7 @@ async def ws_console(websocket: WebSocket, node: Node = Depends(dep_node)):
@router.post("/console/reset",
status_code=status.HTTP_204_NO_CONTENT,
responses=responses)
async def reset_console_all(project: Project = Depends(dep_project)):
async def reset_console_all_nodes(project: Project = Depends(dep_project)):
"""
Reset console for all nodes belonging to the project.
"""

@ -347,7 +347,7 @@ async def import_project(project_id: UUID, request: Request, path: Optional[Path
**responses,
409: {"model": schemas.ErrorMessage, "description": "Could not duplicate project"}
})
async def duplicate(project_data: schemas.ProjectDuplicate, project: Project = Depends(dep_project)):
async def duplicate_project(project_data: schemas.ProjectDuplicate, project: Project = Depends(dep_project)):
"""
Duplicate a project.
"""

@ -1,6 +1,6 @@
#!/usr/bin/env python
#
# Copyright (C) 2016 GNS3 Technologies Inc.
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by

@ -0,0 +1,125 @@
#!/usr/bin/env python
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
API routes for users.
"""
from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from uuid import UUID
from typing import List
from gns3server import schemas
from gns3server.controller.controller_error import ControllerBadRequestError, ControllerNotFoundError
from gns3server.db.repositories.users import UsersRepository
from gns3server.services import auth_service
from .dependencies.authentication import get_current_active_user
import logging
log = logging.getLogger(__name__)
router = APIRouter()
@router.get("", response_model=List[schemas.User])
async def get_users(user_repo: UsersRepository = Depends()) -> List[schemas.User]:
"""
Get all users.
"""
users = await user_repo.get_users()
return users
@router.post("", response_model=schemas.User, status_code=status.HTTP_201_CREATED)
async def create_user(new_user: schemas.UserCreate, user_repo: UsersRepository = Depends()) -> schemas.User:
"""
Create a new user.
"""
if await user_repo.get_user_by_username(new_user.username):
raise ControllerBadRequestError(f"Username '{new_user.username}' is already registered")
if new_user.email and await user_repo.get_user_by_email(new_user.email):
raise ControllerBadRequestError(f"Email '{new_user.email}' is already registered")
return await user_repo.create_user(new_user)
@router.get("/{user_id}", response_model=schemas.User)
async def get_user(user_id: UUID, user_repo: UsersRepository = Depends()) -> schemas.User:
"""
Get an user.
"""
user = await user_repo.get_user(user_id)
if not user:
raise ControllerNotFoundError(f"User '{user_id}' not found")
return user
@router.put("/{user_id}", response_model=schemas.User)
async def update_user(user_id: UUID,
update_user: schemas.UserUpdate,
user_repo: UsersRepository = Depends()) -> schemas.User:
"""
Update an user.
"""
user = await user_repo.update_user(user_id, update_user)
if not user:
raise ControllerNotFoundError(f"User '{user_id}' not found")
return user
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_user(user_id: UUID, user_repo: UsersRepository = Depends()):
"""
Delete an user.
"""
success = await user_repo.delete_user(user_id)
if not success:
raise ControllerNotFoundError(f"User '{user_id}' not found")
@router.post("/login", response_model=schemas.Token)
async def login(user_repo: UsersRepository = Depends(),
form_data: OAuth2PasswordRequestForm = Depends()) -> schemas.Token:
"""
User login.
"""
user = await user_repo.authenticate_user(username=form_data.username, password=form_data.password)
if not user:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,
detail="Authentication was unsuccessful.",
headers={"WWW-Authenticate": "Bearer"})
token = schemas.Token(access_token=auth_service.create_access_token(user.username), token_type="bearer")
return token
@router.get("/users/me/", response_model=schemas.User)
async def get_current_active_user(current_user: schemas.User = Depends(get_current_active_user)) -> schemas.User:
"""
Get the current active user.
"""
return current_user

@ -30,6 +30,7 @@ from fastapi.responses import JSONResponse
from gns3server.controller.controller_error import (
ControllerError,
ControllerNotFoundError,
ControllerBadRequestError,
ControllerTimeoutError,
ControllerForbiddenError,
ControllerUnauthorizedError
@ -119,6 +120,15 @@ async def controller_not_found_error_handler(request: Request, exc: ControllerNo
)
@app.exception_handler(ControllerBadRequestError)
async def controller_bad_request_error_handler(request: Request, exc: ControllerBadRequestError):
log.error(f"Controller bad request error: {exc}")
return JSONResponse(
status_code=400,
content={"message": str(exc)},
)
# make sure the content key is "message", not "detail" per default
@app.exception_handler(StarletteHTTPException)
async def http_exception_handler(request: Request, exc: StarletteHTTPException):

@ -35,6 +35,12 @@ class ControllerNotFoundError(ControllerError):
super().__init__(message)
class ControllerBadRequestError(ControllerError):
def __init__(self, message: str):
super().__init__(message)
class ControllerUnauthorizedError(ControllerError):
def __init__(self, message: str):

@ -100,12 +100,12 @@ class Template:
try:
template_schema = TEMPLATE_TYPE_TO_SHEMA[self.template_type]
template_settings_with_defaults = template_schema.parse_obj(self.__json__())
self._settings = template_settings_with_defaults.dict()
self._settings = jsonable_encoder(template_settings_with_defaults.dict())
if self.template_type == "dynamips":
# special case for Dynamips to cover all platform types that contain specific settings
dynamips_template_schema = DYNAMIPS_PLATFORM_TO_SHEMA[self._settings["platform"]]
dynamips_template_settings_with_defaults = dynamips_template_schema.parse_obj(self.__json__())
self._settings = dynamips_template_settings_with_defaults.dict()
self._settings = jsonable_encoder(dynamips_template_settings_with_defaults.dict())
except ValidationError as e:
print(e) #TODO: handle errors
raise

@ -25,7 +25,8 @@ from gns3server.controller import Controller
from gns3server.compute import MODULES
from gns3server.compute.port_manager import PortManager
from gns3server.utils.http_client import HTTPClient
#from gns3server.db.tasks import connect_to_db, close_db_connection
from gns3server.db.tasks import connect_to_db
import logging
log = logging.getLogger(__name__)
@ -54,7 +55,7 @@ def create_startup_handler(app: FastAPI) -> Callable:
loop.set_debug(True)
# connect to the database
# await connect_to_db(app)
await connect_to_db()
await Controller.instance().start()
# Because with a large image collection
@ -89,7 +90,4 @@ def create_shutdown_handler(app: FastAPI) -> Callable:
if PortManager.instance().udp_ports:
log.warning("UDP ports are still used {}".format(PortManager.instance().udp_ports))
# close the connection to the database
# await close_db_connection(app)
return shutdown_handler

@ -0,0 +1,26 @@
#!/usr/bin/env python
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import os
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy.orm import declarative_base
SQLALCHEMY_DATABASE_URL = os.environ.get("DATABASE_URI", "sqlite:///./sql_app.db")
engine = create_async_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
Base = declarative_base()

@ -0,0 +1,95 @@
#!/usr/bin/env python
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import uuid
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime, func
from sqlalchemy.orm import relationship
from sqlalchemy.types import TypeDecorator, CHAR
from sqlalchemy.dialects.postgresql import UUID
from .database import Base
class GUID(TypeDecorator):
"""Platform-independent GUID type.
Uses PostgreSQL's UUID type, otherwise uses
CHAR(32), storing as stringified hex values.
"""
impl = CHAR
def load_dialect_impl(self, dialect):
if dialect.name == 'postgresql':
return dialect.type_descriptor(UUID())
else:
return dialect.type_descriptor(CHAR(32))
def process_bind_param(self, value, dialect):
if value is None:
return value
elif dialect.name == 'postgresql':
return str(value)
else:
if not isinstance(value, uuid.UUID):
return "%.32x" % uuid.UUID(value).int
else:
# hexstring
return "%.32x" % value.int
def process_result_value(self, value, dialect):
if value is None:
return value
else:
if not isinstance(value, uuid.UUID):
value = uuid.UUID(value)
return value
class BaseTable(Base):
__abstract__ = True
created_at = Column(DateTime, default=func.current_timestamp())
updated_at = Column(DateTime,
default=func.current_timestamp(),
onupdate=func.current_timestamp())
class User(BaseTable):
__tablename__ = "users"
user_id = Column(GUID, primary_key=True, default=str(uuid.uuid4()))
username = Column(String, unique=True, index=True)
email = Column(String, unique=True, index=True)
full_name = Column(String)
hashed_password = Column(String)
is_active = Column(Boolean, default=True)
is_superuser = Column(Boolean, default=False)
# items = relationship("Item", back_populates="owner")
#
#
# class Item(Base):
# __tablename__ = "items"
#
# id = Column(Integer, primary_key=True, index=True)
# title = Column(String, index=True)
# description = Column(String, index=True)
# owner_id = Column(Integer, ForeignKey("users.id"))
#
# owner = relationship("User", back_populates="items")

@ -0,0 +1,29 @@
#!/usr/bin/env python
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sqlalchemy.ext.asyncio import AsyncSession
from ..database import engine
class BaseRepository:
async def db(self):
session = AsyncSession(engine)
try:
yield session
finally:
await session.close()

@ -0,0 +1,110 @@
#!/usr/bin/env python
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from uuid import UUID
from typing import Optional, List
from sqlalchemy import select, update, delete
from sqlalchemy.ext.asyncio import AsyncSession
from ..database import engine
from .base import BaseRepository
import gns3server.db.models as models
from gns3server import schemas
from gns3server.services import auth_service
class UsersRepository(BaseRepository):
def __init__(self) -> None:
super().__init__()
self._auth_service = auth_service
async def get_user(self, user_id: UUID) -> Optional[models.User]:
async with AsyncSession(engine) as session:
result = await session.execute(select(models.User).where(models.User.user_id == user_id))
return result.scalars().first()
async def get_user_by_username(self, username: str) -> Optional[models.User]:
async with AsyncSession(engine) as session:
result = await session.execute(select(models.User).where(models.User.username == username))
return result.scalars().first()
async def get_user_by_email(self, email: str) -> Optional[models.User]:
async with AsyncSession(engine) as session:
result = await session.execute(select(models.User).where(models.User.email == email))
return result.scalars().first()
async def get_users(self) -> List[models.User]:
async with AsyncSession(engine) as session:
result = await session.execute(select(models.User))
return result.scalars().all()
async def create_user(self, user: schemas.UserCreate) -> models.User:
async with AsyncSession(engine) as session:
hashed_password = self._auth_service.hash_password(user.password)
db_user = models.User(username=user.username,
email=user.email,
full_name=user.full_name,
hashed_password=hashed_password)
session.add(db_user)
await session.commit()
await session.refresh(db_user)
return db_user
async def update_user(self, user_id: UUID, user_update: schemas.UserUpdate) -> Optional[models.User]:
async with AsyncSession(engine) as session:
update_values = user_update.dict(exclude_unset=True)
password = update_values.pop("password", None)
if password:
update_values["hashed_password"] = self._auth_service.hash_password(password=password)
print(update_values)
query = update(models.User) \
.where(models.User.user_id == user_id) \
.values(update_values)
await session.execute(query)
await session.commit()
return await self.get_user(user_id)
async def delete_user(self, user_id: UUID) -> bool:
async with AsyncSession(engine) as session:
query = delete(models.User).where(models.User.user_id == user_id)
result = await session.execute(query)
await session.commit()
return result.rowcount > 0
#except:
# await session.rollback()
async def authenticate_user(self, username: str, password: str) -> Optional[models.User]:
user = await self.get_user_by_username(username)
if not user:
return None
if not self._auth_service.verify_password(password, user.hashed_password):
return None
return user

@ -0,0 +1,34 @@
#!/usr/bin/env python
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from sqlalchemy.exc import SQLAlchemyError
from .database import engine
from .models import Base
import logging
log = logging.getLogger(__name__)
async def connect_to_db() -> None:
try:
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
log.info("Successfully connected to the database")
except SQLAlchemyError as e:
log.error(f"Error while connecting to the database: {e}")

@ -26,6 +26,8 @@ from .drawings import Drawing
from .gns3vm import GNS3VM
from .nodes import NodeUpdate, NodeDuplicate, NodeCapture, Node
from .projects import ProjectCreate, ProjectUpdate, ProjectDuplicate, Project, ProjectFile
from .users import UserCreate, UserUpdate, User
from .tokens import Token
from .snapshots import SnapshotCreate, Snapshot
from .capabilities import Capabilities
from .nios import UDPNIO, TAPNIO, EthernetNIO

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Optional
from datetime import datetime
from pydantic import BaseModel
class DateTimeModelMixin(BaseModel):
created_at: Optional[datetime]
updated_at: Optional[datetime]

@ -24,14 +24,14 @@ from typing import Optional, List
from enum import Enum
DEFAULT_PORTS = [
dict(port_number=0, name="Ethernet0", vlan=1, type="access", ethertype=""),
dict(port_number=1, name="Ethernet1", vlan=1, type="access", ethertype=""),
dict(port_number=2, name="Ethernet2", vlan=1, type="access", ethertype=""),
dict(port_number=3, name="Ethernet3", vlan=1, type="access", ethertype=""),
dict(port_number=4, name="Ethernet4", vlan=1, type="access", ethertype=""),
dict(port_number=5, name="Ethernet5", vlan=1, type="access", ethertype=""),
dict(port_number=6, name="Ethernet6", vlan=1, type="access", ethertype=""),
dict(port_number=7, name="Ethernet7", vlan=1, type="access", ethertype="")
dict(port_number=0, name="Ethernet0", vlan=1, type="access", ethertype="0x8100"),
dict(port_number=1, name="Ethernet1", vlan=1, type="access", ethertype="0x8100"),
dict(port_number=2, name="Ethernet2", vlan=1, type="access", ethertype="0x8100"),
dict(port_number=3, name="Ethernet3", vlan=1, type="access", ethertype="0x8100"),
dict(port_number=4, name="Ethernet4", vlan=1, type="access", ethertype="0x8100"),
dict(port_number=5, name="Ethernet5", vlan=1, type="access", ethertype="0x8100"),
dict(port_number=6, name="Ethernet6", vlan=1, type="access", ethertype="0x8100"),
dict(port_number=7, name="Ethernet7", vlan=1, type="access", ethertype="0x8100")
]

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Optional
from pydantic import BaseModel
class Token(BaseModel):
access_token: str
token_type: str
class TokenData(BaseModel):
username: Optional[str] = None

@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from typing import Optional
from pydantic import EmailStr, BaseModel, Field
from uuid import UUID
from .base import DateTimeModelMixin
class UserBase(BaseModel):
"""
Common user properties.
"""
username: Optional[str] = Field(None, min_length=3, regex="[a-zA-Z0-9_-]+$")
email: Optional[EmailStr]
full_name: Optional[str]
class UserCreate(UserBase):
"""
Properties to create an user.
"""
username: str = Field(..., min_length=3, regex="[a-zA-Z0-9_-]+$")
password: str = Field(..., min_length=7, max_length=100)
class UserUpdate(UserBase):
"""
Properties to update an user.
"""
password: Optional[str] = Field(None, min_length=7, max_length=100)
class User(DateTimeModelMixin, UserBase):
user_id: UUID
is_active: bool = True
is_superuser: bool = False
class Config:
orm_mode = True

@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from .authentication import AuthService
auth_service = AuthService()

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import bcrypt
from jose import JWTError, jwt
from datetime import datetime, timedelta
from passlib.context import CryptContext
from typing import Optional
from fastapi import HTTPException, status
from gns3server.schemas.tokens import TokenData
from pydantic import ValidationError
# FIXME: temporary variables to move to config
SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7"
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 30
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
#class AuthException(BaseException):
# pass
class AuthService:
def hash_password(self, password: str) -> str:
return pwd_context.hash(password)
def verify_password(self, password, hashed_password) -> bool:
return pwd_context.verify(password, hashed_password)
def create_access_token(self, username):
expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode = {"sub": username, "exp": expire}
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def get_username_from_token(self, token: str) -> Optional[str]:
credentials_exception = HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
raise credentials_exception
token_data = TokenData(username=username)
except (JWTError, ValidationError):
raise credentials_exception
return token_data.username

@ -10,3 +10,7 @@ psutil==5.7.3
async-timeout==3.0.1
distro==1.5.0
py-cpuinfo==7.0.0
sqlalchemy==1.4.0b1 # beta version with asyncio support
passlib[bcrypt]==1.7.2
python-jose==3.2.0
email-validator==1.1.2

@ -1,94 +0,0 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2015 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
Base code use for all API tests
"""
import json
import pytest
class Query:
"""
Helper to make queries against the test server
"""
def __init__(self, http_client, ws_client, prefix='', api_version=None):
"""
:param prefix: Prefix added before path (ex: /compute)
:param api_version: Version of the API
"""
self._http_client = http_client
self._ws_client = ws_client
self._prefix = prefix
self._api_version = api_version
def post(self, path, body={}, **kwargs):
return self._request("POST", path, body, **kwargs)
def put(self, path, body={}, **kwargs):
return self._request("PUT", path, body, **kwargs)
def get(self, path, **kwargs):
return self._request("GET", path, **kwargs)
def delete(self, path, **kwargs):
return self._request("DELETE", path, **kwargs)
def patch(self, path, **kwargs):
return self._request("PATCH", path, **kwargs)
def ws(self, path):
return self._ws_client.websocket_connect(self.get_url(path))
def get_url(self, path):
if self._api_version is None:
return "/{}{}".format(self._prefix, path)
return "/v{}{}{}".format(self._api_version, self._prefix, path)
@pytest.mark.asyncio
async def _request(self, method, path, body=None, raw=False, **kwargs):
if body is not None and raw is False:
body = json.dumps(body)
async with self._http_client as ac:
response = await ac.request(method, self.get_url(path), data=body, **kwargs)
#response.body = await response.read()
# x_route = response.headers.get('X-Route', None)
# if x_route is not None:
# response.route = x_route.replace("/v{}".format(self._api_version), "")
# response.route = response.route.replace(self._prefix, "")
#response.json = {}
#response.html = ""
if response.content is not None:
if response.headers.get("content-type") == "application/json":
try:
response.json = response.json()
except ValueError:
response.json = None
# else:
# try:
# response.html = response.text
# except UnicodeDecodeError:
# response.html = None
return response

@ -20,35 +20,38 @@ import sys
import pytest
import psutil
from fastapi import FastAPI, status
from httpx import AsyncClient
from gns3server.version import __version__
from gns3server.utils.path import get_default_project_directory
pytestmark = pytest.mark.asyncio
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
@pytest.mark.asyncio
async def test_get(compute_api, windows_platform):
async def test_get(app: FastAPI, client: AsyncClient, windows_platform) -> None:
response = await compute_api.get('/capabilities')
assert response.status_code == 200
assert response.json == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'traceng', 'docker', 'iou'],
'version': __version__,
'platform': sys.platform,
'cpus': psutil.cpu_count(logical=True),
'memory': psutil.virtual_memory().total,
'disk_size': psutil.disk_usage(get_default_project_directory()).total,
}
response = await client.get(app.url_path_for("get_capabilities"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'traceng', 'docker', 'iou'],
'version': __version__,
'platform': sys.platform,
'cpus': psutil.cpu_count(logical=True),
'memory': psutil.virtual_memory().total,
'disk_size': psutil.disk_usage(get_default_project_directory()).total,
}
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
@pytest.mark.asyncio
async def test_get_on_gns3vm(compute_api, on_gns3vm):
response = await compute_api.get('/capabilities')
assert response.status_code == 200
assert response.json == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'traceng', 'docker', 'iou'],
'version': __version__,
'platform': sys.platform,
'cpus': psutil.cpu_count(logical=True),
'memory': psutil.virtual_memory().total,
'disk_size': psutil.disk_usage(get_default_project_directory()).total,
}
async def test_get_on_gns3vm(app: FastAPI, client: AsyncClient, on_gns3vm) -> None:
response = await client.get(app.url_path_for("get_capabilities"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {'node_types': ['cloud', 'ethernet_hub', 'ethernet_switch', 'nat', 'vpcs', 'virtualbox', 'dynamips', 'frame_relay_switch', 'atm_switch', 'qemu', 'vmware', 'traceng', 'docker', 'iou'],
'version': __version__,
'platform': sys.platform,
'cpus': psutil.cpu_count(logical=True),
'memory': psutil.virtual_memory().total,
'disk_size': psutil.disk_usage(get_default_project_directory()).total,
}

@ -17,99 +17,126 @@
import pytest
from unittest.mock import patch
from fastapi import FastAPI, status
from httpx import AsyncClient
from tests.utils import asyncio_patch
from gns3server.compute.project import Project
pytestmark = pytest.mark.asyncio
@pytest.fixture(scope="function")
@pytest.mark.asyncio
async def vm(compute_api, compute_project, on_gns3vm):
async def vm(app: FastAPI, client: AsyncClient, compute_project: Project, on_gns3vm) -> dict:
with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud._start_ubridge"):
response = await compute_api.post("/projects/{project_id}/cloud/nodes".format(project_id=compute_project.id), {"name": "Cloud 1"})
assert response.status_code == 201
return response.json
response = await client.post(app.url_path_for("create_cloud", project_id=compute_project.id),
json={"name": "Cloud 1"})
assert response.status_code == status.HTTP_201_CREATED
return response.json()
@pytest.mark.asyncio
async def test_cloud_create(compute_api, compute_project):
async def test_cloud_create(app: FastAPI, client: AsyncClient, compute_project: Project) -> None:
with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud._start_ubridge"):
response = await compute_api.post("/projects/{project_id}/cloud/nodes".format(project_id=compute_project.id), {"name": "Cloud 1"})
response = await client.post(app.url_path_for("create_cloud", project_id=compute_project.id),
json={"name": "Cloud 1"})
assert response.status_code == 201
assert response.json["name"] == "Cloud 1"
assert response.json["project_id"] == compute_project.id
assert response.json()["name"] == "Cloud 1"
assert response.json()["project_id"] == compute_project.id
@pytest.mark.asyncio
async def test_cloud_get(compute_api, compute_project, vm):
async def test_get_cloud(app: FastAPI, client: AsyncClient, compute_project: Project, vm: dict) -> None:
response = await compute_api.get("/projects/{project_id}/cloud/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 200
assert response.json["name"] == "Cloud 1"
assert response.json["project_id"] == compute_project.id
assert response.json["status"] == "started"
response = await client.get(app.url_path_for("get_cloud", project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "Cloud 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["status"] == "started"
@pytest.mark.asyncio
async def test_cloud_nio_create_udp(compute_api, vm):
async def test_cloud_nio_create_udp(app: FastAPI, client: AsyncClient, compute_project: Project, vm: dict) -> None:
params = {"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"}
response = await compute_api.post("/projects/{project_id}/cloud/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
assert response.json["type"] == "nio_udp"
url = app.url_path_for("create_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
@pytest.mark.asyncio
async def test_cloud_nio_update_udp(compute_api, vm):
async def test_cloud_nio_update_udp(app: FastAPI, client: AsyncClient, compute_project: Project, vm: dict) -> None:
params = {"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"}
await compute_api.post("/projects/{project_id}/cloud/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
url = app.url_path_for("create_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
await client.post(url, json=params)
params["filters"] = {}
response = await compute_api.put("/projects/{project_id}/cloud/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201, response.body.decode()
assert response.json["type"] == "nio_udp"
url = app.url_path_for("create_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
response = await client.put(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
@pytest.mark.asyncio
async def test_cloud_delete_nio(compute_api, vm):
async def test_cloud_delete_nio(app: FastAPI, client: AsyncClient, compute_project: Project, vm: dict) -> None:
params = {"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"}
await compute_api.post("/projects/{project_id}/cloud/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
url = app.url_path_for("create_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
await client.post(url, json=params)
url = app.url_path_for("delete_cloud_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud._start_ubridge"):
response = await compute_api.delete("/projects/{project_id}/cloud/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
response = await client.delete(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_cloud_delete(compute_api, vm):
async def test_cloud_delete(app: FastAPI, client: AsyncClient, compute_project: Project, vm: dict) -> None:
response = await compute_api.delete("/projects/{project_id}/cloud/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
response = await client.delete(app.url_path_for("delete_cloud", project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_cloud_update(compute_api, vm):
async def test_cloud_update(app: FastAPI, client: AsyncClient, vm: dict) -> None:
response = await compute_api.put("/projects/{project_id}/cloud/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), {"name": "test"})
assert response.status_code == 200
assert response.json["name"] == "test"
response = await client.put(app.url_path_for("update_cloud", project_id=vm["project_id"], node_id=vm["node_id"]),
json={"name": "test"})
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
@pytest.mark.asyncio
async def test_cloud_start_capture(compute_api, vm):
async def test_cloud_start_capture(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"capture_file_name": "test.pcap",
@ -117,18 +144,26 @@ async def test_cloud_start_capture(compute_api, vm):
}
with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud.start_capture") as mock:
response = await compute_api.post("/projects/{project_id}/cloud/nodes/{node_id}/adapters/0/ports/0/capture/start".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 200
response = await client.post(app.url_path_for("start_cloud_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0"),
json=params)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json["pcap_file_path"]
assert "test.pcap" in response.json()["pcap_file_path"]
@pytest.mark.asyncio
async def test_cloud_stop_capture(compute_api, vm):
async def test_cloud_stop_capture(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.builtin.nodes.cloud.Cloud.stop_capture") as mock:
response = await compute_api.post("/projects/{project_id}/cloud/nodes/{node_id}/adapters/0/ports/0/capture/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
response = await client.post(app.url_path_for("stop_cloud_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0"))
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called

@ -18,34 +18,35 @@
import os
import pytest
from fastapi import FastAPI, status
from httpx import AsyncClient
from gns3server.version import __version__
from gns3server.compute.project import Project
pytestmark = pytest.mark.asyncio
@pytest.mark.asyncio
async def test_udp_allocation(compute_api, compute_project):
async def test_udp_allocation(app: FastAPI, client: AsyncClient, compute_project: Project) -> None:
response = await compute_api.post('/projects/{}/ports/udp'.format(compute_project.id), {})
assert response.status_code == 201
assert response.json['udp_port'] is not None
response = await client.post(app.url_path_for("allocate_udp_port", project_id=compute_project.id), json={})
assert response.status_code == status.HTTP_201_CREATED
assert response.json()['udp_port'] is not None
# Netfifaces is not available on Travis
@pytest.mark.skipif(os.environ.get("TRAVIS", False) is not False, reason="Not supported on Travis")
@pytest.mark.asyncio
async def test_interfaces(compute_api):
async def test_interfaces(app: FastAPI, client: AsyncClient) -> None:
response = await compute_api.get('/network/interfaces')
assert response.status_code == 200
assert isinstance(response.json, list)
response = await client.get(app.url_path_for("network_interfaces"))
assert response.status_code == status.HTTP_200_OK
assert isinstance(response.json(), list)
@pytest.mark.asyncio
async def test_version_output(compute_api, config):
async def test_version_output(app: FastAPI, client: AsyncClient, config) -> None:
config.set("Server", "local", "true")
response = await compute_api.get('/version')
assert response.status_code == 200
assert response.json == {'local': True, 'version': __version__}
response = await client.get(app.url_path_for("compute_version"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {'local': True, 'version': __version__}
# @pytest.mark.asyncio
@ -55,8 +56,7 @@ async def test_version_output(compute_api, config):
# assert response.status_code == 200
@pytest.mark.asyncio
async def test_statistics_output(compute_api):
async def test_statistics_output(app: FastAPI, client: AsyncClient) -> None:
response = await compute_api.get('/statistics')
assert response.status_code == 200
response = await client.get(app.url_path_for("compute_statistics"))
assert response.status_code == status.HTTP_200_OK

@ -17,16 +17,20 @@
import pytest
import sys
import uuid
from fastapi import FastAPI, status
from httpx import AsyncClient
from tests.utils import asyncio_patch
from unittest.mock import patch
pytestmark = pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
from gns3server.compute.project import Project
pytestmark = [pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows"),
pytest.mark.asyncio]
@pytest.fixture
def base_params():
def base_params() -> dict:
"""Return standard parameters"""
params = {
@ -53,90 +57,96 @@ def base_params():
@pytest.fixture
@pytest.mark.asyncio
async def vm(compute_api, compute_project, base_params):
async def vm(app: FastAPI, client: AsyncClient, compute_project: Project, base_params: dict) -> dict:
with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "nginx"}]):
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value={"Id": "8bd8153ea8f5"}):
with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="exited"):
response = await compute_api.post("/projects/{project_id}/docker/nodes".format(project_id=compute_project.id), base_params)
assert response.status_code == 201
return response.json
response = await client.post(app.url_path_for("create_docker_node", project_id=compute_project.id),
json=base_params)
assert response.status_code == status.HTTP_201_CREATED
return response.json()
@pytest.mark.asyncio
async def test_docker_create(compute_api, compute_project, base_params):
async def test_docker_create(app: FastAPI, client: AsyncClient, compute_project: Project, base_params: dict) -> None:
with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "nginx"}]):
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value={"Id": "8bd8153ea8f5"}):
response = await compute_api.post("/projects/{project_id}/docker/nodes".format(project_id=compute_project.id), base_params)
assert response.status_code == 201
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == compute_project.id
assert response.json["container_id"] == "8bd8153ea8f5"
assert response.json["image"] == "nginx:latest"
assert response.json["adapters"] == 2
assert response.json["environment"] == "YES=1\nNO=0"
assert response.json["console_resolution"] == "1280x1024"
assert response.json["extra_hosts"] == "test:127.0.0.1"
response = await client.post(app.url_path_for("create_docker_node", project_id=compute_project.id),
json=base_params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["container_id"] == "8bd8153ea8f5"
assert response.json()["image"] == "nginx:latest"
assert response.json()["adapters"] == 2
assert response.json()["environment"] == "YES=1\nNO=0"
assert response.json()["console_resolution"] == "1280x1024"
assert response.json()["extra_hosts"] == "test:127.0.0.1"
@pytest.mark.asyncio
async def test_docker_start(compute_api, vm):
async def test_docker_start(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.start", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/docker/nodes/{node_id}/start".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("start_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_docker_stop(compute_api, vm):
async def test_docker_stop(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.stop", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/docker/nodes/{node_id}/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("stop_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_docker_reload(compute_api, vm):
async def test_docker_reload(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.restart", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/docker/nodes/{node_id}/reload".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("reload_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_docker_delete(compute_api, vm):
async def test_docker_delete(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.delete", return_value=True) as mock:
response = await compute_api.delete("/projects/{project_id}/docker/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.delete(app.url_path_for("delete_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_docker_pause(compute_api, vm):
async def test_docker_pause(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.pause", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/docker/nodes/{node_id}/pause".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("pause_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_docker_unpause(compute_api, vm):
async def test_docker_unpause(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.unpause", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/docker/nodes/{node_id}/unpause".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("unpause_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_docker_nio_create_udp(compute_api, vm):
async def test_docker_nio_create_udp(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -144,13 +154,17 @@ async def test_docker_nio_create_udp(compute_api, vm):
"rport": 4343,
"rhost": "127.0.0.1"}
response = await compute_api.post("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
assert response.json["type"] == "nio_udp"
url = app.url_path_for("create_docker_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
@pytest.mark.asyncio
async def test_docker_update_nio(compute_api, vm):
async def test_docker_update_nio(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -159,23 +173,37 @@ async def test_docker_update_nio(compute_api, vm):
"rhost": "127.0.0.1"
}
response = await compute_api.post("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
url = app.url_path_for("create_docker_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
url = app.url_path_for("update_docker_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.adapter_update_nio_binding"):
response = await compute_api.put("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201, response.body.decode()
response = await client.put(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
@pytest.mark.asyncio
async def test_docker_delete_nio(compute_api, vm):
async def test_docker_delete_nio(app: FastAPI, client: AsyncClient, vm: dict) -> None:
url = app.url_path_for("delete_docker_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.adapter_remove_nio_binding"):
response = await compute_api.delete("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
response = await client.delete(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_docker_update(compute_api, vm, free_console_port):
async def test_docker_update(app: FastAPI, client: AsyncClient, vm: dict, free_console_port: int) -> None:
params = {
"name": "test",
@ -186,48 +214,62 @@ async def test_docker_update(compute_api, vm, free_console_port):
}
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.update") as mock:
response = await compute_api.put("/projects/{project_id}/docker/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
response = await client.put(app.url_path_for("update_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
assert response.status_code == 200
assert mock.called
assert response.json["name"] == "test"
assert response.json["console"] == free_console_port
assert response.json["start_command"] == "yes"
assert response.json["environment"] == "GNS3=1\nGNS4=0"
assert response.json["extra_hosts"] == "test:127.0.0.1"
assert response.json()["name"] == "test"
assert response.json()["console"] == free_console_port
assert response.json()["start_command"] == "yes"
assert response.json()["environment"] == "GNS3=1\nGNS4=0"
assert response.json()["extra_hosts"] == "test:127.0.0.1"
async def test_docker_start_capture(app: FastAPI, client: AsyncClient, vm: dict) -> None:
@pytest.mark.asyncio
async def test_docker_start_capture(compute_api, vm):
url = app.url_path_for("start_docker_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with patch("gns3server.compute.docker.docker_vm.DockerVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.start_capture") as mock:
params = {"capture_file_name": "test.pcap", "data_link_type": "DLT_EN10MB"}
response = await compute_api.post("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/capture/start".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params)
assert response.status_code == 200
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json["pcap_file_path"]
assert "test.pcap" in response.json()["pcap_file_path"]
@pytest.mark.asyncio
async def test_docker_stop_capture(compute_api, vm):
async def test_docker_stop_capture(app: FastAPI, client: AsyncClient, vm: dict) -> None:
url = app.url_path_for("stop_docker_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with patch("gns3server.compute.docker.docker_vm.DockerVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.stop_capture") as mock:
response = await compute_api.post("/projects/{project_id}/docker/nodes/{node_id}/adapters/0/ports/0/capture/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
response = await client.post(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
@pytest.mark.asyncio
async def test_docker_duplicate(compute_api, compute_project, base_params, vm):
async def test_docker_duplicate(app: FastAPI, client: AsyncClient, vm: dict, base_params: dict) -> None:
# create destination node first
with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "nginx"}]):
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value={"Id": "8bd8153ea8f5"}):
response = await compute_api.post("/projects/{project_id}/docker/nodes".format(project_id=compute_project.id), base_params)
assert response.status_code == 201
params = {"destination_node_id": response.json["node_id"]}
response = await compute_api.post("/projects/{project_id}/docker/nodes/{node_id}/duplicate".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_docker_node",
project_id=vm["project_id"]), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
params = {"destination_node_id": response.json()["node_id"]}
response = await client.post(app.url_path_for("duplicate_docker_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
assert response.status_code == status.HTTP_201_CREATED

@ -19,10 +19,12 @@ import pytest
import sys
import os
import stat
from unittest.mock import patch
from tests.utils import asyncio_patch
from unittest.mock import patch
from fastapi import FastAPI, status
from httpx import AsyncClient
pytestmark = pytest.mark.asyncio
# @pytest.yield_fixture(scope="module")
# async def vm(compute_api, compute_project, fake_image):
@ -139,7 +141,7 @@ from tests.utils import asyncio_patch
@pytest.fixture
def fake_image(tmpdir):
def fake_image(tmpdir) -> str:
"""Create a fake Dynamips image on disk"""
path = str(tmpdir / "7200.bin")
@ -150,7 +152,7 @@ def fake_image(tmpdir):
@pytest.fixture
def fake_file(tmpdir):
def fake_file(tmpdir) -> str:
"""Create a fake file disk"""
path = str(tmpdir / "7200.txt")
@ -160,24 +162,21 @@ def fake_file(tmpdir):
return path
@pytest.mark.asyncio
async def test_images(compute_api, tmpdir, fake_image, fake_file):
async def test_images(app: FastAPI, client: AsyncClient, tmpdir, fake_image: str, fake_file: str) -> None:
with patch("gns3server.utils.images.default_images_directory", return_value=str(tmpdir)):
response = await compute_api.get("/dynamips/images")
assert response.status_code == 200
assert response.json == [{"filename": "7200.bin",
"path": "7200.bin",
"filesize": 7,
"md5sum": "b0d5aa897d937aced5a6b1046e8f7e2e"
}]
response = await client.get(app.url_path_for("get_dynamips_images"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == [{"filename": "7200.bin",
"path": "7200.bin",
"filesize": 7,
"md5sum": "b0d5aa897d937aced5a6b1046e8f7e2e"}]
@pytest.mark.asyncio
async def test_upload_image(compute_api, images_dir):
async def test_upload_image(app: FastAPI, client: AsyncClient, images_dir: str) -> None:
response = await compute_api.post("/dynamips/images/test2", body=b"TEST", raw=True)
assert response.status_code == 204
response = await client.post(app.url_path_for("upload_dynamips_image", filename="test2"), content=b"TEST")
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(os.path.join(images_dir, "IOS", "test2")) as f:
assert f.read() == "TEST"
@ -188,13 +187,12 @@ async def test_upload_image(compute_api, images_dir):
@pytest.mark.skipif(not sys.platform.startswith("win") and os.getuid() == 0, reason="Root can delete any image")
@pytest.mark.asyncio
async def test_upload_image_permission_denied(compute_api, images_dir):
async def test_upload_image_permission_denied(app: FastAPI, client: AsyncClient, images_dir: str) -> None:
os.makedirs(os.path.join(images_dir, "IOS"), exist_ok=True)
with open(os.path.join(images_dir, "IOS", "test2.tmp"), "w+") as f:
f.write("")
os.chmod(os.path.join(images_dir, "IOS", "test2.tmp"), 0)
response = await compute_api.post("/dynamips/images/test2", body=b"TEST", raw=True)
assert response.status_code == 409
response = await client.post(app.url_path_for("upload_dynamips_image", filename="test2"), content=b"TEST")
assert response.status_code == status.HTTP_409_CONFLICT

@ -21,14 +21,19 @@ import stat
import sys
import uuid
from fastapi import FastAPI, status
from httpx import AsyncClient
from tests.utils import asyncio_patch
from unittest.mock import patch
pytestmark = pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
from gns3server.compute.project import Project
pytestmark = [pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows"),
pytest.mark.asyncio]
@pytest.fixture
def fake_iou_bin(images_dir):
def fake_iou_bin(images_dir) -> str:
"""Create a fake IOU image on disk"""
path = os.path.join(images_dir, "IOU", "iou.bin")
@ -39,44 +44,44 @@ def fake_iou_bin(images_dir):
@pytest.fixture
def base_params(tmpdir, fake_iou_bin):
def base_params(tmpdir, fake_iou_bin) -> dict:
"""Return standard parameters"""
return {"application_id": 42, "name": "PC TEST 1", "path": "iou.bin"}
@pytest.fixture
@pytest.mark.asyncio
async def vm(compute_api, compute_project, base_params):
async def vm(app: FastAPI, client: AsyncClient, compute_project: Project, base_params: dict) -> dict:
response = await compute_api.post("/projects/{project_id}/iou/nodes".format(project_id=compute_project.id), base_params)
assert response.status_code == 201
return response.json
response = await client.post(app.url_path_for("create_iou_node", project_id=compute_project.id), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
return response.json()
def startup_config_file(compute_project, vm):
def startup_config_file(compute_project: Project, vm: dict) -> str:
directory = os.path.join(compute_project.path, "project-files", "iou", vm["node_id"])
os.makedirs(directory, exist_ok=True)
return os.path.join(directory, "startup-config.cfg")
@pytest.mark.asyncio
async def test_iou_create(compute_api, compute_project, base_params):
async def test_iou_create(app: FastAPI, client: AsyncClient, compute_project: Project, base_params: dict) -> None:
response = await compute_api.post("/projects/{project_id}/iou/nodes".format(project_id=compute_project.id), base_params)
assert response.status_code == 201
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == compute_project.id
assert response.json["serial_adapters"] == 2
assert response.json["ethernet_adapters"] == 2
assert response.json["ram"] == 256
assert response.json["nvram"] == 128
assert response.json["l1_keepalives"] is False
response = await client.post(app.url_path_for("create_iou_node", project_id=compute_project.id), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["serial_adapters"] == 2
assert response.json()["ethernet_adapters"] == 2
assert response.json()["ram"] == 256
assert response.json()["nvram"] == 128
assert response.json()["l1_keepalives"] is False
@pytest.mark.asyncio
async def test_iou_create_with_params(compute_api, compute_project, base_params):
async def test_iou_create_with_params(app: FastAPI,
client: AsyncClient,
compute_project: Project,
base_params: dict) -> None:
params = base_params
params["ram"] = 1024
@ -87,23 +92,26 @@ async def test_iou_create_with_params(compute_api, compute_project, base_params)
params["startup_config_content"] = "hostname test"
params["use_default_iou_values"] = False
response = await compute_api.post("/projects/{project_id}/iou/nodes".format(project_id=compute_project.id), params)
assert response.status_code == 201
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == compute_project.id
assert response.json["serial_adapters"] == 4
assert response.json["ethernet_adapters"] == 0
assert response.json["ram"] == 1024
assert response.json["nvram"] == 512
assert response.json["l1_keepalives"] is True
assert response.json["use_default_iou_values"] is False
with open(startup_config_file(compute_project, response.json)) as f:
response = await client.post(app.url_path_for("create_iou_node", project_id=compute_project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["serial_adapters"] == 4
assert response.json()["ethernet_adapters"] == 0
assert response.json()["ram"] == 1024
assert response.json()["nvram"] == 512
assert response.json()["l1_keepalives"] is True
assert response.json()["use_default_iou_values"] is False
with open(startup_config_file(compute_project, response.json())) as f:
assert f.read() == "hostname test"
@pytest.mark.asyncio
async def test_iou_create_startup_config_already_exist(compute_api, compute_project, base_params):
async def test_iou_create_startup_config_already_exist(
app: FastAPI,
client: AsyncClient,
compute_project: Project,
base_params: dict) -> None:
"""We don't erase a startup-config if already exist at project creation"""
node_id = str(uuid.uuid4())
@ -115,78 +123,78 @@ async def test_iou_create_startup_config_already_exist(compute_api, compute_proj
params["node_id"] = node_id
params["startup_config_content"] = "hostname test"
response = await compute_api.post("/projects/{project_id}/iou/nodes".format(project_id=compute_project.id), params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_iou_node", project_id=compute_project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED
with open(startup_config_file(compute_project, response.json)) as f:
with open(startup_config_file(compute_project, response.json())) as f:
assert f.read() == "echo hello"
@pytest.mark.asyncio
async def test_iou_get(compute_api, compute_project, vm):
async def test_iou_get(app: FastAPI, client: AsyncClient, compute_project: Project, vm: dict) -> None:
response = await compute_api.get("/projects/{project_id}/iou/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 200
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == compute_project.id
assert response.json["serial_adapters"] == 2
assert response.json["ethernet_adapters"] == 2
assert response.json["ram"] == 256
assert response.json["nvram"] == 128
assert response.json["l1_keepalives"] is False
response = await client.get(app.url_path_for("get_iou_node", project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["serial_adapters"] == 2
assert response.json()["ethernet_adapters"] == 2
assert response.json()["ram"] == 256
assert response.json()["nvram"] == 128
assert response.json()["l1_keepalives"] is False
@pytest.mark.asyncio
async def test_iou_start(compute_api, vm):
async def test_iou_start(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.start", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/iou/nodes/{node_id}/start".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("start_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json={})
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_iou_start_with_iourc(compute_api, vm):
async def test_iou_start_with_iourc(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {"iourc_content": "test"}
with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.start", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/iou/nodes/{node_id}/start".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
response = await client.post(app.url_path_for("start_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
assert mock.called
assert response.status_code == 204
response = await compute_api.get("/projects/{project_id}/iou/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 200
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_iou_stop(compute_api, vm):
async def test_iou_stop(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.stop", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/iou/nodes/{node_id}/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("stop_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_iou_reload(compute_api, vm):
async def test_iou_reload(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.reload", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/iou/nodes/{node_id}/reload".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("reload_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_iou_delete(compute_api, vm):
async def test_iou_delete(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.iou.IOU.delete_node", return_value=True) as mock:
response = await compute_api.delete("/projects/{project_id}/iou/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.delete(app.url_path_for("delete_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_iou_update(compute_api, vm, free_console_port):
async def test_iou_update(app: FastAPI, client: AsyncClient, vm: dict, free_console_port: int) -> None:
params = {
"name": "test",
@ -199,91 +207,122 @@ async def test_iou_update(compute_api, vm, free_console_port):
"use_default_iou_values": True,
}
response = await compute_api.put("/projects/{project_id}/iou/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 200
assert response.json["name"] == "test"
assert response.json["console"] == free_console_port
assert response.json["ethernet_adapters"] == 4
assert response.json["serial_adapters"] == 0
assert response.json["ram"] == 512
assert response.json["nvram"] == 2048
assert response.json["l1_keepalives"] is True
assert response.json["use_default_iou_values"] is True
response = await client.put(app.url_path_for("update_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
assert response.json()["console"] == free_console_port
assert response.json()["ethernet_adapters"] == 4
assert response.json()["serial_adapters"] == 0
assert response.json()["ram"] == 512
assert response.json()["nvram"] == 2048
assert response.json()["l1_keepalives"] is True
assert response.json()["use_default_iou_values"] is True
@pytest.mark.asyncio
async def test_iou_nio_create_udp(compute_api, vm):
async def test_iou_nio_create_udp(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"}
response = await compute_api.post("/projects/{project_id}/iou/nodes/{node_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
assert response.json["type"] == "nio_udp"
url = app.url_path_for("create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
@pytest.mark.asyncio
async def test_iou_nio_update_udp(compute_api, vm):
async def test_iou_nio_update_udp(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {"type": "nio_udp",
"lport": 4242,
"rport": 4343,
"rhost": "127.0.0.1"}
await compute_api.post("/projects/{project_id}/iou/nodes/{node_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
url = app.url_path_for("create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
await client.post(url, json=params)
params["filters"] = {}
response = await compute_api.put("/projects/{project_id}/iou/nodes/{node_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201, response.body.decode()
assert response.json["type"] == "nio_udp"
url = app.url_path_for("update_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
response = await client.put(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
@pytest.mark.asyncio
async def test_iou_nio_create_ethernet(compute_api, vm, ethernet_device):
async def test_iou_nio_create_ethernet(app: FastAPI, client: AsyncClient, vm: dict, ethernet_device: str) -> None:
params = {
"type": "nio_ethernet",
"ethernet_device": ethernet_device
}
response = await compute_api.post("/projects/{project_id}/iou/nodes/{node_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
assert response.json["type"] == "nio_ethernet"
assert response.json["ethernet_device"] == ethernet_device
url = app.url_path_for("create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_ethernet"
assert response.json()["ethernet_device"] == ethernet_device
@pytest.mark.asyncio
async def test_iou_nio_create_ethernet_different_port(compute_api, vm, ethernet_device):
async def test_iou_nio_create_ethernet_different_port(app: FastAPI,
client: AsyncClient,
vm: dict,
ethernet_device: str) -> None:
params = {
"type": "nio_ethernet",
"ethernet_device": ethernet_device
}
response = await compute_api.post("/projects/{project_id}/iou/nodes/{node_id}/adapters/0/ports/3/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
assert response.json["type"] == "nio_ethernet"
assert response.json["ethernet_device"] == ethernet_device
url = app.url_path_for("create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="3")
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_ethernet"
assert response.json()["ethernet_device"] == ethernet_device
@pytest.mark.asyncio
async def test_iou_nio_create_tap(compute_api, vm, ethernet_device):
async def test_iou_nio_create_tap(app: FastAPI, client: AsyncClient, vm: dict, ethernet_device: str) -> None:
params = {
"type": "nio_tap",
"tap_device": ethernet_device
}
url = app.url_path_for("create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
with patch("gns3server.compute.base_manager.BaseManager.has_privileged_access", return_value=True):
response = await compute_api.post("/projects/{project_id}/iou/nodes/{node_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
assert response.json["type"] == "nio_tap"
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_tap"
@pytest.mark.asyncio
async def test_iou_delete_nio(compute_api, vm):
async def test_iou_delete_nio(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -292,33 +331,57 @@ async def test_iou_delete_nio(compute_api, vm):
"rhost": "127.0.0.1"
}
await compute_api.post("/projects/{project_id}/iou/nodes/{node_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
response = await compute_api.delete("/projects/{project_id}/iou/nodes/{node_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
url = app.url_path_for("create_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
await client.post(url, json=params)
url = app.url_path_for("delete_iou_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
response = await client.delete(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_iou_start_capture(compute_api, vm):
async def test_iou_start_capture(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"capture_file_name": "test.pcap",
"data_link_type": "DLT_EN10MB"
}
url = app.url_path_for("start_iou_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with patch("gns3server.compute.iou.iou_vm.IOUVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.start_capture") as mock:
response = await compute_api.post("/projects/{project_id}/iou/nodes/{node_id}/adapters/0/ports/0/capture/start".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 200
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json["pcap_file_path"]
assert "test.pcap" in response.json()["pcap_file_path"]
@pytest.mark.asyncio
async def test_iou_stop_capture(compute_api, vm):
async def test_iou_stop_capture(app: FastAPI, client: AsyncClient, vm: dict) -> None:
url = app.url_path_for("stop_iou_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with patch("gns3server.compute.iou.iou_vm.IOUVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.stop_capture") as mock:
response = await compute_api.post("/projects/{project_id}/iou/nodes/{node_id}/adapters/0/ports/0/capture/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
response = await client.post(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
@ -327,24 +390,22 @@ async def test_iou_stop_capture(compute_api, vm):
#
# with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM.get_nio"):
# with asyncio_patch("gns3server.compute.iou.IOU.stream_pcap_file"):
# response = await compute_api.get("/projects/{project_id}/iou/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=compute_project.id, node_id=vm["node_id"]), raw=True)
# assert response.status_code == 200
# response = await client.get("/projects/{project_id}/iou/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=compute_project.id, node_id=vm["node_id"]), raw=True)
# assert response.status_code == status.HTTP_200_OK
@pytest.mark.asyncio
async def test_images(compute_api, fake_iou_bin):
async def test_images(app: FastAPI, client: AsyncClient, fake_iou_bin: str) -> None:
response = await compute_api.get("/iou/images")
assert response.status_code == 200
assert response.json == [{"filename": "iou.bin", "path": "iou.bin", "filesize": 7, "md5sum": "e573e8f5c93c6c00783f20c7a170aa6c"}]
response = await client.get(app.url_path_for("get_iou_images"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == [{"filename": "iou.bin", "path": "iou.bin", "filesize": 7, "md5sum": "e573e8f5c93c6c00783f20c7a170aa6c"}]
@pytest.mark.asyncio
async def test_image_vm(compute_api, tmpdir):
async def test_image_vm(app: FastAPI, client: AsyncClient, tmpdir) -> None:
with patch("gns3server.compute.IOU.get_images_directory", return_value=str(tmpdir)):
response = await compute_api.post("/iou/images/test2", body="TEST", raw=True)
assert response.status_code == 204
response = await client.post(app.url_path_for("download_iou_image", filename="test2"), content=b"TEST")
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(str(tmpdir / "test2")) as f:
assert f.read() == "TEST"
@ -354,13 +415,15 @@ async def test_image_vm(compute_api, tmpdir):
assert checksum == "033bd94b1168d7e4f0d644c3c95e35bf"
@pytest.mark.asyncio
async def test_iou_duplicate(compute_api, compute_project, vm, base_params):
async def test_iou_duplicate(app: FastAPI, client: AsyncClient, vm: dict, base_params: dict) -> None:
# create destination node first
response = await compute_api.post("/projects/{project_id}/iou/nodes".format(project_id=compute_project.id), base_params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_iou_node", project_id=vm["project_id"]), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
params = {"destination_node_id": response.json()["node_id"]}
params = {"destination_node_id": response.json["node_id"]}
response = await compute_api.post("/projects/{project_id}/iou/nodes/{node_id}/duplicate".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
response = await client.post(app.url_path_for("duplicate_iou_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
assert response.status_code == status.HTTP_201_CREATED

@ -17,41 +17,45 @@
import pytest
from fastapi import FastAPI, status
from httpx import AsyncClient
from tests.utils import asyncio_patch
from gns3server.compute.project import Project
pytestmark = pytest.mark.asyncio
@pytest.fixture(scope="function")
@pytest.mark.asyncio
async def vm(compute_api, compute_project, ubridge_path, on_gns3vm):
async def vm(app: FastAPI, client: AsyncClient, compute_project: Project, ubridge_path: str, on_gns3vm) -> dict:
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat._start_ubridge"):
response = await compute_api.post("/projects/{project_id}/nat/nodes".format(project_id=compute_project.id), {"name": "Nat 1"})
assert response.status_code == 201
return response.json
response = await client.post(app.url_path_for("create_nat_node", project_id=compute_project.id),
json={"name": "Nat 1"})
assert response.status_code == status.HTTP_201_CREATED
return response.json()
@pytest.mark.asyncio
async def test_nat_create(compute_api, compute_project, on_gns3vm):
async def test_nat_create(app: FastAPI, client: AsyncClient, compute_project: Project, on_gns3vm) -> None:
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat._start_ubridge"):
response = await compute_api.post("/projects/{project_id}/nat/nodes".format(project_id=compute_project.id), {"name": "Nat 1"})
assert response.status_code == 201
assert response.json["name"] == "Nat 1"
assert response.json["project_id"] == compute_project.id
response = await client.post(app.url_path_for("create_nat_node", project_id=compute_project.id),
json={"name": "Nat 1"})
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "Nat 1"
assert response.json()["project_id"] == compute_project.id
@pytest.mark.asyncio
async def test_nat_get(compute_api, compute_project, vm):
async def test_nat_get(app: FastAPI, client: AsyncClient, compute_project: Project, vm: dict) -> None:
response = await compute_api.get("/projects/{project_id}/nat/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 200
assert response.json["name"] == "Nat 1"
assert response.json["project_id"] == compute_project.id
assert response.json["status"] == "started"
response = await client.get(app.url_path_for("get_nat_node", project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "Nat 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["status"] == "started"
@pytest.mark.asyncio
async def test_nat_nio_create_udp(compute_api, vm):
async def test_nat_nio_create_udp(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -60,14 +64,19 @@ async def test_nat_nio_create_udp(compute_api, vm):
"rhost": "127.0.0.1"
}
url = app.url_path_for("create_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.add_nio"):
response = await compute_api.post("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
assert response.json["type"] == "nio_udp"
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
@pytest.mark.asyncio
async def test_nat_nio_update_udp(compute_api, vm):
async def test_nat_nio_update_udp(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -76,15 +85,26 @@ async def test_nat_nio_update_udp(compute_api, vm):
"rhost": "127.0.0.1"
}
await compute_api.post("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
url = app.url_path_for("create_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
await client.post(url, json=params)
params["filters"] = {}
response = await compute_api.put("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201, response.body.decode()
assert response.json["type"] == "nio_udp"
url = app.url_path_for("update_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
response = await client.put(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
@pytest.mark.asyncio
async def test_nat_delete_nio(compute_api, vm):
async def test_nat_delete_nio(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -93,50 +113,73 @@ async def test_nat_delete_nio(compute_api, vm):
"rhost": "127.0.0.1"
}
url = app.url_path_for("create_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.add_nio"):
await compute_api.post("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
await client.post(url, json=params)
url = app.url_path_for("delete_nat_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.remove_nio") as mock:
response = await compute_api.delete("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.delete(url)
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_nat_delete(compute_api, vm):
async def test_nat_delete(app: FastAPI, client: AsyncClient, vm: dict) -> None:
response = await compute_api.delete("/projects/{project_id}/nat/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
response = await client.delete(app.url_path_for("delete_nat_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_nat_update(compute_api, vm):
response = await compute_api.put("/projects/{project_id}/nat/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), {"name": "test"})
assert response.status_code == 200
assert response.json["name"] == "test"
async def test_nat_update(app: FastAPI, client: AsyncClient, vm: dict) -> None:
response = await client.put(app.url_path_for("update_nat_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json={"name": "test"})
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
@pytest.mark.asyncio
async def test_nat_start_capture(compute_api, vm):
async def test_nat_start_capture(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"capture_file_name": "test.pcap",
"data_link_type": "DLT_EN10MB"
}
url = app.url_path_for("start_nat_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.start_capture") as mock:
response = await compute_api.post("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/capture/start".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params)
assert response.status_code == 200
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json["pcap_file_path"]
assert "test.pcap" in response.json()["pcap_file_path"]
async def test_nat_stop_capture(app: FastAPI, client: AsyncClient, vm: dict) -> None:
@pytest.mark.asyncio
async def test_nat_stop_capture(compute_api, vm):
url = app.url_path_for("stop_nat_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.stop_capture") as mock:
response = await compute_api.post("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/capture/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
response = await client.post(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
@ -145,5 +188,5 @@ async def test_nat_stop_capture(compute_api, vm):
#
# with asyncio_patch("gns3server.compute.builtin.nodes.nat.Nat.get_nio"):
# with asyncio_patch("gns3server.compute.builtin.Builtin.stream_pcap_file"):
# response = await compute_api.get("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=compute_project.id, node_id=vm["node_id"]), raw=True)
# assert response.status_code == 200
# response = await client.get("/projects/{project_id}/nat/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=compute_project.id, node_id=vm["node_id"]), raw=True)
# assert response.status_code == status.HTTP_200_OK

@ -21,11 +21,11 @@ import json
from gns3server.compute.notification_manager import NotificationManager
@pytest.mark.asyncio
async def test_notification_ws(compute_api):
# FIXME: how to test websockets
pass
# @pytest.mark.asyncio
# async def test_notification_ws(compute_api):
#
# # FIXME: how to test websockets
# pass
#with compute_api.ws("/notifications/ws") as ws:

@ -21,12 +21,17 @@ import os
from unittest.mock import patch
from tests.utils import asyncio_patch
from fastapi import FastAPI, status
from httpx import AsyncClient
from gns3server.compute.project_manager import ProjectManager
from gns3server.compute.project import Project
pytestmark = pytest.mark.asyncio
@pytest.fixture
def base_params(tmpdir):
def base_params(tmpdir) -> dict:
"""Return standard parameters"""
params = {
@ -37,109 +42,103 @@ def base_params(tmpdir):
return params
@pytest.mark.asyncio
async def test_create_project_with_path(compute_api, base_params):
async def test_create_project_with_path(app: FastAPI, client: AsyncClient, base_params: dict) -> None:
with patch("gns3server.compute.project.Project.is_local", return_value=True):
response = await compute_api.post("/projects", base_params)
assert response.status_code == 201
assert response.json["project_id"] == base_params["project_id"]
response = await client.post(app.url_path_for("create_compute_project"), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["project_id"] == base_params["project_id"]
@pytest.mark.asyncio
async def test_create_project_with_path_and_empty_variables(compute_api, base_params):
async def test_create_project_with_path_and_empty_variables(app: FastAPI,
client: AsyncClient,
base_params: dict) -> None:
base_params["variables"] = None
with patch("gns3server.compute.project.Project.is_local", return_value=True):
response = await compute_api.post("/projects", base_params)
assert response.status_code == 201
assert response.json["project_id"] == base_params["project_id"]
response = await client.post(app.url_path_for("create_compute_project"), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["project_id"] == base_params["project_id"]
@pytest.mark.asyncio
async def test_create_project_without_dir(compute_api, base_params):
async def test_create_project_without_dir(app: FastAPI, client: AsyncClient, base_params: dict) -> None:
del base_params["path"]
response = await compute_api.post("/projects", base_params)
assert response.status_code == 201
assert response.json["project_id"] == base_params["project_id"]
assert response.json["name"] == base_params["name"]
response = await client.post(app.url_path_for("create_compute_project"), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["project_id"] == base_params["project_id"]
assert response.json()["name"] == base_params["name"]
@pytest.mark.asyncio
async def test_show_project(compute_api, base_params):
async def test_show_project(app: FastAPI, client: AsyncClient, base_params: dict) -> None:
response = await compute_api.post("/projects", base_params)
assert response.status_code == 201
response = await compute_api.get("/projects/{project_id}".format(project_id=base_params["project_id"]))
response = await client.post(app.url_path_for("create_compute_project"), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
response = await client.get(app.url_path_for("get_compute_project", project_id=base_params["project_id"]))
#print(response.json.keys())
#assert len(response.json.keys()) == 3
assert response.json["project_id"] == base_params["project_id"]
assert response.json["name"] == base_params["name"]
assert response.json["variables"] is None
#print(response.json().keys())
#assert len(response.json().keys()) == 3
assert response.json()["project_id"] == base_params["project_id"]
assert response.json()["name"] == base_params["name"]
assert response.json()["variables"] is None
@pytest.mark.asyncio
async def test_show_project_invalid_uuid(compute_api):
async def test_show_project_invalid_uuid(app: FastAPI, client: AsyncClient) -> None:
response = await compute_api.get("/projects/50010203-0405-0607-0809-0a0b0c0d0e42")
assert response.status_code == 404
response = await client.get(app.url_path_for("get_compute_project",
project_id="50010203-0405-0607-0809-0a0b0c0d0e42"))
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_list_projects(compute_api):
async def test_list_projects(app: FastAPI, client: AsyncClient) -> dict:
ProjectManager.instance()._projects = {}
params = {"name": "test", "project_id": "51010203-0405-0607-0809-0a0b0c0d0e0f"}
response = await compute_api.post("/projects", params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_compute_project"), json=params)
assert response.status_code == status.HTTP_201_CREATED
params = {"name": "test", "project_id": "52010203-0405-0607-0809-0a0b0c0d0e0b"}
response = await compute_api.post("/projects", params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_compute_project"), json=params)
assert response.status_code == status.HTTP_201_CREATED
response = await compute_api.get("/projects")
assert response.status_code == 200
assert len(response.json) == 2
assert "51010203-0405-0607-0809-0a0b0c0d0e0f" in [p["project_id"] for p in response.json]
response = await client.get(app.url_path_for("get_compute_projects"))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 2
assert "51010203-0405-0607-0809-0a0b0c0d0e0f" in [p["project_id"] for p in response.json()]
@pytest.mark.asyncio
async def test_delete_project(compute_api, compute_project):
async def test_delete_project(app: FastAPI, client: AsyncClient, compute_project: Project) -> None:
with asyncio_patch("gns3server.compute.project.Project.delete", return_value=True) as mock:
response = await compute_api.delete("/projects/{project_id}".format(project_id=compute_project.id))
assert response.status_code == 204
response = await client.delete(app.url_path_for("delete_compute_project", project_id=compute_project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
@pytest.mark.asyncio
async def test_update_project(compute_api, base_params):
async def test_update_project(app: FastAPI, client: AsyncClient, base_params: dict) -> None:
response = await compute_api.post("/projects", base_params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_compute_project"), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
params = {"variables": [{"name": "TEST1", "value": "VAL1"}]}
response = await compute_api.put("/projects/{project_id}".format(project_id=base_params["project_id"]), params)
assert response.status_code == 200
assert response.json["variables"] == [{"name": "TEST1", "value": "VAL1"}]
response = await client.put(app.url_path_for("update_compute_project", project_id=base_params["project_id"]),
json=params)
assert response.status_code == status.HTTP_200_OK
assert response.json()["variables"] == [{"name": "TEST1", "value": "VAL1"}]
@pytest.mark.asyncio
async def test_delete_project_invalid_uuid(compute_api):
async def test_delete_project_invalid_uuid(app: FastAPI, client: AsyncClient) -> None:
response = await compute_api.delete("/projects/{project_id}".format(project_id=uuid.uuid4()))
assert response.status_code == 404
response = await client.delete(app.url_path_for("delete_compute_project", project_id=str(uuid.uuid4())))
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_close_project(compute_api, compute_project):
async def test_close_project(app: FastAPI, client: AsyncClient, compute_project: Project) -> None:
with asyncio_patch("gns3server.compute.project.Project.close", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/close".format(project_id=compute_project.id))
assert response.status_code == 204
response = await client.post(app.url_path_for("close_compute_project", project_id=compute_project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
@ -148,20 +147,18 @@ async def test_close_project(compute_api, compute_project):
#
# ProjectHandler._notifications_listening = {compute_project.id: 2}
# with asyncio_patch("gns3server.compute.project.Project.close", return_value=True) as mock:
# response = await compute_api.post("/projects/{project_id}/close".format(project_id=compute_project.id))
# assert response.status_code == 204
# response = await client.post("/projects/{project_id}/close".format(project_id=compute_project.id))
# assert response.status_code == status.HTTP_204_NO_CONTENT
# assert not mock.called
@pytest.mark.asyncio
async def test_close_project_invalid_uuid(compute_api):
async def test_close_project_invalid_uuid(app: FastAPI, client: AsyncClient) -> None:
response = await compute_api.post("/projects/{project_id}/close".format(project_id=uuid.uuid4()))
assert response.status_code == 404
response = await client.post(app.url_path_for("close_compute_project", project_id=str(uuid.uuid4())))
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_get_file(compute_api, tmpdir):
async def test_get_file(app: FastAPI, client: AsyncClient, tmpdir) -> None:
with patch("gns3server.config.Config.get_section_config", return_value={"projects_path": str(tmpdir)}):
project = ProjectManager.instance().create_project(project_id="01010203-0405-0607-0809-0a0b0c0d0e0b")
@ -169,48 +166,33 @@ async def test_get_file(compute_api, tmpdir):
with open(os.path.join(project.path, "hello"), "w+") as f:
f.write("world")
response = await compute_api.get("/projects/{project_id}/files/hello".format(project_id=project.id), raw=True)
assert response.status_code == 200
response = await client.get(app.url_path_for("get_compute_project_file", project_id=project.id, file_path="hello"))
assert response.status_code == status.HTTP_200_OK
assert response.content == b"world"
response = await compute_api.get("/projects/{project_id}/files/false".format(project_id=project.id), raw=True)
assert response.status_code == 404
response = await client.get(app.url_path_for("get_compute_project_file", project_id=project.id, file_path="false"))
assert response.status_code == status.HTTP_404_NOT_FOUND
response = await compute_api.get("/projects/{project_id}/files/../hello".format(project_id=project.id), raw=True)
assert response.status_code == 404
response = await client.get(app.url_path_for("get_compute_project_file",
project_id=project.id,
file_path="../hello"))
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_write_file(compute_api, tmpdir):
async def test_write_file(app: FastAPI, client: AsyncClient, tmpdir) -> None:
with patch("gns3server.config.Config.get_section_config", return_value={"projects_path": str(tmpdir)}):
project = ProjectManager.instance().create_project(project_id="01010203-0405-0607-0809-0a0b0c0d0e0b")
response = await compute_api.post("/projects/{project_id}/files/hello".format(project_id=project.id), body="world", raw=True)
assert response.status_code == 204
response = await client.post(app.url_path_for("write_compute_project_file",
project_id=project.id,
file_path="hello"), content=b"world")
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(os.path.join(project.path, "hello")) as f:
assert f.read() == "world"
response = await compute_api.post("/projects/{project_id}/files/../hello".format(project_id=project.id), raw=True)
assert response.status_code == 404
@pytest.mark.asyncio
async def test_stream_file(compute_api, tmpdir):
with patch("gns3server.config.Config.get_section_config", return_value={"projects_path": str(tmpdir)}):
project = ProjectManager.instance().create_project(project_id="01010203-0405-0607-0809-0a0b0c0d0e0b")
with open(os.path.join(project.path, "hello"), "w+") as f:
f.write("world")
response = await compute_api.get("/projects/{project_id}/files/hello".format(project_id=project.id), raw=True)
assert response.status_code == 200
assert response.content == b"world"
response = await compute_api.get("/projects/{project_id}/files/false".format(project_id=project.id), raw=True)
assert response.status_code == 404
response = await compute_api.get("/projects/{project_id}/files/../hello".format(project_id=project.id), raw=True)
assert response.status_code == 404
response = await client.post(app.url_path_for("write_compute_project_file",
project_id=project.id,
file_path="../hello"))
assert response.status_code == status.HTTP_404_NOT_FOUND

@ -16,16 +16,22 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
import uuid
import os
import sys
import stat
from fastapi import FastAPI, status
from httpx import AsyncClient
from tests.utils import asyncio_patch
from unittest.mock import patch
from gns3server.compute.project import Project
pytestmark = pytest.mark.asyncio
@pytest.fixture
def fake_qemu_bin(monkeypatch, tmpdir):
def fake_qemu_bin(monkeypatch, tmpdir) -> str:
monkeypatch.setenv("PATH", str(tmpdir))
if sys.platform.startswith("win"):
@ -40,7 +46,7 @@ def fake_qemu_bin(monkeypatch, tmpdir):
@pytest.fixture
def fake_qemu_vm(images_dir):
def fake_qemu_vm(images_dir) -> str:
img_dir = os.path.join(images_dir, "QEMU")
bin_path = os.path.join(img_dir, "linux载.img")
@ -51,140 +57,165 @@ def fake_qemu_vm(images_dir):
@pytest.fixture
def base_params(tmpdir, fake_qemu_bin):
def base_params(tmpdir, fake_qemu_bin) -> dict:
"""Return standard parameters"""
return {"name": "PC TEST 1", "qemu_path": fake_qemu_bin}
@pytest.fixture
@pytest.mark.asyncio
async def vm(compute_api, compute_project, base_params):
async def vm(app: FastAPI, client: AsyncClient, compute_project: Project, base_params: dict) -> None:
response = await compute_api.post("/projects/{project_id}/qemu/nodes".format(project_id=compute_project.id), base_params)
assert response.status_code == 201
return response.json
response = await client.post(app.url_path_for("create_qemu_node", project_id=compute_project.id), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
return response.json()
@pytest.mark.asyncio
async def test_qemu_create(compute_api, compute_project, base_params, fake_qemu_bin):
async def test_qemu_create(app: FastAPI,
client: AsyncClient,
compute_project: Project,
base_params: dict,
fake_qemu_bin: str) -> None:
response = await compute_api.post("/projects/{project_id}/qemu/nodes".format(project_id=compute_project.id), base_params)
assert response.status_code == 201
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == compute_project.id
assert response.json["qemu_path"] == fake_qemu_bin
assert response.json["platform"] == "x86_64"
response = await client.post(app.url_path_for("create_qemu_node", project_id=compute_project.id), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["qemu_path"] == fake_qemu_bin
assert response.json()["platform"] == "x86_64"
@pytest.mark.asyncio
async def test_qemu_create_platform(compute_api, compute_project, base_params, fake_qemu_bin):
async def test_qemu_create_platform(app: FastAPI,
client: AsyncClient,
compute_project: Project,
base_params: dict,
fake_qemu_bin: str):
base_params["qemu_path"] = None
base_params["platform"] = "x86_64"
response = await compute_api.post("/projects/{project_id}/qemu/nodes".format(project_id=compute_project.id), base_params)
assert response.status_code == 201
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == compute_project.id
assert response.json["qemu_path"] == fake_qemu_bin
assert response.json["platform"] == "x86_64"
response = await client.post(app.url_path_for("create_qemu_node", project_id=compute_project.id), json=base_params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["qemu_path"] == fake_qemu_bin
assert response.json()["platform"] == "x86_64"
@pytest.mark.asyncio
async def test_qemu_create_with_params(compute_api, compute_project, base_params, fake_qemu_vm):
async def test_qemu_create_with_params(app: FastAPI,
client: AsyncClient,
compute_project: Project,
base_params: dict,
fake_qemu_vm: str):
params = base_params
params["ram"] = 1024
params["hda_disk_image"] = "linux载.img"
response = await compute_api.post("/projects/{project_id}/qemu/nodes".format(project_id=compute_project.id), params)
assert response.status_code == 201
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == compute_project.id
assert response.json["ram"] == 1024
assert response.json["hda_disk_image"] == "linux载.img"
assert response.json["hda_disk_image_md5sum"] == "c4ca4238a0b923820dcc509a6f75849b"
response = await client.post(app.url_path_for("create_qemu_node", project_id=compute_project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["ram"] == 1024
assert response.json()["hda_disk_image"] == "linux载.img"
assert response.json()["hda_disk_image_md5sum"] == "c4ca4238a0b923820dcc509a6f75849b"
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
@pytest.mark.asyncio
async def test_qemu_create_with_project_file(compute_api, compute_project, base_params, fake_qemu_vm):
response = await compute_api.post("/projects/{project_id}/files/hello.img".format(project_id=compute_project.id), body=b"world", raw=True)
assert response.status_code == 204
async def test_qemu_create_with_project_file(app: FastAPI,
client: AsyncClient,
compute_project: Project,
base_params: dict,
fake_qemu_vm: str) -> None:
response = await client.post(app.url_path_for("write_compute_project_file",
project_id=compute_project.id,
file_path="hello.img"), content=b"world")
assert response.status_code == status.HTTP_204_NO_CONTENT
params = base_params
params["hda_disk_image"] = "hello.img"
response = await compute_api.post("/projects/{project_id}/qemu/nodes".format(project_id=compute_project.id), params)
assert response.status_code == 201
assert response.json["hda_disk_image"] == "hello.img"
assert response.json["hda_disk_image_md5sum"] == "7d793037a0760186574b0282f2f435e7"
response = await client.post(app.url_path_for("create_qemu_node", project_id=compute_project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["hda_disk_image"] == "hello.img"
assert response.json()["hda_disk_image_md5sum"] == "7d793037a0760186574b0282f2f435e7"
@pytest.mark.asyncio
async def test_qemu_get(compute_api, compute_project, vm):
async def test_qemu_get(app: FastAPI, client: AsyncClient, compute_project: Project, vm: dict):
response = await compute_api.get("/projects/{project_id}/qemu/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 200
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == compute_project.id
assert response.json["node_directory"] == os.path.join(compute_project.path, "project-files", "qemu", vm["node_id"])
response = await client.get(app.url_path_for("get_qemu_node", project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["node_directory"] == os.path.join(compute_project.path,
"project-files",
"qemu",
vm["node_id"])
@pytest.mark.asyncio
async def test_qemu_start(compute_api, vm):
async def test_qemu_start(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.start", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/qemu/nodes/{node_id}/start".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("start_qemu_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_qemu_stop(compute_api, vm):
async def test_qemu_stop(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.stop", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/qemu/nodes/{node_id}/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("stop_qemu_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_qemu_reload(compute_api, vm):
async def test_qemu_reload(app: FastAPI, client: AsyncClient, vm) -> None:
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.reload", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/qemu/nodes/{node_id}/reload".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("reload_qemu_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_qemu_suspend(compute_api, vm):
async def test_qemu_suspend(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.suspend", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/qemu/nodes/{node_id}/suspend".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("suspend_qemu_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_qemu_resume(compute_api, vm):
async def test_qemu_resume(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.resume", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/qemu/nodes/{node_id}/resume".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("resume_qemu_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_qemu_delete(compute_api, vm):
async def test_qemu_delete(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.qemu.Qemu.delete_node", return_value=True) as mock:
response = await compute_api.delete("/projects/{project_id}/qemu/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.delete(app.url_path_for("delete_qemu_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_qemu_update(compute_api, vm, free_console_port, fake_qemu_vm):
async def test_qemu_update(app: FastAPI,
client: AsyncClient,
vm: dict,
free_console_port: int,
fake_qemu_vm: str) -> None:
params = {
"name": "test",
@ -193,16 +224,17 @@ async def test_qemu_update(compute_api, vm, free_console_port, fake_qemu_vm):
"hdb_disk_image": "linux载.img"
}
response = await compute_api.put("/projects/{project_id}/qemu/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 200
assert response.json["name"] == "test"
assert response.json["console"] == free_console_port
assert response.json["hdb_disk_image"] == "linux载.img"
assert response.json["ram"] == 1024
response = await client.put(app.url_path_for("update_qemu_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
assert response.json()["console"] == free_console_port
assert response.json()["hdb_disk_image"] == "linux载.img"
assert response.json()["ram"] == 1024
@pytest.mark.asyncio
async def test_qemu_nio_create_udp(compute_api, vm):
async def test_qemu_nio_create_udp(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -212,14 +244,21 @@ async def test_qemu_nio_create_udp(compute_api, vm):
}
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.add_ubridge_udp_connection"):
await compute_api.put("/projects/{project_id}/qemu/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), {"adapters": 2})
response = await compute_api.post("/projects/{project_id}/qemu/nodes/{node_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
assert response.json["type"] == "nio_udp"
await client.put(app.url_path_for("update_qemu_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json={"adapters": 2})
url = app.url_path_for("create_qemu_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
@pytest.mark.asyncio
async def test_qemu_nio_update_udp(compute_api, vm):
async def test_qemu_nio_update_udp(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -228,17 +267,31 @@ async def test_qemu_nio_update_udp(compute_api, vm):
"rhost": "127.0.0.1"
}
await compute_api.put("/projects/{project_id}/qemu/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), {"adapters": 2})
await compute_api.post("/projects/{project_id}/qemu/nodes/{node_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
await client.put(app.url_path_for("update_qemu_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json={"adapters": 2})
url = app.url_path_for("create_qemu_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
await client.post(url, json=params)
params["filters"] = {}
response = await compute_api.put("/projects/{project_id}/qemu/nodes/{node_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201, response.body.decode()
assert response.json["type"] == "nio_udp"
url = app.url_path_for("update_qemu_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
response = await client.put(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
@pytest.mark.asyncio
async def test_qemu_delete_nio(compute_api, vm):
async def test_qemu_delete_nio(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -248,57 +301,69 @@ async def test_qemu_delete_nio(compute_api, vm):
}
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM._ubridge_send"):
await compute_api.put("/projects/{project_id}/qemu/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), {"adapters": 2})
await compute_api.post("/projects/{project_id}/qemu/nodes/{node_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
response = await compute_api.delete("/projects/{project_id}/qemu/nodes/{node_id}/adapters/1/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
await client.put(app.url_path_for("update_qemu_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json={"adapters": 2})
url = app.url_path_for("create_qemu_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
await client.post(url, json=params)
@pytest.mark.asyncio
async def test_qemu_list_binaries(compute_api, vm):
url = app.url_path_for("delete_qemu_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="1",
port_number="0")
response = await client.delete(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_qemu_list_binaries(app: FastAPI, client: AsyncClient, vm: dict) -> None:
ret = [{"path": "/tmp/1", "version": "2.2.0"},
{"path": "/tmp/2", "version": "2.1.0"}]
with asyncio_patch("gns3server.compute.qemu.Qemu.binary_list", return_value=ret) as mock:
response = await compute_api.get("/qemu/binaries".format(project_id=vm["project_id"]))
response = await client.get(app.url_path_for("get_qemu_binaries"))
assert mock.called_with(None)
assert response.status_code == 200
assert response.json == ret
@pytest.mark.asyncio
async def test_qemu_list_binaries_filter(compute_api, vm):
assert response.status_code == status.HTTP_200_OK
assert response.json() == ret
ret = [
{"path": "/tmp/x86_64", "version": "2.2.0"},
{"path": "/tmp/alpha", "version": "2.1.0"},
{"path": "/tmp/i386", "version": "2.1.0"}
]
with asyncio_patch("gns3server.compute.qemu.Qemu.binary_list", return_value=ret) as mock:
response = await compute_api.get("/qemu/binaries".format(project_id=vm["project_id"]), body={"archs": ["i386"]})
assert response.status_code == 200
assert mock.called_with(["i386"])
assert response.json == ret
# async def test_qemu_list_binaries_filter(app: FastAPI, client: AsyncClient, vm: dict) -> None:
#
# ret = [
# {"path": "/tmp/x86_64", "version": "2.2.0"},
# {"path": "/tmp/alpha", "version": "2.1.0"},
# {"path": "/tmp/i386", "version": "2.1.0"}
# ]
#
# with asyncio_patch("gns3server.compute.qemu.Qemu.binary_list", return_value=ret) as mock:
# response = await client.get(app.url_path_for("get_qemu_binaries"),
# json={"archs": ["i386"]})
# assert response.status_code == status.HTTP_200_OK
# assert mock.called_with(["i386"])
# assert response.json() == ret
@pytest.mark.asyncio
async def test_images(compute_api, fake_qemu_vm):
async def test_images(app: FastAPI, client: AsyncClient, fake_qemu_vm) -> None:
response = await compute_api.get("/qemu/images")
assert response.status_code == 200
assert {"filename": "linux载.img", "path": "linux载.img", "md5sum": "c4ca4238a0b923820dcc509a6f75849b", "filesize": 1} in response.json
response = await client.get(app.url_path_for("get_qemu_images"))
assert response.status_code == status.HTTP_200_OK
assert {"filename": "linux载.img", "path": "linux载.img", "md5sum": "c4ca4238a0b923820dcc509a6f75849b", "filesize": 1} in response.json()
@pytest.mark.asyncio
async def test_upload_image(compute_api, tmpdir):
async def test_upload_image(app: FastAPI, client: AsyncClient, tmpdir: str) -> None:
with patch("gns3server.compute.Qemu.get_images_directory", return_value=str(tmpdir)):
response = await compute_api.post("/qemu/images/test2使", body="TEST", raw=True)
assert response.status_code == 204
print(os.listdir(tmpdir))
response = await client.post(app.url_path_for("upload_qemu_image",
filename="test2使"), content=b"TEST")
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(str(tmpdir / "test2使")) as f:
assert f.read() == "TEST"
@ -307,12 +372,13 @@ async def test_upload_image(compute_api, tmpdir):
assert checksum == "033bd94b1168d7e4f0d644c3c95e35bf"
@pytest.mark.asyncio
async def test_upload_image_ova(compute_api, tmpdir):
async def test_upload_image_ova(app: FastAPI, client: AsyncClient, tmpdir:str) -> None:
with patch("gns3server.compute.Qemu.get_images_directory", return_value=str(tmpdir)):
response = await compute_api.post("/qemu/images/test2.ova/test2.vmdk", body="TEST", raw=True)
assert response.status_code == 204
response = await client.post(app.url_path_for("upload_qemu_image",
filename="test2.ova/test2.vmdk"), content=b"TEST")
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(str(tmpdir / "test2.ova" / "test2.vmdk")) as f:
assert f.read() == "TEST"
@ -322,28 +388,27 @@ async def test_upload_image_ova(compute_api, tmpdir):
assert checksum == "033bd94b1168d7e4f0d644c3c95e35bf"
@pytest.mark.asyncio
async def test_upload_image_forbiden_location(compute_api, tmpdir):
async def test_upload_image_forbiden_location(app: FastAPI, client: AsyncClient, tmpdir: str) -> None:
with patch("gns3server.compute.Qemu.get_images_directory", return_value=str(tmpdir)):
response = await compute_api.post("/qemu/images/../../test2", body="TEST", raw=True)
assert response.status_code == 404
response = await client.post(app.url_path_for("upload_qemu_image",
filename="/qemu/images/../../test2"), content=b"TEST")
assert response.status_code == status.HTTP_403_FORBIDDEN
@pytest.mark.skipif(not sys.platform.startswith("win") and os.getuid() == 0, reason="Root can delete any image")
@pytest.mark.asyncio
async def test_upload_image_permission_denied(compute_api, images_dir):
async def test_upload_image_permission_denied(app: FastAPI, client: AsyncClient, images_dir: str) -> None:
with open(os.path.join(images_dir, "QEMU", "test2.tmp"), "w+") as f:
f.write("")
os.chmod(os.path.join(images_dir, "QEMU", "test2.tmp"), 0)
response = await compute_api.post("/qemu/images/test2", body="TEST", raw=True)
assert response.status_code == 409
response = await client.post(app.url_path_for("upload_qemu_image", filename="test2"), content=b"TEST")
assert response.status_code == status.HTTP_409_CONFLICT
@pytest.mark.asyncio
async def test_create_img_relative(compute_api):
async def test_create_img_relative(app: FastAPI, client: AsyncClient):
params = {
"qemu_img": "/tmp/qemu-img",
@ -356,12 +421,11 @@ async def test_create_img_relative(compute_api):
"size": 100
}
with asyncio_patch("gns3server.compute.Qemu.create_disk"):
response = await compute_api.post("/qemu/img", params)
assert response.status_code == 204
response = await client.post(app.url_path_for("create_qemu_image"), json=params)
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_create_img_absolute_non_local(compute_api, config):
async def test_create_img_absolute_non_local(app: FastAPI, client: AsyncClient, config: dict) -> None:
config.set("Server", "local", "false")
params = {
@ -375,12 +439,11 @@ async def test_create_img_absolute_non_local(compute_api, config):
"size": 100
}
with asyncio_patch("gns3server.compute.Qemu.create_disk"):
response = await compute_api.post("/qemu/img", params)
response = await client.post(app.url_path_for("create_qemu_image"), json=params)
assert response.status_code == 403
@pytest.mark.asyncio
async def test_create_img_absolute_local(compute_api, config):
async def test_create_img_absolute_local(app: FastAPI, client: AsyncClient, config: dict) -> None:
config.set("Server", "local", "true")
params = {
@ -394,60 +457,76 @@ async def test_create_img_absolute_local(compute_api, config):
"size": 100
}
with asyncio_patch("gns3server.compute.Qemu.create_disk"):
response = await compute_api.post("/qemu/img", params)
assert response.status_code == 204
response = await client.post(app.url_path_for("create_qemu_image"), json=params)
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_capabilities(compute_api):
async def test_capabilities(app: FastAPI, client: AsyncClient) -> None:
with asyncio_patch("gns3server.compute.Qemu.get_kvm_archs", return_value=["x86_64"]):
response = await compute_api.get("/qemu/capabilities")
assert response.json["kvm"] == ["x86_64"]
response = await client.get(app.url_path_for("get_qemu_capabilities"))
assert response.json()["kvm"] == ["x86_64"]
@pytest.mark.asyncio
async def test_qemu_duplicate(compute_api, compute_project, vm, base_params):
async def test_qemu_duplicate(app: FastAPI,
client: AsyncClient,
compute_project: Project,
vm: dict,
base_params: dict) -> None:
# create destination node first
response = await compute_api.post("/projects/{project_id}/qemu/nodes".format(project_id=compute_project.id), base_params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_qemu_node",
project_id=vm["project_id"]), json=base_params)
params = {"destination_node_id": response.json["node_id"]}
response = await compute_api.post("/projects/{project_id}/qemu/nodes/{node_id}/duplicate".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
assert response.status_code == status.HTTP_201_CREATED
params = {"destination_node_id": response.json()["node_id"]}
response = await client.post(app.url_path_for("duplicate_qemu_node",
project_id=vm["project_id"], node_id=vm["node_id"]), json=params)
assert response.status_code == status.HTTP_201_CREATED
@pytest.mark.asyncio
async def test_qemu_start_capture(compute_api, vm):
async def test_qemu_start_capture(app: FastAPI, client: AsyncClient, vm):
params = {
"capture_file_name": "test.pcap",
"data_link_type": "DLT_EN10MB"
}
url = app.url_path_for("start_qemu_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with patch("gns3server.compute.qemu.qemu_vm.QemuVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.start_capture") as mock:
response = await compute_api.post("/projects/{project_id}/qemu/nodes/{node_id}/adapters/0/ports/0/capture/start".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 200
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json["pcap_file_path"]
assert "test.pcap" in response.json()["pcap_file_path"]
@pytest.mark.asyncio
async def test_qemu_stop_capture(compute_api, vm):
async def test_qemu_stop_capture(app: FastAPI, client: AsyncClient, vm):
url = app.url_path_for("stop_qemu_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with patch("gns3server.compute.qemu.qemu_vm.QemuVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.stop_capture") as mock:
response = await compute_api.post("/projects/{project_id}/qemu/nodes/{node_id}/adapters/0/ports/0/capture/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
response = await client.post(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
# @pytest.mark.asyncio
# async def test_qemu_pcap(compute_api, vm, compute_project):
# async def test_qemu_pcap(app: FastAPI, client: AsyncClient, vm, compute_project):
#
# with asyncio_patch("gns3server.compute.qemu.qemu_vm.QemuVM.get_nio"):
# with asyncio_patch("gns3server.compute.qemu.Qemu.stream_pcap_file"):
# response = await compute_api.get("/projects/{project_id}/qemu/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=compute_project.id, node_id=vm["node_id"]), raw=True)
# assert response.status_code == 200
# response = await client.get("/projects/{project_id}/qemu/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=compute_project.id, node_id=vm["node_id"]), raw=True)
# assert response.status_code == status.HTTP_200_OK

@ -16,13 +16,19 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from fastapi import FastAPI, status
from httpx import AsyncClient
from tests.utils import asyncio_patch
from unittest.mock import patch
from gns3server.compute.project import Project
pytestmark = pytest.mark.asyncio
@pytest.fixture(scope="function")
@pytest.mark.asyncio
async def vm(compute_api, compute_project):
async def vm(app: FastAPI, client: AsyncClient, compute_project: Project) -> None:
vboxmanage_path = "/fake/VboxManage"
params = {
@ -32,16 +38,16 @@ async def vm(compute_api, compute_project):
}
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.create", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/virtualbox/nodes".format(project_id=compute_project.id), params)
response = await client.post(app.url_path_for("create_virtualbox_node", project_id=compute_project.id),
json=params)
assert mock.called
assert response.status_code == 201
assert response.status_code == status.HTTP_201_CREATED
with patch("gns3server.compute.virtualbox.VirtualBox.find_vboxmanage", return_value=vboxmanage_path):
return response.json
return response.json()
@pytest.mark.asyncio
async def test_vbox_create(compute_api, compute_project):
async def test_vbox_create(app: FastAPI, client: AsyncClient, compute_project: Project) -> None:
params = {
"name": "VM1",
@ -50,68 +56,76 @@ async def test_vbox_create(compute_api, compute_project):
}
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.create", return_value=True):
response = await compute_api.post("/projects/{project_id}/virtualbox/nodes".format(project_id=compute_project.id), params)
assert response.status_code == 201
assert response.json["name"] == "VM1"
assert response.json["project_id"] == compute_project.id
response = await client.post(app.url_path_for("create_virtualbox_node", project_id=compute_project.id),
json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "VM1"
assert response.json()["project_id"] == compute_project.id
@pytest.mark.asyncio
async def test_vbox_get(compute_api, compute_project, vm):
async def test_vbox_get(app: FastAPI, client: AsyncClient, compute_project: Project, vm: dict) -> None:
response = await compute_api.get("/projects/{project_id}/virtualbox/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 200
assert response.json["name"] == "VMTEST"
assert response.json["project_id"] == compute_project.id
response = await client.get(app.url_path_for("get_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "VMTEST"
assert response.json()["project_id"] == compute_project.id
@pytest.mark.asyncio
async def test_vbox_start(compute_api, vm):
async def test_vbox_start(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.check_hw_virtualization", return_value=True):
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.start", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/virtualbox/nodes/{node_id}/start".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("start_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vbox_stop(compute_api, vm):
async def test_vbox_stop(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.stop", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/virtualbox/nodes/{node_id}/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("stop_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vbox_suspend(compute_api, vm):
async def test_vbox_suspend(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.suspend", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/virtualbox/nodes/{node_id}/suspend".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("suspend_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vbox_resume(compute_api, vm):
async def test_vbox_resume(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.resume", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/virtualbox/nodes/{node_id}/resume".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("resume_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vbox_reload(compute_api, vm):
async def test_vbox_reload(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.reload", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/virtualbox/nodes/{node_id}/reload".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("reload_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vbox_nio_create_udp(compute_api, vm):
async def test_vbox_nio_create_udp(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -120,18 +134,24 @@ async def test_vbox_nio_create_udp(compute_api, vm):
"rhost": "127.0.0.1"
}
url = app.url_path_for("create_virtualbox_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch('gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.adapter_add_nio_binding') as mock:
response = await compute_api.post("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
response = await client.post(url, json=params)
assert mock.called
args, kwgars = mock.call_args
assert args[0] == 0
assert response.status_code == 201
assert response.json["type"] == "nio_udp"
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
# @pytest.mark.asyncio
# async def test_vbox_nio_update_udp(compute_api, vm):
# async def test_vbox_nio_update_udp(app: FastAPI, client: AsyncClient, vm):
#
# params = {
# "type": "nio_udp",
@ -143,68 +163,86 @@ async def test_vbox_nio_create_udp(compute_api, vm):
#
# with asyncio_patch('gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.ethernet_adapters'):
# with asyncio_patch('gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.adapter_remove_nio_binding'):
# response = await compute_api.put("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
# response = await client.put("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
#
# assert response.status_code == 201
# assert response.json["type"] == "nio_udp"
# assert response.status_code == status.HTTP_201_CREATED
# assert response.json()["type"] == "nio_udp"
@pytest.mark.asyncio
async def test_vbox_delete_nio(compute_api, vm):
async def test_vbox_delete_nio(app: FastAPI, client: AsyncClient, vm: dict) -> None:
url = app.url_path_for("delete_virtualbox_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch('gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.adapter_remove_nio_binding') as mock:
response = await compute_api.delete("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.delete(url)
assert mock.called
args, kwgars = mock.call_args
assert args[0] == 0
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vbox_update(compute_api, vm, free_console_port):
async def test_vbox_update(app: FastAPI, client: AsyncClient, vm, free_console_port):
params = {
"name": "test",
"console": free_console_port
}
response = await compute_api.put("/projects/{project_id}/virtualbox/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 200
assert response.json["name"] == "test"
assert response.json["console"] == free_console_port
response = await client.put(app.url_path_for("update_virtualbox_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
assert response.json()["console"] == free_console_port
@pytest.mark.asyncio
async def test_virtualbox_start_capture(compute_api, vm):
async def test_virtualbox_start_capture(app: FastAPI, client: AsyncClient, vm):
params = {
"capture_file_name": "test.pcap",
"data_link_type": "DLT_EN10MB"
}
url = app.url_path_for("start_virtualbox_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.start_capture") as mock:
response = await compute_api.post("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/capture/start".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 200
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json["pcap_file_path"]
assert "test.pcap" in response.json()["pcap_file_path"]
@pytest.mark.asyncio
async def test_virtualbox_stop_capture(compute_api, vm):
async def test_virtualbox_stop_capture(app: FastAPI, client: AsyncClient, vm):
url = app.url_path_for("stop_virtualbox_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.stop_capture") as mock:
response = await compute_api.post("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/capture/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
response = await client.post(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
# @pytest.mark.asyncio
# async def test_virtualbox_pcap(compute_api, vm, compute_project):
# async def test_virtualbox_pcap(app: FastAPI, client: AsyncClient, vm, compute_project):
#
# with asyncio_patch("gns3server.compute.virtualbox.virtualbox_vm.VirtualBoxVM.get_nio"):
# with asyncio_patch("gns3server.compute.virtualbox.VirtualBox.stream_pcap_file"):
# response = await compute_api.get("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=compute_project.id, node_id=vm["node_id"]), raw=True)
# assert response.status_code == 200
# response = await client.get("/projects/{project_id}/virtualbox/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=compute_project.id, node_id=vm["node_id"]), raw=True)
# assert response.status_code == status.HTTP_200_OK

@ -16,13 +16,19 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from fastapi import FastAPI, status
from httpx import AsyncClient
from tests.utils import asyncio_patch
from unittest.mock import patch
from gns3server.compute.project import Project
pytestmark = pytest.mark.asyncio
@pytest.fixture(scope="function")
@pytest.mark.asyncio
async def vm(compute_api, compute_project, vmx_path):
async def vm(app: FastAPI, client: AsyncClient, compute_project: Project, vmx_path: str) -> dict:
params = {
"name": "VMTEST",
@ -31,15 +37,15 @@ async def vm(compute_api, compute_project, vmx_path):
}
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.create", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/vmware/nodes".format(project_id=compute_project.id), params)
assert mock.called
assert response.status_code == 201, response.body.decode()
return response.json
response = await client.post(app.url_path_for("create_vmware_node", project_id=compute_project.id),
json=params)
assert mock.called
assert response.status_code == status.HTTP_201_CREATED
return response.json()
@pytest.fixture
@pytest.mark.asyncio
def vmx_path(tmpdir):
def vmx_path(tmpdir: str) -> str:
"""
Return a fake VMX file
"""
@ -50,8 +56,7 @@ def vmx_path(tmpdir):
return path
@pytest.mark.asyncio
async def test_vmware_create(compute_api, compute_project, vmx_path):
async def test_vmware_create(app: FastAPI, client: AsyncClient, compute_project: Project, vmx_path: str) -> None:
params = {
"name": "VM1",
@ -60,70 +65,74 @@ async def test_vmware_create(compute_api, compute_project, vmx_path):
}
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.create", return_value=True):
response = await compute_api.post("/projects/{project_id}/vmware/nodes".format(project_id=compute_project.id), params)
assert response.status_code == 201, response.body.decode()
assert response.json["name"] == "VM1"
assert response.json["project_id"] == compute_project.id
response = await client.post(app.url_path_for("create_vmware_node", project_id=compute_project.id),
json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "VM1"
assert response.json()["project_id"] == compute_project.id
@pytest.mark.asyncio
async def test_vmware_get(compute_api, compute_project, vm):
async def test_vmware_get(app: FastAPI, client: AsyncClient, compute_project: Project, vm: dict) -> None:
response = await compute_api.get("/projects/{project_id}/vmware/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 200
assert response.json["name"] == "VMTEST"
assert response.json["project_id"] == compute_project.id
response = await client.get(app.url_path_for("get_vmware_node", project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "VMTEST"
assert response.json()["project_id"] == compute_project.id
@pytest.mark.asyncio
async def test_vmware_start(compute_api, vm):
async def test_vmware_start(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.check_hw_virtualization", return_value=True) as mock1:
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.start", return_value=True) as mock2:
response = await compute_api.post("/projects/{project_id}/vmware/nodes/{node_id}/start".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("start_vmware_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock1.called
assert mock2.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vmware_stop(compute_api, vm):
async def test_vmware_stop(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.stop", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/vmware/nodes/{node_id}/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("stop_vmware_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vmware_suspend(compute_api, vm):
async def test_vmware_suspend(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.suspend", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/vmware/nodes/{node_id}/suspend".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("suspend_vmware_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vmware_resume(compute_api, vm):
async def test_vmware_resume(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.resume", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/vmware/nodes/{node_id}/resume".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("resume_vmware_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vmware_reload(compute_api, vm):
async def test_vmware_reload(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.reload", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/vmware/nodes/{node_id}/reload".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("reload_vmware_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vmware_nio_create_udp(compute_api, vm):
async def test_vmware_nio_create_udp(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -132,18 +141,24 @@ async def test_vmware_nio_create_udp(compute_api, vm):
"rhost": "127.0.0.1"
}
url = app.url_path_for("create_vmware_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch('gns3server.compute.vmware.vmware_vm.VMwareVM.adapter_add_nio_binding') as mock:
response = await compute_api.post("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
response = await client.post(url, json=params)
assert mock.called
args, kwgars = mock.call_args
assert args[0] == 0
assert response.status_code == 201
assert response.json["type"] == "nio_udp"
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
# @pytest.mark.asyncio
# async def test_vmware_nio_update_udp(compute_api, vm):
# async def test_vmware_nio_update_udp(app: FastAPI, client: AsyncClient, vm):
#
# params = {
# "type": "nio_udp",
@ -156,68 +171,84 @@ async def test_vmware_nio_create_udp(compute_api, vm):
# with asyncio_patch('gns3server.compute.vmware.vmware_vm.VMwareVM._ubridge_send'):
# with asyncio_patch('gns3server.compute.vmware.vmware_vm.VMwareVM.ethernet_adapters'):
# with patch('gns3server.compute.vmware.vmware_vm.VMwareVM._get_vnet') as mock:
# response = await compute_api.put("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
# assert response.status_code == 201
# assert response.json["type"] == "nio_udp"
# response = await client.put("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
# assert response.status_code == status.HTTP_201_CREATED
# assert response.json()["type"] == "nio_udp"
async def test_vmware_delete_nio(app: FastAPI, client: AsyncClient, vm: dict) -> None:
@pytest.mark.asyncio
async def test_vmware_delete_nio(compute_api, vm):
url = app.url_path_for("delete_vmware_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch('gns3server.compute.vmware.vmware_vm.VMwareVM.adapter_remove_nio_binding') as mock:
response = await compute_api.delete("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.delete(url)
assert mock.called
args, kwgars = mock.call_args
assert args[0] == 0
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vmware_update(compute_api, vm, free_console_port):
async def test_vmware_update(app: FastAPI, client: AsyncClient, vm: dict, free_console_port: int) -> None:
params = {
"name": "test",
"console": free_console_port
}
response = await compute_api.put("/projects/{project_id}/vmware/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 200
assert response.json["name"] == "test"
assert response.json["console"] == free_console_port
response = await client.put(app.url_path_for("update_vmware_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
assert response.json()["console"] == free_console_port
@pytest.mark.asyncio
async def test_vmware_start_capture(compute_api, vm):
async def test_vmware_start_capture(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"capture_file_name": "test.pcap",
"data_link_type": "DLT_EN10MB"
}
url = app.url_path_for("start_vmware_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with patch("gns3server.compute.vmware.vmware_vm.VMwareVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.start_capture") as mock:
response = await compute_api.post("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/capture/start".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params)
assert response.status_code == 200
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json["pcap_file_path"]
assert "test.pcap" in response.json()["pcap_file_path"]
async def test_vmware_stop_capture(app: FastAPI, client: AsyncClient, vm: dict) -> None:
@pytest.mark.asyncio
async def test_vmware_stop_capture(compute_api, vm):
url = app.url_path_for("stop_vmware_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with patch("gns3server.compute.vmware.vmware_vm.VMwareVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.stop_capture") as mock:
response = await compute_api.post("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/capture/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
response = await client.post(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
# @pytest.mark.asyncio
# async def test_vmware_pcap(compute_api, vm, compute_project):
# async def test_vmware_pcap(app: FastAPI, client: AsyncClient, vm, compute_project):
#
# with asyncio_patch("gns3server.compute.vmware.vmware_vm.VMwareVM.get_nio"):
# with asyncio_patch("gns3server.compute.vmware.VMware.stream_pcap_file"):
# response = await compute_api.get("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=compute_project.id, node_id=vm["node_id"]), raw=True)
# assert response.status_code == 200
# response = await client.get("/projects/{project_id}/vmware/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=compute_project.id, node_id=vm["node_id"]), raw=True)
# assert response.status_code == status.HTTP_200_OK

@ -16,72 +16,75 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
import uuid
from fastapi import FastAPI, status
from httpx import AsyncClient
from tests.utils import asyncio_patch
from unittest.mock import patch
from gns3server.compute.project import Project
pytestmark = pytest.mark.asyncio
@pytest.fixture
@pytest.mark.asyncio
async def vm(compute_api, compute_project):
async def vm(app: FastAPI, client: AsyncClient, compute_project: Project) -> None:
params = {"name": "PC TEST 1"}
response = await compute_api.post("/projects/{project_id}/vpcs/nodes".format(project_id=compute_project.id), params)
assert response.status_code == 201
return response.json
response = await client.post(app.url_path_for("create_vpcs_node", project_id=compute_project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED
return response.json()
@pytest.mark.asyncio
async def test_vpcs_create(compute_api, compute_project):
async def test_vpcs_create(app: FastAPI, client: AsyncClient, compute_project: Project) -> None:
params = {"name": "PC TEST 1"}
response = await compute_api.post("/projects/{project_id}/vpcs/nodes".format(project_id=compute_project.id), params)
assert response.status_code == 201
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == compute_project.id
response = await client.post(app.url_path_for("create_vpcs_node", project_id=compute_project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
@pytest.mark.asyncio
async def test_vpcs_get(compute_api, compute_project, vm):
async def test_vpcs_get(app: FastAPI, client: AsyncClient, compute_project: Project, vm: dict) -> None:
response = await compute_api.get("/projects/{project_id}/vpcs/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 200
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == compute_project.id
assert response.json["status"] == "stopped"
response = await client.get(app.url_path_for("get_vpcs_node", project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["status"] == "stopped"
@pytest.mark.asyncio
async def test_vpcs_create_startup_script(compute_api, compute_project):
async def test_vpcs_create_startup_script(app: FastAPI, client: AsyncClient, compute_project: Project) -> None:
params = {
"name": "PC TEST 1",
"startup_script": "ip 192.168.1.2\necho TEST"
}
response = await compute_api.post("/projects/{project_id}/vpcs/nodes".format(project_id=compute_project.id), params)
assert response.status_code == 201
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == compute_project.id
response = await client.post(app.url_path_for("create_vpcs_node", project_id=compute_project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
@pytest.mark.asyncio
async def test_vpcs_create_port(compute_api, compute_project, free_console_port):
async def test_vpcs_create_port(app: FastAPI,
client: AsyncClient,
compute_project: Project,
free_console_port: int) -> None:
params = {
"name": "PC TEST 1",
"console": free_console_port
}
response = await compute_api.post("/projects/{project_id}/vpcs/nodes".format(project_id=compute_project.id), params)
assert response.status_code == 201
assert response.json["name"] == "PC TEST 1"
assert response.json["project_id"] == compute_project.id
assert response.json["console"] == free_console_port
response = await client.post(app.url_path_for("create_vpcs_node", project_id=compute_project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1"
assert response.json()["project_id"] == compute_project.id
assert response.json()["console"] == free_console_port
@pytest.mark.asyncio
async def test_vpcs_nio_create_udp(compute_api, vm):
async def test_vpcs_nio_create_udp(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -90,14 +93,19 @@ async def test_vpcs_nio_create_udp(compute_api, vm):
"rhost": "127.0.0.1"
}
url = app.url_path_for("create_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.add_ubridge_udp_connection"):
response = await compute_api.post("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
assert response.json["type"] == "nio_udp"
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
@pytest.mark.asyncio
async def test_vpcs_nio_update_udp(compute_api, vm):
async def test_vpcs_nio_update_udp(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -106,18 +114,28 @@ async def test_vpcs_nio_update_udp(compute_api, vm):
"rhost": "127.0.0.1"
}
url = app.url_path_for("create_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.add_ubridge_udp_connection"):
response = await compute_api.post("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
params["filters"] = {}
response = await compute_api.put("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201, response.body.decode("utf-8")
assert response.json["type"] == "nio_udp"
url = app.url_path_for("update_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
response = await client.put(url, json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["type"] == "nio_udp"
@pytest.mark.asyncio
async def test_vpcs_delete_nio(compute_api, vm):
async def test_vpcs_delete_nio(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"type": "nio_udp",
@ -127,63 +145,78 @@ async def test_vpcs_delete_nio(compute_api, vm):
}
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM._ubridge_send"):
await compute_api.post("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
response = await compute_api.delete("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/nio".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204, response.body.decode()
url = app.url_path_for("create_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
await client.post(url, json=params)
url = app.url_path_for("delete_vpcs_node_nio",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
response = await client.delete(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vpcs_start(compute_api, vm):
async def test_vpcs_start(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.start", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/vpcs/nodes/{node_id}/start".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("start_vpcs_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vpcs_stop(compute_api, vm):
async def test_vpcs_stop(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.stop", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/vpcs/nodes/{node_id}/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("stop_vpcs_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vpcs_reload(compute_api, vm):
async def test_vpcs_reload(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.reload", return_value=True) as mock:
response = await compute_api.post("/projects/{project_id}/vpcs/nodes/{node_id}/reload".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.post(app.url_path_for("reload_vpcs_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vpcs_delete(compute_api, vm):
async def test_vpcs_delete(app: FastAPI, client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.vpcs.VPCS.delete_node", return_value=True) as mock:
response = await compute_api.delete("/projects/{project_id}/vpcs/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]))
response = await client.delete(app.url_path_for("delete_vpcs_node",
project_id=vm["project_id"],
node_id=vm["node_id"]))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_vpcs_duplicate(compute_api, compute_project, vm):
async def test_vpcs_duplicate(app: FastAPI, client: AsyncClient, compute_project: Project, vm: dict) -> None:
# create destination node first
params = {"name": "PC TEST 1"}
response = await compute_api.post("/projects/{project_id}/vpcs/nodes".format(project_id=compute_project.id), params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_vpcs_node", project_id=compute_project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED
params = {"destination_node_id": response.json["node_id"]}
response = await compute_api.post("/projects/{project_id}/vpcs/nodes/{node_id}/duplicate".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 201
params = {"destination_node_id": response.json()["node_id"]}
response = await client.post(app.url_path_for("duplicate_vpcs_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
assert response.status_code == status.HTTP_201_CREATED
@pytest.mark.asyncio
async def test_vpcs_update(compute_api, vm, free_console_port):
async def test_vpcs_update(app: FastAPI, client: AsyncClient, vm: dict, free_console_port: int) -> None:
console_port = free_console_port
params = {
@ -191,42 +224,54 @@ async def test_vpcs_update(compute_api, vm, free_console_port):
"console": console_port
}
response = await compute_api.put("/projects/{project_id}/vpcs/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), params)
assert response.status_code == 200
assert response.json["name"] == "test"
assert response.json["console"] == console_port
response = await client.put(app.url_path_for("update_vpcs_node",
project_id=vm["project_id"],
node_id=vm["node_id"]), json=params)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
assert response.json()["console"] == console_port
@pytest.mark.asyncio
async def test_vpcs_start_capture(compute_api, vm):
async def test_vpcs_start_capture(app: FastAPI, client: AsyncClient, vm: dict) -> None:
params = {
"capture_file_name": "test.pcap",
"data_link_type": "DLT_EN10MB"
}
url = app.url_path_for("start_vpcs_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.start_capture") as mock:
response = await compute_api.post("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/capture/start".format(project_id=vm["project_id"], node_id=vm["node_id"]), body=params)
assert response.status_code == 200
response = await client.post(url, json=params)
assert response.status_code == status.HTTP_200_OK
assert mock.called
assert "test.pcap" in response.json["pcap_file_path"]
assert "test.pcap" in response.json()["pcap_file_path"]
async def test_vpcs_stop_capture(app: FastAPI, client: AsyncClient, vm: dict) -> None:
@pytest.mark.asyncio
async def test_vpcs_stop_capture(compute_api, vm):
url = app.url_path_for("stop_vpcs_node_capture",
project_id=vm["project_id"],
node_id=vm["node_id"],
adapter_number="0",
port_number="0")
with patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.is_running", return_value=True):
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.stop_capture") as mock:
response = await compute_api.post("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/capture/stop".format(project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == 204
response = await client.post(url)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
# @pytest.mark.asyncio
# async def test_vpcs_pcap(compute_api, vm, compute_project):
# async def test_vpcs_pcap(app: FastAPI, client: AsyncClient, vm, compute_project: Project):
#
# with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.get_nio"):
# with asyncio_patch("gns3server.compute.vpcs.VPCS.stream_pcap_file"):
# response = await compute_api.get("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=compute_project.id, node_id=vm["node_id"]), raw=True)
# assert response.status_code == 200
# response = await client.get("/projects/{project_id}/vpcs/nodes/{node_id}/adapters/0/ports/0/pcap".format(project_id=compute_project.id, node_id=vm["node_id"]), raw=True)
# assert response.status_code == status.HTTP_200_OK

@ -17,10 +17,14 @@
import pytest
from fastapi import FastAPI, status
from httpx import AsyncClient
@pytest.mark.asyncio
async def test_appliances_list(controller_api):
pytestmark = pytest.mark.asyncio
response = await controller_api.get("/appliances/")
assert response.status_code == 200
assert len(response.json) > 0
async def test_appliances_list(app: FastAPI, client: AsyncClient) -> None:
response = await client.get(app.url_path_for("get_appliances"))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) > 0

@ -16,11 +16,19 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from fastapi import FastAPI, status
from httpx import AsyncClient
from gns3server.controller import Controller
pytestmark = pytest.mark.asyncio
import unittest
from tests.utils import asyncio_patch
@pytest.mark.asyncio
async def test_compute_create_without_id(controller_api, controller):
async def test_compute_create_without_id(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {
"protocol": "http",
@ -29,17 +37,17 @@ async def test_compute_create_without_id(controller_api, controller):
"user": "julien",
"password": "secure"}
response = await controller_api.post("/computes", params)
assert response.status_code == 201
assert response.json["user"] == "julien"
assert response.json["compute_id"] is not None
assert "password" not in response.json
response = await client.post(app.url_path_for("create_compute"), json=params)
assert response.status_code == status.HTTP_201_CREATED
response_content = response.json()
assert response_content["user"] == "julien"
assert response_content["compute_id"] is not None
assert "password" not in response_content
assert len(controller.computes) == 1
assert controller.computes[response.json["compute_id"]].host == "localhost"
assert controller.computes[response_content["compute_id"]].host == "localhost"
@pytest.mark.asyncio
async def test_compute_create_with_id(controller_api, controller):
async def test_compute_create_with_id(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {
"compute_id": "my_compute_id",
@ -49,16 +57,15 @@ async def test_compute_create_with_id(controller_api, controller):
"user": "julien",
"password": "secure"}
response = await controller_api.post("/computes", params)
assert response.status_code == 201
assert response.json["user"] == "julien"
assert "password" not in response.json
response = await client.post(app.url_path_for("create_compute"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["user"] == "julien"
assert "password" not in response.json()
assert len(controller.computes) == 1
assert controller.computes["my_compute_id"].host == "localhost"
@pytest.mark.asyncio
async def test_compute_get(controller_api):
async def test_compute_get(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {
"compute_id": "my_compute_id",
@ -69,15 +76,14 @@ async def test_compute_get(controller_api):
"password": "secure"
}
response = await controller_api.post("/computes", params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_compute"), json=params)
assert response.status_code == status.HTTP_201_CREATED
response = await controller_api.get("/computes/my_compute_id")
assert response.status_code == 200
response = await client.get(app.url_path_for("update_compute", compute_id="my_compute_id"))
assert response.status_code == status.HTTP_200_OK
@pytest.mark.asyncio
async def test_compute_update(controller_api):
async def test_compute_update(app: FastAPI, client: AsyncClient) -> None:
params = {
"compute_id": "my_compute_id",
@ -88,22 +94,20 @@ async def test_compute_update(controller_api):
"password": "secure"
}
response = await controller_api.post("/computes", params)
assert response.status_code == 201
response = await controller_api.get("/computes/my_compute_id")
assert response.status_code == 200
assert response.json["protocol"] == "http"
response = await client.post(app.url_path_for("create_compute"), json=params)
assert response.status_code == status.HTTP_201_CREATED
response = await client.get(app.url_path_for("get_compute", compute_id="my_compute_id"))
assert response.status_code == status.HTTP_200_OK
assert response.json()["protocol"] == "http"
params["protocol"] = "https"
response = await controller_api.put("/computes/my_compute_id", params)
response = await client.put(app.url_path_for("update_compute", compute_id="my_compute_id"), json=params)
assert response.status_code == 200
assert response.json["protocol"] == "https"
assert response.status_code == status.HTTP_200_OK
assert response.json()["protocol"] == "https"
@pytest.mark.asyncio
async def test_compute_list(controller_api):
async def test_compute_list(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {
"compute_id": "my_compute_id",
@ -115,13 +119,13 @@ async def test_compute_list(controller_api):
"name": "My super server"
}
response = await controller_api.post("/computes", params)
assert response.status_code == 201
assert response.json["user"] == "julien"
assert "password" not in response.json
response = await client.post(app.url_path_for("create_compute"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["user"] == "julien"
assert "password" not in response.json()
response = await controller_api.get("/computes")
for compute in response.json:
response = await client.get(app.url_path_for("get_computes"))
for compute in response.json():
if compute['compute_id'] != 'local':
assert compute == {
'compute_id': 'my_compute_id',
@ -146,8 +150,7 @@ async def test_compute_list(controller_api):
}
@pytest.mark.asyncio
async def test_compute_delete(controller_api):
async def test_compute_delete(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {
"compute_id": "my_compute_id",
@ -157,41 +160,40 @@ async def test_compute_delete(controller_api):
"user": "julien",
"password": "secure"
}
response = await controller_api.post("/computes", params)
assert response.status_code == 201
response = await controller_api.get("/computes")
assert len(response.json) == 1
response = await client.post(app.url_path_for("create_compute"), json=params)
assert response.status_code == status.HTTP_201_CREATED
response = await client.get(app.url_path_for("get_computes"))
assert len(response.json()) == 1
response = await controller_api.delete("/computes/my_compute_id")
assert response.status_code == 204
response = await client.delete(app.url_path_for("delete_compute", compute_id="my_compute_id"))
assert response.status_code == status.HTTP_204_NO_CONTENT
response = await controller_api.get("/computes")
assert len(response.json) == 0
response = await client.get(app.url_path_for("get_computes"))
assert len(response.json()) == 0
@pytest.mark.asyncio
async def test_compute_list_images(controller_api):
async def test_compute_list_images(app: FastAPI, client: AsyncClient) -> None:
params = {
"compute_id": "my_compute",
"compute_id": "my_compute_id",
"protocol": "http",
"host": "localhost",
"port": 84,
"user": "julien",
"password": "secure"
}
response = await controller_api.post("/computes", params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_compute"), json=params)
assert response.status_code == status.HTTP_201_CREATED
with asyncio_patch("gns3server.controller.compute.Compute.images", return_value=[{"filename": "linux.qcow2"}, {"filename": "asav.qcow2"}]) as mock:
response = await controller_api.get("/computes/my_compute/qemu/images")
assert response.json == [{"filename": "linux.qcow2"}, {"filename": "asav.qcow2"}]
response = await client.get(app.url_path_for("delete_compute", compute_id="my_compute_id") + "/qemu/images")
assert response.json() == [{"filename": "linux.qcow2"}, {"filename": "asav.qcow2"}]
mock.assert_called_with("qemu")
@pytest.mark.asyncio
async def test_compute_list_vms(controller_api):
async def test_compute_list_vms(app: FastAPI, client: AsyncClient) -> None:
params = {
"compute_id": "my_compute",
@ -201,17 +203,16 @@ async def test_compute_list_vms(controller_api):
"user": "julien",
"password": "secure"
}
response = await controller_api.post("/computes", params)
assert response.status_code == 201
response = await client.post(app.url_path_for("get_computes"), json=params)
assert response.status_code == status.HTTP_201_CREATED
with asyncio_patch("gns3server.controller.compute.Compute.forward", return_value=[]) as mock:
response = await controller_api.get("/computes/my_compute/virtualbox/vms")
response = await client.get(app.url_path_for("get_compute", compute_id="my_compute_id") + "/virtualbox/vms")
mock.assert_called_with("GET", "virtualbox", "vms")
assert response.json == []
assert response.json() == []
@pytest.mark.asyncio
async def test_compute_create_img(controller_api):
async def test_compute_create_img(app: FastAPI, client: AsyncClient) -> None:
params = {
"compute_id": "my_compute",
@ -222,18 +223,17 @@ async def test_compute_create_img(controller_api):
"password": "secure"
}
response = await controller_api.post("/computes", params)
assert response.status_code == 201
response = await client.post(app.url_path_for("get_computes"), json=params)
assert response.status_code == status.HTTP_201_CREATED
params = {"path": "/test"}
with asyncio_patch("gns3server.controller.compute.Compute.forward", return_value=[]) as mock:
response = await controller_api.post("/computes/my_compute/qemu/img", params)
assert response.json == []
response = await client.post(app.url_path_for("get_compute", compute_id="my_compute_id") + "/qemu/img", json=params)
assert response.json() == []
mock.assert_called_with("POST", "qemu", "img", data=unittest.mock.ANY)
@pytest.mark.asyncio
async def test_compute_autoidlepc(controller_api):
async def test_compute_autoidlepc(app: FastAPI, client: AsyncClient) -> None:
params = {
"compute_id": "my_compute_id",
@ -244,7 +244,7 @@ async def test_compute_autoidlepc(controller_api):
"password": "secure"
}
await controller_api.post("/computes", params)
await client.post(app.url_path_for("get_computes"), json=params)
params = {
"platform": "c7200",
@ -253,9 +253,9 @@ async def test_compute_autoidlepc(controller_api):
}
with asyncio_patch("gns3server.controller.Controller.autoidlepc", return_value={"idlepc": "0x606de20c"}) as mock:
response = await controller_api.post("/computes/my_compute_id/auto_idlepc", params)
response = await client.post(app.url_path_for("get_compute", compute_id="my_compute_id") + "/auto_idlepc", json=params)
assert mock.called
assert response.status_code == 200
assert response.status_code == status.HTTP_200_OK
# FIXME

@ -18,25 +18,29 @@
import os
import pytest
from fastapi import FastAPI, status
from httpx import AsyncClient
from unittest.mock import MagicMock
from gns3server.config import Config
@pytest.mark.asyncio
async def test_shutdown_local(controller_api, config):
pytestmark = pytest.mark.asyncio
async def test_shutdown_local(app: FastAPI, client: AsyncClient, config: Config) -> None:
os.kill = MagicMock()
config.set("Server", "local", True)
response = await controller_api.post('/shutdown')
assert response.status_code == 204
response = await client.post(app.url_path_for("shutdown"))
assert response.status_code == status.HTTP_204_NO_CONTENT
assert os.kill.called
@pytest.mark.asyncio
async def test_shutdown_non_local(controller_api, config):
async def test_shutdown_non_local(app: FastAPI, client: AsyncClient, config: Config) -> None:
config.set("Server", "local", False)
response = await controller_api.post('/shutdown')
assert response.status_code == 403
response = await client.post(app.url_path_for("shutdown"))
assert response.status_code == status.HTTP_403_FORBIDDEN
# @pytest.mark.asyncio
@ -60,8 +64,7 @@ async def test_shutdown_non_local(controller_api, config):
# assert response.status_code == 403
@pytest.mark.asyncio
async def test_statistics_output(controller_api):
async def test_statistics_output(app: FastAPI, client: AsyncClient) -> None:
response = await controller_api.get('/statistics')
assert response.status_code == 200
response = await client.get(app.url_path_for("statistics"))
assert response.status_code == status.HTTP_200_OK

@ -16,11 +16,17 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from fastapi import FastAPI, status
from httpx import AsyncClient
from gns3server.controller.drawing import Drawing
from gns3server.controller.project import Project
pytestmark = pytest.mark.asyncio
@pytest.mark.asyncio
async def test_create_drawing(controller_api, project):
async def test_create_drawing(app: FastAPI, client: AsyncClient, project: Project) -> None:
params = {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
@ -29,13 +35,12 @@ async def test_create_drawing(controller_api, project):
"z": 0
}
response = await controller_api.post("/projects/{}/drawings".format(project.id), params)
assert response.status_code == 201
assert response.json["drawing_id"] is not None
response = await client.post(app.url_path_for("create_drawing", project_id=project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["drawing_id"] is not None
@pytest.mark.asyncio
async def test_get_drawing(controller_api, project):
async def test_get_drawing(app: FastAPI, client: AsyncClient, project: Project) -> None:
params = {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
@ -44,14 +49,17 @@ async def test_get_drawing(controller_api, project):
"z": 0
}
response = await controller_api.post("/projects/{}/drawings".format(project.id), params)
response = await controller_api.get("/projects/{}/drawings/{}".format(project.id, response.json["drawing_id"]))
response = await client.post(app.url_path_for("create_drawing", project_id=project.id), json=params)
response = await client.get(app.url_path_for(
"get_drawing",
project_id=project.id,
drawing_id=response.json()["drawing_id"])
)
assert response.status_code == 200
assert response.json["x"] == 10
assert response.json()["x"] == 10
@pytest.mark.asyncio
async def test_update_drawing(controller_api, project):
async def test_update_drawing(app: FastAPI, client: AsyncClient, project: Project) -> None:
params = {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
@ -60,14 +68,19 @@ async def test_update_drawing(controller_api, project):
"z": 0
}
response = await controller_api.post("/projects/{}/drawings".format(project.id), params)
response = await controller_api.put("/projects/{}/drawings/{}".format(project.id, response.json["drawing_id"]), {"x": 42})
assert response.status_code == 200
assert response.json["x"] == 42
response = await client.post(app.url_path_for("create_drawing", project_id=project.id), json=params)
response = await client.put(app.url_path_for(
"update_drawing",
project_id=project.id,
drawing_id=response.json()["drawing_id"]),
json={"x": 42}
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["x"] == 42
@pytest.mark.asyncio
async def test_list_drawing(controller_api, project):
async def test_all_drawings(app: FastAPI, client: AsyncClient, project: Project) -> None:
params = {
"svg": '<svg height="210" width="500"><line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" /></svg>',
@ -76,17 +89,20 @@ async def test_list_drawing(controller_api, project):
"z": 0
}
await controller_api.post("/projects/{}/drawings".format(project.id), params)
response = await controller_api.get("/projects/{}/drawings".format(project.id))
assert response.status_code == 200
assert len(response.json) == 1
await client.post(app.url_path_for("create_drawing", project_id=project.id), json=params)
response = await client.get(app.url_path_for("get_drawings", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 1
@pytest.mark.asyncio
async def test_delete_drawing(controller_api, project):
async def test_delete_drawing(app: FastAPI, client: AsyncClient, project: Project) -> None:
drawing = Drawing(project)
project._drawings = {drawing.id: drawing}
response = await controller_api.delete("/projects/{}/drawings/{}".format(project.id, drawing.id))
assert response.status_code == 204
response = await client.delete(app.url_path_for(
"delete_drawing",
project_id=project.id,
drawing_id=drawing.id)
)
assert response.status_code == status.HTTP_204_NO_CONTENT
assert drawing.id not in project.drawings

@ -16,39 +16,40 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from fastapi import FastAPI, status
from httpx import AsyncClient
from tests.utils import asyncio_patch
pytestmark = pytest.mark.asyncio
@pytest.mark.asyncio
async def test_list_vms(controller_api):
async def test_list_vms(app: FastAPI, client: AsyncClient) -> None:
with asyncio_patch("gns3server.controller.gns3vm.vmware_gns3_vm.VMwareGNS3VM.list", return_value=[{"vmname": "test"}]):
response = await controller_api.get('/gns3vm/engines/vmware/vms')
assert response.status_code == 200
assert response.json == [
response = await client.get(app.url_path_for("get_vms", engine="vmware"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == [
{
"vmname": "test"
}
]
@pytest.mark.asyncio
async def test_engines(controller_api):
async def test_engines(app: FastAPI, client: AsyncClient) -> None:
response = await controller_api.get('/gns3vm/engines')
assert response.status_code == 200
assert len(response.json) > 0
response = await client.get(app.url_path_for("get_engines"))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) > 0
@pytest.mark.asyncio
async def test_put_gns3vm(controller_api):
async def test_put_gns3vm(app: FastAPI, client: AsyncClient) -> None:
response = await controller_api.put('/gns3vm', {"vmname": "TEST VM"})
assert response.status_code == 200
assert response.json["vmname"] == "TEST VM"
response = await client.put(app.url_path_for("update_gns3vm_settings"), json={"vmname": "TEST VM"})
assert response.status_code == status.HTTP_200_OK
assert response.json()["vmname"] == "TEST VM"
@pytest.mark.asyncio
async def test_get_gns3vm(controller_api):
response = await controller_api.get('/gns3vm')
assert response.status_code == 200
async def test_get_gns3vm(app: FastAPI, client: AsyncClient) -> None:
response = await client.get(app.url_path_for("get_gns3vm_settings"))
assert response.status_code == status.HTTP_200_OK

@ -17,17 +17,25 @@
import pytest
from typing import Tuple
from fastapi import FastAPI, status
from httpx import AsyncClient
from unittest.mock import patch, MagicMock
from tests.utils import asyncio_patch, AsyncioMagicMock
from gns3server.controller.project import Project
from gns3server.controller.compute import Compute
from gns3server.controller.node import Node
from gns3server.controller.ports.ethernet_port import EthernetPort
from gns3server.controller.link import Link, FILTERS
from gns3server.controller.udp_link import UDPLink
pytestmark = pytest.mark.asyncio
@pytest.fixture
@pytest.mark.asyncio
async def nodes(compute, project):
async def nodes(compute: Compute, project: Project) -> Tuple[Node, Node]:
response = MagicMock()
response.json = {"console": 2048}
@ -40,8 +48,7 @@ async def nodes(compute, project):
return node1, node2
@pytest.mark.asyncio
async def test_create_link(controller_api, project, nodes):
async def test_create_link(app: FastAPI, client: AsyncClient, project: Project, nodes: Tuple[Node, Node]) -> None:
node1, node2 = nodes
@ -51,7 +58,7 @@ async def test_create_link(controller_api, project, nodes):
}
with asyncio_patch("gns3server.controller.udp_link.UDPLink.create") as mock:
response = await controller_api.post("/projects/{}/links".format(project.id), {
response = await client.post(app.url_path_for("create_link", project_id=project.id), json={
"nodes": [
{
"node_id": node1.id,
@ -73,16 +80,15 @@ async def test_create_link(controller_api, project, nodes):
})
assert mock.called
assert response.status_code == 201
assert response.json["link_id"] is not None
assert len(response.json["nodes"]) == 2
assert response.json["nodes"][0]["label"]["x"] == 42
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["link_id"] is not None
assert len(response.json()["nodes"]) == 2
assert response.json()["nodes"][0]["label"]["x"] == 42
assert len(project.links) == 1
assert list(project.links.values())[0].filters == filters
@pytest.mark.asyncio
async def test_create_link_failure(controller_api, compute, project):
async def test_create_link_failure(app: FastAPI, client: AsyncClient, compute: Compute, project: Project) -> None:
"""
Make sure the link is deleted if we failed to create it.
@ -96,7 +102,7 @@ async def test_create_link_failure(controller_api, compute, project):
node1 = await project.add_node(compute, "node1", None, node_type="qemu")
node1._ports = [EthernetPort("E0", 0, 0, 3), EthernetPort("E0", 0, 0, 4)]
response = await controller_api.post("/projects/{}/links".format(project.id), {
response = await client.post(app.url_path_for("create_link", project_id=project.id), json={
"nodes": [
{
"node_id": node1.id,
@ -116,16 +122,15 @@ async def test_create_link_failure(controller_api, compute, project):
]
})
assert response.status_code == 409
assert response.status_code == status.HTTP_409_CONFLICT
assert len(project.links) == 0
@pytest.mark.asyncio
async def test_get_link(controller_api, project, nodes):
async def test_get_link(app: FastAPI, client: AsyncClient, project: Project, nodes: Tuple[Node, Node]) -> None:
node1, node2 = nodes
with asyncio_patch("gns3server.controller.udp_link.UDPLink.create") as mock:
response = await controller_api.post("/projects/{}/links".format(project.id), {
response = await client.post(app.url_path_for("create_link", project_id=project.id), json={
"nodes": [
{
"node_id": node1.id,
@ -146,19 +151,18 @@ async def test_get_link(controller_api, project, nodes):
})
assert mock.called
link_id = response.json["link_id"]
assert response.json["nodes"][0]["label"]["x"] == 42
response = await controller_api.get("/projects/{}/links/{}".format(project.id, link_id))
assert response.status_code == 200
assert response.json["nodes"][0]["label"]["x"] == 42
link_id = response.json()["link_id"]
assert response.json()["nodes"][0]["label"]["x"] == 42
response = await client.get(app.url_path_for("get_link", project_id=project.id, link_id=link_id))
assert response.status_code == status.HTTP_200_OK
assert response.json()["nodes"][0]["label"]["x"] == 42
@pytest.mark.asyncio
async def test_update_link_suspend(controller_api, project, nodes):
async def test_update_link_suspend(app: FastAPI, client: AsyncClient, project: Project, nodes: Tuple[Node, Node]) -> None:
node1, node2 = nodes
with asyncio_patch("gns3server.controller.udp_link.UDPLink.create") as mock:
response = await controller_api.post("/projects/{}/links".format(project.id), {
response = await client.post(app.url_path_for("create_link", project_id=project.id), json={
"nodes": [
{
"node_id": node1.id,
@ -179,10 +183,10 @@ async def test_update_link_suspend(controller_api, project, nodes):
})
assert mock.called
link_id = response.json["link_id"]
assert response.json["nodes"][0]["label"]["x"] == 42
link_id = response.json()["link_id"]
assert response.json()["nodes"][0]["label"]["x"] == 42
response = await controller_api.put("/projects/{}/links/{}".format(project.id, link_id), {
response = await client.put(app.url_path_for("update_link", project_id=project.id, link_id=link_id), json={
"nodes": [
{
"node_id": node1.id,
@ -203,14 +207,13 @@ async def test_update_link_suspend(controller_api, project, nodes):
"suspend": True
})
assert response.status_code == 200
assert response.json["nodes"][0]["label"]["x"] == 64
assert response.json["suspend"]
assert response.json["filters"] == {}
assert response.status_code == status.HTTP_200_OK
assert response.json()["nodes"][0]["label"]["x"] == 64
assert response.json()["suspend"]
assert response.json()["filters"] == {}
@pytest.mark.asyncio
async def test_update_link(controller_api, project, nodes):
async def test_update_link(app: FastAPI, client: AsyncClient, project: Project, nodes: Tuple[Node, Node]) -> None:
filters = {
"latency": [10],
@ -219,7 +222,7 @@ async def test_update_link(controller_api, project, nodes):
node1, node2 = nodes
with asyncio_patch("gns3server.controller.udp_link.UDPLink.create") as mock:
response = await controller_api.post("/projects/{}/links".format(project.id), {
response = await client.post(app.url_path_for("create_link", project_id=project.id), json={
"nodes": [
{
"node_id": node1.id,
@ -240,10 +243,10 @@ async def test_update_link(controller_api, project, nodes):
})
assert mock.called
link_id = response.json["link_id"]
assert response.json["nodes"][0]["label"]["x"] == 42
link_id = response.json()["link_id"]
assert response.json()["nodes"][0]["label"]["x"] == 42
response = await controller_api.put("/projects/{}/links/{}".format(project.id, link_id), {
response = await client.put(app.url_path_for("update_link", project_id=project.id, link_id=link_id), json={
"nodes": [
{
"node_id": node1.id,
@ -264,13 +267,12 @@ async def test_update_link(controller_api, project, nodes):
"filters": filters
})
assert response.status_code == 200
assert response.json["nodes"][0]["label"]["x"] == 64
assert response.status_code == status.HTTP_200_OK
assert response.json()["nodes"][0]["label"]["x"] == 64
assert list(project.links.values())[0].filters == filters
@pytest.mark.asyncio
async def test_list_link(controller_api, project, nodes):
async def test_list_link(app: FastAPI, client: AsyncClient, project: Project, nodes: Tuple[Node, Node]) -> None:
filters = {
"latency": [10],
@ -291,51 +293,48 @@ async def test_list_link(controller_api, project, nodes):
}
]
with asyncio_patch("gns3server.controller.udp_link.UDPLink.create") as mock:
await controller_api.post("/projects/{}/links".format(project.id), {
await client.post(app.url_path_for("create_link", project_id=project.id), json={
"nodes": nodes,
"filters": filters
})
assert mock.called
response = await controller_api.get("/projects/{}/links".format(project.id))
assert response.status_code == 200
assert len(response.json) == 1
assert response.json[0]["filters"] == filters
response = await client.get(app.url_path_for("get_links", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 1
assert response.json()[0]["filters"] == filters
@pytest.mark.asyncio
async def test_reset_link(controller_api, project):
async def test_reset_link(app: FastAPI, client: AsyncClient, project: Project) -> None:
link = UDPLink(project)
project._links = {link.id: link}
with asyncio_patch("gns3server.controller.udp_link.UDPLink.delete") as delete_mock:
with asyncio_patch("gns3server.controller.udp_link.UDPLink.create") as create_mock:
response = await controller_api.post("/projects/{}/links/{}/reset".format(project.id, link.id))
response = await client.post(app.url_path_for("reset_link", project_id=project.id, link_id=link.id))
assert delete_mock.called
assert create_mock.called
assert response.status_code == 200
assert response.status_code == status.HTTP_200_OK
@pytest.mark.asyncio
async def test_start_capture(controller_api, project):
async def test_start_capture(app: FastAPI, client: AsyncClient, project: Project) -> None:
link = Link(project)
project._links = {link.id: link}
with asyncio_patch("gns3server.controller.link.Link.start_capture") as mock:
response = await controller_api.post("/projects/{}/links/{}/capture/start".format(project.id, link.id))
assert mock.called
assert response.status_code == 201
response = await client.post(app.url_path_for("start_capture", project_id=project.id, link_id=link.id), json={})
assert mock.called
assert response.status_code == status.HTTP_201_CREATED
@pytest.mark.asyncio
async def test_stop_capture(controller_api, project):
async def test_stop_capture(app: FastAPI, client: AsyncClient, project: Project) -> None:
link = Link(project)
project._links = {link.id: link}
with asyncio_patch("gns3server.controller.link.Link.stop_capture") as mock:
response = await controller_api.post("/projects/{}/links/{}/capture/stop".format(project.id, link.id))
assert mock.called
assert response.status_code == 204
response = await client.post(app.url_path_for("stop_capture", project_id=project.id, link_id=link.id))
assert mock.called
assert response.status_code == status.HTTP_204_NO_CONTENT
# async def test_pcap(controller_api, http_client, project):
@ -359,24 +358,22 @@ async def test_stop_capture(controller_api, project):
# assert b'hello' == response.body
@pytest.mark.asyncio
async def test_delete_link(controller_api, project):
async def test_delete_link(app: FastAPI, client: AsyncClient, project: Project) -> None:
link = Link(project)
project._links = {link.id: link}
with asyncio_patch("gns3server.controller.link.Link.delete") as mock:
response = await controller_api.delete("/projects/{}/links/{}".format(project.id, link.id))
response = await client.delete(app.url_path_for("delete_link", project_id=project.id, link_id=link.id))
assert mock.called
assert response.status_code == 204
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_list_filters(controller_api, project):
async def test_list_filters(app: FastAPI, client: AsyncClient, project: Project) -> None:
link = Link(project)
project._links = {link.id: link}
with patch("gns3server.controller.link.Link.available_filters", return_value=FILTERS) as mock:
response = await controller_api.get("/projects/{}/links/{}/available_filters".format(project.id, link.id))
response = await client.get(app.url_path_for("get_filters", project_id=project.id, link_id=link.id))
assert mock.called
assert response.status_code == 200
assert response.json == FILTERS
assert response.status_code == status.HTTP_200_OK
assert response.json() == FILTERS

@ -18,28 +18,34 @@
import pytest
from fastapi import FastAPI, status
from httpx import AsyncClient
from unittest.mock import MagicMock
from tests.utils import AsyncioMagicMock
from gns3server.controller.node import Node
from gns3server.controller.project import Project
from gns3server.controller.compute import Compute
pytestmark = pytest.mark.asyncio
@pytest.fixture
def node(project, compute):
def node(project: Project, compute: Compute) -> Node:
node = Node(project, compute, "test", node_type="vpcs")
project._nodes[node.id] = node
return node
@pytest.mark.asyncio
async def test_create_node(controller_api, project, compute):
async def test_create_node(app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
response = MagicMock()
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
response = await controller_api.post("/projects/{}/nodes".format(project.id), {
response = await client.post(app.url_path_for("create_node", project_id=project.id), json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
@ -48,19 +54,18 @@ async def test_create_node(controller_api, project, compute):
}
})
assert response.status_code == 201
assert response.json["name"] == "test"
assert "name" not in response.json["properties"]
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "test"
assert "name" not in response.json()["properties"]
@pytest.mark.asyncio
async def test_list_node(controller_api, project, compute):
async def test_list_node(app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
response = MagicMock()
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
await controller_api.post("/projects/{}/nodes".format(project.id), {
await client.post(app.url_path_for("create_node", project_id=project.id), json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
@ -69,19 +74,18 @@ async def test_list_node(controller_api, project, compute):
}
})
response = await controller_api.get("/projects/{}/nodes".format(project.id))
assert response.status_code == 200
assert response.json[0]["name"] == "test"
response = await client.get(app.url_path_for("get_nodes", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert response.json()[0]["name"] == "test"
@pytest.mark.asyncio
async def test_get_node(controller_api, project, compute):
async def test_get_node(app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
response = MagicMock()
response.json = {"console": 2048}
compute.post = AsyncioMagicMock(return_value=response)
response = await controller_api.post("/projects/{}/nodes".format(project.id), {
response = await client.post(app.url_path_for("create_node", project_id=project.id), json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
@ -90,19 +94,18 @@ async def test_get_node(controller_api, project, compute):
}
})
response = await controller_api.get("/projects/{}/nodes/{}".format(project.id, response.json["node_id"]))
assert response.status_code == 200
assert response.json["name"] == "test"
response = await client.get(app.url_path_for("get_node", project_id=project.id, node_id=response.json()["node_id"]))
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
@pytest.mark.asyncio
async def test_update_node(controller_api, project, compute, node):
async def test_update_node(app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node) -> None:
response = MagicMock()
response.json = {"console": 2048}
compute.put = AsyncioMagicMock(return_value=response)
response = await controller_api.put("/projects/{}/nodes/{}".format(project.id, node.id), {
response = await client.put(app.url_path_for("update_node", project_id=project.id, node_id=node.id), json={
"name": "test",
"node_type": "vpcs",
"compute_id": "example.com",
@ -112,156 +115,167 @@ async def test_update_node(controller_api, project, compute, node):
})
assert response.status_code == 200
assert response.json["name"] == "test"
assert "name" not in response.json["properties"]
assert response.json()["name"] == "test"
assert "name" not in response.json()["properties"]
@pytest.mark.asyncio
async def test_start_all_nodes(controller_api, project, compute):
async def test_start_all_nodes(app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
compute.post = AsyncioMagicMock()
response = await controller_api.post("/projects/{}/nodes/start".format(project.id))
assert response.status_code == 204
response = await client.post(app.url_path_for("start_all_nodes", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_stop_all_nodes(controller_api, project, compute):
async def test_stop_all_nodes(app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
compute.post = AsyncioMagicMock()
response = await controller_api.post("/projects/{}/nodes/stop".format(project.id))
assert response.status_code == 204
response = await client.post(app.url_path_for("stop_all_nodes", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_suspend_all_nodes(controller_api, project, compute):
async def test_suspend_all_nodes(app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
compute.post = AsyncioMagicMock()
response = await controller_api.post("/projects/{}/nodes/suspend".format(project.id))
assert response.status_code == 204
response = await client.post(app.url_path_for("suspend_all_nodes", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_reload_all_nodes(controller_api, project, compute):
async def test_reload_all_nodes(app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
compute.post = AsyncioMagicMock()
response = await controller_api.post("/projects/{}/nodes/reload".format(project.id))
assert response.status_code == 204
response = await client.post(app.url_path_for("reload_all_nodes", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_reset_console_all_nodes(controller_api, project, compute):
async def test_reset_console_all_nodes(app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None:
compute.post = AsyncioMagicMock()
response = await controller_api.post("/projects/{}/nodes/console/reset".format(project.id))
assert response.status_code == 204
response = await client.post(app.url_path_for("reset_console_all_nodes", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_start_node(controller_api, project, node, compute):
async def test_start_node(app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node) -> None:
compute.post = AsyncioMagicMock()
response = await controller_api.post("/projects/{}/nodes/{}/start".format(project.id, node.id))
assert response.status_code == 204
response = await client.post(app.url_path_for("start_node", project_id=project.id, node_id=node.id), json={})
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_stop_node(controller_api, project, node, compute):
async def test_stop_node(app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node) -> None:
compute.post = AsyncioMagicMock()
response = await controller_api.post("/projects/{}/nodes/{}/stop".format(project.id, node.id))
assert response.status_code == 204
response = await client.post(app.url_path_for("stop_node", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_suspend_node(controller_api, project, node, compute):
async def test_suspend_node(app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node) -> None:
compute.post = AsyncioMagicMock()
response = await controller_api.post("/projects/{}/nodes/{}/suspend".format(project.id, node.id))
assert response.status_code == 204
response = await client.post(app.url_path_for("suspend_node", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_reload_node(controller_api, project, node, compute):
async def test_reload_node(app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node):
compute.post = AsyncioMagicMock()
response = await controller_api.post("/projects/{}/nodes/{}/reload".format(project.id, node.id))
assert response.status_code == 204
response = await client.post(app.url_path_for("reload_node", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_duplicate_node(controller_api, project, compute, node):
async def test_duplicate_node(
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node) -> None:
response = MagicMock()
response.json({"console": 2035})
compute.post = AsyncioMagicMock(return_value=response)
response = await controller_api.post("/projects/{}/nodes/{}/duplicate".format(project.id, node.id),
{"x": 10,
"y": 5,
"z": 0})
assert response.status_code == 201, response.body.decode()
response = await client.post(app.url_path_for("duplicate_node", project_id=project.id, node_id=node.id),
json={"x": 10, "y": 5, "z": 0})
assert response.status_code == status.HTTP_201_CREATED
@pytest.mark.asyncio
async def test_delete_node(controller_api, project, node, compute):
async def test_delete_node(app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node) -> None:
compute.post = AsyncioMagicMock()
response = await controller_api.delete("/projects/{}/nodes/{}".format(project.id, node.id))
assert response.status_code == 204
response = await client.delete(app.url_path_for("delete_node", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.asyncio
async def test_dynamips_idle_pc(controller_api, project, compute, node):
async def test_dynamips_idle_pc(
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node) -> None:
response = MagicMock()
response.json = {"idlepc": "0x60606f54"}
compute.get = AsyncioMagicMock(return_value=response)
response = await controller_api.get("/projects/{}/nodes/{}/dynamips/auto_idlepc".format(project.id, node.id))
assert response.status_code == 200
assert response.json["idlepc"] == "0x60606f54"
response = await client.get(app.url_path_for("auto_idlepc", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_200_OK
assert response.json()["idlepc"] == "0x60606f54"
@pytest.mark.asyncio
async def test_dynamips_idlepc_proposals(controller_api, project, compute, node):
async def test_dynamips_idlepc_proposals(
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node) -> None:
response = MagicMock()
response.json = ["0x60606f54", "0x33805a22"]
compute.get = AsyncioMagicMock(return_value=response)
response = await controller_api.get("/projects/{}/nodes/{}/dynamips/idlepc_proposals".format(project.id, node.id))
assert response.status_code == 200
assert response.json == ["0x60606f54", "0x33805a22"]
response = await client.get(app.url_path_for("idlepc_proposals", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_200_OK
assert response.json() == ["0x60606f54", "0x33805a22"]
@pytest.mark.asyncio
async def test_get_file(controller_api, project, node, compute):
async def test_get_file(app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node) -> None:
response = MagicMock()
response.body = b"world"
compute.http_query = AsyncioMagicMock(return_value=response)
response = await controller_api.get("/projects/{project_id}/nodes/{node_id}/files/hello".format(project_id=project.id, node_id=node.id))
assert response.status_code == 200
response = await client.get(app.url_path_for("get_file", project_id=project.id, node_id=node.id, file_path="hello"))
assert response.status_code == status.HTTP_200_OK
assert response.content == b'world'
compute.http_query.assert_called_with("GET", "/projects/{project_id}/files/project-files/vpcs/{node_id}/hello".format(project_id=project.id, node_id=node.id), timeout=None, raw=True)
compute.http_query.assert_called_with(
"GET",
"/projects/{project_id}/files/project-files/vpcs/{node_id}/hello".format(
project_id=project.id,
node_id=node.id),
timeout=None,
raw=True)
response = await controller_api.get("/projects/{project_id}/nodes/{node_id}/files/../hello".format(project_id=project.id, node_id=node.id))
assert response.status_code == 404
response = await client.get(app.url_path_for(
"get_file",
project_id=project.id,
node_id=node.id,
file_path="../hello"))
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_post_file(controller_api, project, node, compute):
async def test_post_file(app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node) -> None:
compute.http_query = AsyncioMagicMock()
response = await controller_api.post("/projects/{project_id}/nodes/{node_id}/files/hello".format(project_id=project.id, node_id=node.id), body=b"hello", raw=True)
assert response.status_code == 201
response = await client.post(app.url_path_for(
"post_file",
project_id=project.id,
node_id=node.id,
file_path="hello"), content=b"hello")
assert response.status_code == status.HTTP_201_CREATED
compute.http_query.assert_called_with("POST", "/projects/{project_id}/files/project-files/vpcs/{node_id}/hello".format(project_id=project.id, node_id=node.id), data=b'hello', timeout=None, raw=True)
response = await controller_api.get("/projects/{project_id}/nodes/{node_id}/files/../hello".format(project_id=project.id, node_id=node.id))
assert response.status_code == 404
response = await client.get("/projects/{project_id}/nodes/{node_id}/files/../hello".format(project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_404_NOT_FOUND
# @pytest.mark.asyncio

@ -17,177 +17,173 @@
import uuid
import os
import pytest
import zipfile
import json
import pytest
from fastapi import FastAPI, status
from httpx import AsyncClient
from unittest.mock import patch, MagicMock
from tests.utils import asyncio_patch
from gns3server.controller import Controller
from gns3server.controller.project import Project
pytestmark = pytest.mark.asyncio
@pytest.fixture
@pytest.mark.asyncio
async def project(controller_api, controller):
async def project(app: FastAPI, client: AsyncClient, controller: Controller) -> Project:
u = str(uuid.uuid4())
params = {"name": "test", "project_id": u}
await controller_api.post("/projects", params)
await client.post(app.url_path_for("create_project"), json=params)
return controller.get_project(u)
@pytest.mark.asyncio
async def test_create_project_with_path(controller_api, tmpdir):
async def test_create_project_with_path(app: FastAPI, client: AsyncClient, controller: Controller, tmpdir) -> None:
response = await controller_api.post("/projects", {"name": "test", "path": str(tmpdir), "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"})
assert response.status_code == 201
assert response.json["name"] == "test"
assert response.json["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e0f"
assert response.json["status"] == "opened"
params = {"name": "test", "path": str(tmpdir), "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"}
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "test"
assert response.json()["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e0f"
assert response.json()["status"] == "opened"
@pytest.mark.asyncio
async def test_create_project_without_dir(controller_api):
async def test_create_project_without_dir(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {"name": "test", "project_id": "10010203-0405-0607-0809-0a0b0c0d0e0f"}
response = await controller_api.post("/projects", params)
assert response.status_code == 201
assert response.json["project_id"] == "10010203-0405-0607-0809-0a0b0c0d0e0f"
assert response.json["name"] == "test"
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["project_id"] == "10010203-0405-0607-0809-0a0b0c0d0e0f"
assert response.json()["name"] == "test"
@pytest.mark.asyncio
async def test_create_project_with_uuid(controller_api):
async def test_create_project_with_uuid(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {"name": "test", "project_id": "30010203-0405-0607-0809-0a0b0c0d0e0f"}
response = await controller_api.post("/projects", params)
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == 201
assert response.json["project_id"] == "30010203-0405-0607-0809-0a0b0c0d0e0f"
assert response.json["name"] == "test"
assert response.json()["project_id"] == "30010203-0405-0607-0809-0a0b0c0d0e0f"
assert response.json()["name"] == "test"
@pytest.mark.asyncio
async def test_create_project_with_variables(controller_api):
async def test_create_project_with_variables(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
variables = [
{"name": "TEST1"},
{"name": "TEST2", "value": "value1"}
]
params = {"name": "test", "project_id": "30010203-0405-0607-0809-0a0b0c0d0e0f", "variables": variables}
response = await controller_api.post("/projects", params)
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == 201
assert response.json["variables"] == [
assert response.json()["variables"] == [
{"name": "TEST1"},
{"name": "TEST2", "value": "value1"}
]
@pytest.mark.asyncio
async def test_create_project_with_supplier(controller_api):
async def test_create_project_with_supplier(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
supplier = {
'logo': 'logo.png',
'url': 'http://example.com'
}
params = {"name": "test", "project_id": "30010203-0405-0607-0809-0a0b0c0d0e0f", "supplier": supplier}
response = await controller_api.post("/projects", params)
assert response.status_code == 201
assert response.json["supplier"] == supplier
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["supplier"] == supplier
@pytest.mark.asyncio
async def test_update_project(controller_api):
async def test_update_project(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {"name": "test", "project_id": "10010203-0405-0607-0809-0a0b0c0d0e0f"}
response = await controller_api.post("/projects", params)
assert response.status_code == 201
assert response.json["project_id"] == "10010203-0405-0607-0809-0a0b0c0d0e0f"
assert response.json["name"] == "test"
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["project_id"] == "10010203-0405-0607-0809-0a0b0c0d0e0f"
assert response.json()["name"] == "test"
params = {"name": "test2"}
response = await controller_api.put("/projects/10010203-0405-0607-0809-0a0b0c0d0e0f", params)
assert response.status_code == 200
assert response.json["name"] == "test2"
response = await client.put(app.url_path_for("update_project", project_id="10010203-0405-0607-0809-0a0b0c0d0e0f"),
json=params)
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test2"
@pytest.mark.asyncio
async def test_update_project_with_variables(controller_api):
async def test_update_project_with_variables(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
variables = [
{"name": "TEST1"},
{"name": "TEST2", "value": "value1"}
]
params = {"name": "test", "project_id": "10010203-0405-0607-0809-0a0b0c0d0e0f", "variables": variables}
response = await controller_api.post("/projects", params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_project"), json=params)
assert response.status_code == status.HTTP_201_CREATED
params = {"name": "test2"}
response = await controller_api.put("/projects/10010203-0405-0607-0809-0a0b0c0d0e0f", params)
assert response.status_code == 200
assert response.json["variables"] == variables
response = await client.put(app.url_path_for("update_project", project_id="10010203-0405-0607-0809-0a0b0c0d0e0f"),
json=params)
assert response.status_code == status.HTTP_200_OK
assert response.json()["variables"] == variables
@pytest.mark.asyncio
async def test_list_projects(controller_api, tmpdir):
async def test_list_projects(app: FastAPI, client: AsyncClient, controller: Controller, tmpdir) -> None:
await controller_api.post("/projects", {"name": "test", "path": str(tmpdir), "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"})
response = await controller_api.get("/projects")
assert response.status_code == 200
projects = response.json
params = {"name": "test", "path": str(tmpdir), "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"}
await client.post(app.url_path_for("create_project"), json=params)
response = await client.get(app.url_path_for("get_projects"))
assert response.status_code == status.HTTP_200_OK
projects = response.json()
assert projects[0]["name"] == "test"
@pytest.mark.asyncio
async def test_get_project(controller_api, project):
async def test_get_project(app: FastAPI, client: AsyncClient, project: Project) -> None:
response = await controller_api.get("/projects/{project_id}".format(project_id=project.id))
assert response.status_code == 200
assert response.json["name"] == "test"
response = await client.get(app.url_path_for("get_project", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "test"
@pytest.mark.asyncio
async def test_delete_project(controller_api, project, controller):
async def test_delete_project(app: FastAPI, client: AsyncClient, project: Project, controller: Controller) -> None:
with asyncio_patch("gns3server.controller.project.Project.delete", return_value=True) as mock:
response = await controller_api.delete("/projects/{project_id}".format(project_id=project.id))
assert response.status_code == 204
response = await client.delete(app.url_path_for("delete_project", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
assert project not in controller.projects
@pytest.mark.asyncio
async def test_delete_project_invalid_uuid(controller_api):
async def test_delete_project_invalid_uuid(app: FastAPI, client: AsyncClient) -> None:
response = await controller_api.delete("/projects/{project_id}".format(project_id=uuid.uuid4()))
assert response.status_code == 404
response = await client.delete(app.url_path_for("delete_project", project_id=str(uuid.uuid4())))
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_close_project(controller_api, project):
async def test_close_project(app: FastAPI, client: AsyncClient, project: Project) -> None:
with asyncio_patch("gns3server.controller.project.Project.close", return_value=True) as mock:
response = await controller_api.post("/projects/{project_id}/close".format(project_id=project.id))
assert response.status_code == 204
response = await client.post(app.url_path_for("close_project", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
assert mock.called
@pytest.mark.asyncio
async def test_open_project(controller_api, project):
async def test_open_project(app: FastAPI, client: AsyncClient, project: Project) -> None:
with asyncio_patch("gns3server.controller.project.Project.open", return_value=True) as mock:
response = await controller_api.post("/projects/{project_id}/open".format(project_id=project.id))
assert response.status_code == 201
response = await client.post(app.url_path_for("open_project", project_id=project.id))
assert response.status_code == status.HTTP_201_CREATED
assert mock.called
@pytest.mark.asyncio
async def test_load_project(controller_api, project, config):
async def test_load_project(app: FastAPI, client: AsyncClient, project: Project, config) -> None:
config.set("Server", "local", "true")
with asyncio_patch("gns3server.controller.Controller.load_project", return_value=project) as mock:
response = await controller_api.post("/projects/load", {"path": "/tmp/test.gns3"})
assert response.status_code == 201
response = await client.post(app.url_path_for("load_project"), json={"path": "/tmp/test.gns3"})
assert response.status_code == status.HTTP_201_CREATED
mock.assert_called_with("/tmp/test.gns3")
assert response.json["project_id"] == project.id
assert response.json()["project_id"] == project.id
# @pytest.mark.asyncio
@ -230,8 +226,7 @@ async def test_load_project(controller_api, project, config):
# assert project.status_code == "opened"
@pytest.mark.asyncio
async def test_export_with_images(controller_api, tmpdir, project):
async def test_export_with_images(app: FastAPI, client: AsyncClient, tmpdir, project: Project) -> None:
project.dump = MagicMock()
os.makedirs(project.path, exist_ok=True)
@ -258,8 +253,9 @@ async def test_export_with_images(controller_api, tmpdir, project):
json.dump(topology, f)
with patch("gns3server.compute.Dynamips.get_images_directory", return_value=str(tmpdir / "IOS")):
response = await controller_api.get("/projects/{project_id}/export?include_images=yes".format(project_id=project.id))
assert response.status_code == 200
response = await client.get(app.url_path_for("export_project", project_id=project.id),
params={"include_images": "yes"})
assert response.status_code == status.HTTP_200_OK
assert response.headers['CONTENT-TYPE'] == 'application/gns3project'
assert response.headers['CONTENT-DISPOSITION'] == 'attachment; filename="{}.gns3project"'.format(project.name)
@ -273,8 +269,7 @@ async def test_export_with_images(controller_api, tmpdir, project):
myzip.getinfo("images/IOS/test.image")
@pytest.mark.asyncio
async def test_export_without_images(controller_api, tmpdir, project):
async def test_export_without_images(app: FastAPI, client: AsyncClient, tmpdir, project: Project) -> None:
project.dump = MagicMock()
os.makedirs(project.path, exist_ok=True)
@ -301,8 +296,9 @@ async def test_export_without_images(controller_api, tmpdir, project):
json.dump(topology, f)
with patch("gns3server.compute.Dynamips.get_images_directory", return_value=str(tmpdir / "IOS"),):
response = await controller_api.get("/projects/{project_id}/export?include_images=0".format(project_id=project.id))
assert response.status_code == 200
response = await client.get(app.url_path_for("export_project", project_id=project.id),
params={"include_images": "0"})
assert response.status_code == status.HTTP_200_OK
assert response.headers['CONTENT-TYPE'] == 'application/gns3project'
assert response.headers['CONTENT-DISPOSITION'] == 'attachment; filename="{}.gns3project"'.format(project.name)
@ -318,50 +314,51 @@ async def test_export_without_images(controller_api, tmpdir, project):
myzip.getinfo("images/IOS/test.image")
@pytest.mark.asyncio
async def test_get_file(controller_api, project):
async def test_get_file(app: FastAPI, client: AsyncClient, project: Project) -> None:
os.makedirs(project.path, exist_ok=True)
with open(os.path.join(project.path, 'hello'), 'w+') as f:
f.write('world')
response = await controller_api.get("/projects/{project_id}/files/hello".format(project_id=project.id))
assert response.status_code == 200
response = await client.get(app.url_path_for("get_file", project_id=project.id, file_path="hello"))
assert response.status_code == status.HTTP_200_OK
assert response.content == b"world"
response = await controller_api.get("/projects/{project_id}/files/false".format(project_id=project.id))
assert response.status_code == 404
response = await client.get(app.url_path_for("get_file", project_id=project.id, file_path="false"))
assert response.status_code == status.HTTP_404_NOT_FOUND
response = await controller_api.get("/projects/{project_id}/files/../hello".format(project_id=project.id))
assert response.status_code == 404
response = await client.get(app.url_path_for("get_file", project_id=project.id, file_path="../hello"))
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_write_file(controller_api, project):
async def test_write_file(app: FastAPI, client: AsyncClient, project: Project) -> None:
response = await controller_api.post("/projects/{project_id}/files/hello".format(project_id=project.id), body="world", raw=True)
assert response.status_code == 204
response = await client.post(app.url_path_for("write_file", project_id=project.id, file_path="hello"),
content=b"world")
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(os.path.join(project.path, "hello")) as f:
assert f.read() == "world"
response = await controller_api.post("/projects/{project_id}/files/../hello".format(project_id=project.id), raw=True)
assert response.status_code == 404
response = await client.post(app.url_path_for("write_file", project_id=project.id, file_path="../hello"))
assert response.status_code == status.HTTP_404_NOT_FOUND
@pytest.mark.asyncio
async def test_write_and_get_file_with_leading_slashes_in_filename(controller_api, project):
async def test_write_and_get_file_with_leading_slashes_in_filename(
app: FastAPI,
client: AsyncClient,
project: Project) -> None:
response = await controller_api.post("/projects/{project_id}/files//hello".format(project_id=project.id), body="world", raw=True)
assert response.status_code == 204
response = await client.post(app.url_path_for("write_file", project_id=project.id, file_path="//hello"),
content=b"world")
assert response.status_code == status.HTTP_204_NO_CONTENT
response = await controller_api.get("/projects/{project_id}/files//hello".format(project_id=project.id), raw=True)
assert response.status_code == 200
response = await client.get(app.url_path_for("get_file", project_id=project.id, file_path="//hello"))
assert response.status_code == status.HTTP_200_OK
assert response.content == b"world"
@pytest.mark.asyncio
async def test_import(controller_api, tmpdir, controller):
async def test_import(app: FastAPI, client: AsyncClient, tmpdir, controller: Controller) -> None:
with zipfile.ZipFile(str(tmpdir / "test.zip"), 'w') as myzip:
myzip.writestr("project.gns3", b'{"project_id": "c6992992-ac72-47dc-833b-54aa334bcd05", "version": "2.0.0", "name": "test"}')
@ -369,8 +366,8 @@ async def test_import(controller_api, tmpdir, controller):
project_id = str(uuid.uuid4())
with open(str(tmpdir / "test.zip"), "rb") as f:
response = await controller_api.post("/projects/{project_id}/import".format(project_id=project_id), body=f.read(), raw=True)
assert response.status_code == 201
response = await client.post(app.url_path_for("import_project", project_id=project_id), content=f.read())
assert response.status_code == status.HTTP_201_CREATED
project = controller.get_project(project_id)
with open(os.path.join(project.path, "demo")) as f:
@ -378,9 +375,8 @@ async def test_import(controller_api, tmpdir, controller):
assert content == "hello"
@pytest.mark.asyncio
async def test_duplicate(controller_api, project):
async def test_duplicate(app: FastAPI, client: AsyncClient, project: Project) -> None:
response = await controller_api.post("/projects/{project_id}/duplicate".format(project_id=project.id), {"name": "hello"})
assert response.status_code == 201
assert response.json["name"] == "hello"
response = await client.post(app.url_path_for("duplicate_project", project_id=project.id), json={"name": "hello"})
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "hello"

@ -19,54 +19,57 @@ import os
import uuid
import pytest
from fastapi import FastAPI, status
from httpx import AsyncClient
from gns3server.controller import Controller
from gns3server.controller.project import Project
from gns3server.controller.snapshot import Snapshot
pytestmark = pytest.mark.asyncio
@pytest.fixture
@pytest.mark.asyncio
async def project(controller_api, controller):
async def project(app: FastAPI, client: AsyncClient, controller: Controller) -> Project:
u = str(uuid.uuid4())
params = {"name": "test", "project_id": u}
await controller_api.post("/projects", params)
await client.post(app.url_path_for("create_project"), json=params)
project = controller.get_project(u)
return project
@pytest.fixture
@pytest.mark.asyncio
async def snapshot(project):
async def snapshot(project: Project):
snapshot = await project.snapshot("test")
return snapshot
@pytest.mark.asyncio
async def test_list_snapshots(controller_api, project, snapshot):
async def test_list_snapshots(app: FastAPI, client: AsyncClient, project: Project, snapshot: Snapshot) -> None:
assert snapshot.name == "test"
response = await controller_api.get("/projects/{}/snapshots".format(project.id))
assert response.status_code == 200
assert len(response.json) == 1
response = await client.get(app.url_path_for("get_snapshots", project_id=project.id))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) == 1
@pytest.mark.asyncio
async def test_delete_snapshot(controller_api, project, snapshot):
async def test_delete_snapshot(app: FastAPI, client: AsyncClient, project: Project, snapshot: Snapshot) -> None:
response = await controller_api.delete("/projects/{}/snapshots/{}".format(project.id, snapshot.id))
assert response.status_code == 204
response = await client.delete(app.url_path_for("delete_snapshot", project_id=project.id, snapshot_id=snapshot.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
assert not os.path.exists(snapshot.path)
@pytest.mark.asyncio
async def test_restore_snapshot(controller_api, project, snapshot):
async def test_restore_snapshot(app: FastAPI, client: AsyncClient, project: Project, snapshot: Snapshot) -> None:
response = await controller_api.post("/projects/{}/snapshots/{}/restore".format(project.id, snapshot.id))
assert response.status_code == 201
assert response.json["name"] == project.name
response = await client.post(app.url_path_for("restore_snapshot", project_id=project.id, snapshot_id=snapshot.id))
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == project.name
@pytest.mark.asyncio
async def test_create_snapshot(controller_api, project):
async def test_create_snapshot(app: FastAPI, client: AsyncClient, project: Project) -> None:
response = await controller_api.post("/projects/{}/snapshots".format(project.id), {"name": "snap1"})
assert response.status_code == 201
response = await client.post(app.url_path_for("create_snapshot", project_id=project.id), json={"name": "snap1"})
assert response.status_code == status.HTTP_201_CREATED
assert len(os.listdir(os.path.join(project.path, "snapshots"))) == 1

@ -20,44 +20,49 @@ import pytest
import os
import urllib.parse
from fastapi import FastAPI, status
from httpx import AsyncClient
@pytest.mark.asyncio
async def test_symbols(controller_api):
from gns3server.controller import Controller
response = await controller_api.get('/symbols')
pytestmark = pytest.mark.asyncio
assert response.status_code == 200
async def test_symbols(app: FastAPI, client: AsyncClient) -> None:
response = await client.get(app.url_path_for("get_symbols"))
assert response.status_code == status.HTTP_200_OK
assert {
'symbol_id': ':/symbols/classic/firewall.svg',
'filename': 'firewall.svg',
'builtin': True,
'theme': 'Classic'
} in response.json
} in response.json()
@pytest.mark.asyncio
async def test_get(controller_api, controller):
async def test_get(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
controller.symbols.theme = "Classic"
response = await controller_api.get('/symbols/' + urllib.parse.quote(':/symbols/classic/firewall.svg') + '/raw')
assert response.status_code == 200
url = app.url_path_for("get_symbol", symbol_id=urllib.parse.quote(':/symbols/classic/firewall.svg'))
response = await client.get(url)
assert response.status_code == status.HTTP_200_OK
assert response.headers['CONTENT-TYPE'] == 'image/svg+xml'
assert response.headers['CONTENT-LENGTH'] == '9381'
assert '</svg>' in response.text
# Reply with the default symbol
response = await controller_api.get('/symbols/404.png/raw')
assert response.status_code == 200
response = await client.get(app.url_path_for("get_symbol", symbol_id="404.png"))
assert response.status_code == status.HTTP_200_OK
@pytest.mark.asyncio
async def test_upload(controller_api, symbols_dir):
async def test_upload(app: FastAPI, client: AsyncClient, symbols_dir: str) -> None:
response = await controller_api.post("/symbols/test2/raw", body=b"TEST", raw=True)
assert response.status_code == 204
response = await client.post(app.url_path_for("upload_symbol", symbol_id="test2"), content=b"TEST")
assert response.status_code == status.HTTP_204_NO_CONTENT
with open(os.path.join(symbols_dir, "test2")) as f:
assert f.read() == "TEST"
response = await controller_api.get('/symbols/test2/raw')
assert response.status_code == 200
response = await client.get(app.url_path_for("get_symbol", symbol_id="test2"))
assert response.status_code == status.HTTP_200_OK

@ -19,13 +19,16 @@ import pytest
import uuid
from pathlib import Path
from tests.utils import asyncio_patch
from fastapi import FastAPI, status
from httpx import AsyncClient
from gns3server.controller import Controller
from gns3server.controller.template import Template
pytestmark = pytest.mark.asyncio
@pytest.mark.asyncio
async def test_template_list(controller_api, controller):
async def test_template_list(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
id = str(uuid.uuid4())
controller.template_manager.load_templates()
@ -37,13 +40,12 @@ async def test_template_list(controller_api, controller):
"default_name_format": "{name}-{0}",
"compute_id": "local"
})
response = await controller_api.get("/templates")
assert response.status_code == 200
assert len(response.json) > 0
response = await client.get(app.url_path_for("get_templates"))
assert response.status_code == status.HTTP_200_OK
assert len(response.json()) > 0
@pytest.mark.asyncio
async def test_template_create_without_id(controller_api, controller):
async def test_template_create_without_id(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {"base_script_file": "vpcs_base_config.txt",
"category": "guest",
@ -55,14 +57,13 @@ async def test_template_create_without_id(controller_api, controller):
"symbol": ":/symbols/vpcs_guest.svg",
"template_type": "vpcs"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
assert len(controller.template_manager.templates) == 1
@pytest.mark.asyncio
async def test_template_create_with_id(controller_api, controller):
async def test_template_create_with_id(app: FastAPI, client: AsyncClient, controller: Controller):
params = {"template_id": str(uuid.uuid4()),
"base_script_file": "vpcs_base_config.txt",
@ -75,14 +76,13 @@ async def test_template_create_with_id(controller_api, controller):
"symbol": ":/symbols/vpcs_guest.svg",
"template_type": "vpcs"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
assert len(controller.template_manager.templates) == 1
@pytest.mark.asyncio
async def test_template_create_wrong_type(controller_api, controller):
async def test_template_create_wrong_type(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
params = {"template_id": str(uuid.uuid4()),
"base_script_file": "vpcs_base_config.txt",
@ -95,13 +95,12 @@ async def test_template_create_wrong_type(controller_api, controller):
"symbol": ":/symbols/vpcs_guest.svg",
"template_type": "invalid_template_type"}
response = await controller_api.post("/templates", params)
assert response.status_code == 422
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
assert len(controller.template_manager.templates) == 0
@pytest.mark.asyncio
async def test_template_get(controller_api):
async def test_template_get(app: FastAPI, client: AsyncClient) -> None:
template_id = str(uuid.uuid4())
params = {"template_id": template_id,
@ -115,16 +114,15 @@ async def test_template_get(controller_api):
"symbol": ":/symbols/vpcs_guest.svg",
"template_type": "vpcs"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
response = await controller_api.get("/templates/{}".format(template_id))
assert response.status_code == 200
assert response.json["template_id"] == template_id
response = await client.get(app.url_path_for("get_template", template_id=template_id))
assert response.status_code == status.HTTP_200_OK
assert response.json()["template_id"] == template_id
@pytest.mark.asyncio
async def test_template_update(controller_api):
async def test_template_update(app: FastAPI, client: AsyncClient) -> None:
template_id = str(uuid.uuid4())
params = {"template_id": template_id,
@ -138,22 +136,21 @@ async def test_template_update(controller_api):
"symbol": ":/symbols/vpcs_guest.svg",
"template_type": "vpcs"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
response = await controller_api.get("/templates/{}".format(template_id))
assert response.status_code == 200
assert response.json["template_id"] == template_id
response = await client.get(app.url_path_for("get_template", template_id=template_id))
assert response.status_code == status.HTTP_200_OK
assert response.json()["template_id"] == template_id
params["name"] = "VPCS_TEST_RENAMED"
response = await controller_api.put("/templates/{}".format(template_id), params)
response = await client.put(app.url_path_for("update_template", template_id=template_id), json=params)
assert response.status_code == 200
assert response.json["name"] == "VPCS_TEST_RENAMED"
assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "VPCS_TEST_RENAMED"
@pytest.mark.asyncio
async def test_template_delete(controller_api, controller):
async def test_template_delete(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
template_id = str(uuid.uuid4())
params = {"template_id": template_id,
@ -167,23 +164,22 @@ async def test_template_delete(controller_api, controller):
"symbol": ":/symbols/vpcs_guest.svg",
"template_type": "vpcs"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
response = await controller_api.get("/templates")
assert len(response.json) == 1
response = await client.get(app.url_path_for("get_templates"))
assert len(response.json()) == 1
assert len(controller.template_manager._templates) == 1
response = await controller_api.delete("/templates/{}".format(template_id))
assert response.status_code == 204
response = await client.delete(app.url_path_for("delete_template", template_id=template_id))
assert response.status_code == status.HTTP_204_NO_CONTENT
response = await controller_api.get("/templates")
assert len(response.json) == 0
response = await client.get(app.url_path_for("get_templates"))
assert len(response.json()) == 0
assert len(controller.template_manager.templates) == 0
@pytest.mark.asyncio
async def test_template_duplicate(controller_api, controller):
async def test_template_duplicate(app: FastAPI, client: AsyncClient, controller: Controller) -> None:
template_id = str(uuid.uuid4())
params = {"template_id": template_id,
@ -197,23 +193,22 @@ async def test_template_duplicate(controller_api, controller):
"symbol": ":/symbols/vpcs_guest.svg",
"template_type": "vpcs"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
response = await controller_api.post("/templates/{}/duplicate".format(template_id))
assert response.status_code == 201
assert response.json["template_id"] != template_id
response = await client.post(app.url_path_for("duplicate_template", template_id=template_id))
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] != template_id
params.pop("template_id")
for param, value in params.items():
assert response.json[param] == value
assert response.json()[param] == value
response = await controller_api.get("/templates")
assert len(response.json) == 2
response = await client.get(app.url_path_for("get_templates"))
assert len(response.json()) == 2
assert len(controller.template_manager.templates) == 2
@pytest.mark.asyncio
async def test_c7200_dynamips_template_create(controller_api):
async def test_c7200_dynamips_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Cisco c7200 template",
"platform": "c7200",
@ -221,9 +216,9 @@ async def test_c7200_dynamips_template_create(controller_api):
"image": "c7200-adventerprisek9-mz.124-24.T5.image",
"template_type": "dynamips"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"template_type": "dynamips",
"auto_delete_disks": False,
@ -255,11 +250,10 @@ async def test_c7200_dynamips_template_create(controller_api):
"system_id": "FTX0945W0MY"}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_c3745_dynamips_template_create(controller_api):
async def test_c3745_dynamips_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Cisco c3745 template",
"platform": "c3745",
@ -267,9 +261,9 @@ async def test_c3745_dynamips_template_create(controller_api):
"image": "c3745-adventerprisek9-mz.124-25d.image",
"template_type": "dynamips"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"template_type": "dynamips",
"auto_delete_disks": False,
@ -300,11 +294,10 @@ async def test_c3745_dynamips_template_create(controller_api):
"system_id": "FTX0945W0MY"}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_c3725_dynamips_template_create(controller_api):
async def test_c3725_dynamips_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Cisco c3725 template",
"platform": "c3725",
@ -312,9 +305,9 @@ async def test_c3725_dynamips_template_create(controller_api):
"image": "c3725-adventerprisek9-mz.124-25d.image",
"template_type": "dynamips"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"template_type": "dynamips",
"auto_delete_disks": False,
@ -345,11 +338,10 @@ async def test_c3725_dynamips_template_create(controller_api):
"system_id": "FTX0945W0MY"}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_c3600_dynamips_template_create(controller_api):
async def test_c3600_dynamips_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Cisco c3600 template",
"platform": "c3600",
@ -358,9 +350,9 @@ async def test_c3600_dynamips_template_create(controller_api):
"image": "c3660-a3jk9s-mz.124-25d.image",
"template_type": "dynamips"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"template_type": "dynamips",
"auto_delete_disks": False,
@ -392,11 +384,10 @@ async def test_c3600_dynamips_template_create(controller_api):
"system_id": "FTX0945W0MY"}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_c3600_dynamips_template_create_wrong_chassis(controller_api):
async def test_c3600_dynamips_template_create_wrong_chassis(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Cisco c3600 template",
"platform": "c3600",
@ -405,12 +396,11 @@ async def test_c3600_dynamips_template_create_wrong_chassis(controller_api):
"image": "c3660-a3jk9s-mz.124-25d.image",
"template_type": "dynamips"}
response = await controller_api.post("/templates", params)
assert response.status_code == 409
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_409_CONFLICT
@pytest.mark.asyncio
async def test_c2691_dynamips_template_create(controller_api):
async def test_c2691_dynamips_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Cisco c2691 template",
"platform": "c2691",
@ -418,9 +408,9 @@ async def test_c2691_dynamips_template_create(controller_api):
"image": "c2691-adventerprisek9-mz.124-25d.image",
"template_type": "dynamips"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"template_type": "dynamips",
"auto_delete_disks": False,
@ -451,11 +441,10 @@ async def test_c2691_dynamips_template_create(controller_api):
"system_id": "FTX0945W0MY"}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_c2600_dynamips_template_create(controller_api):
async def test_c2600_dynamips_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Cisco c2600 template",
"platform": "c2600",
@ -464,9 +453,9 @@ async def test_c2600_dynamips_template_create(controller_api):
"image": "c2600-adventerprisek9-mz.124-25d.image",
"template_type": "dynamips"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"template_type": "dynamips",
"auto_delete_disks": False,
@ -498,11 +487,10 @@ async def test_c2600_dynamips_template_create(controller_api):
"system_id": "FTX0945W0MY"}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_c2600_dynamips_template_create_wrong_chassis(controller_api):
async def test_c2600_dynamips_template_create_wrong_chassis(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Cisco c2600 template",
"platform": "c2600",
@ -511,12 +499,11 @@ async def test_c2600_dynamips_template_create_wrong_chassis(controller_api):
"image": "c2600-adventerprisek9-mz.124-25d.image",
"template_type": "dynamips"}
response = await controller_api.post("/templates", params)
assert response.status_code == 409
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_409_CONFLICT
@pytest.mark.asyncio
async def test_c1700_dynamips_template_create(controller_api):
async def test_c1700_dynamips_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Cisco c1700 template",
"platform": "c1700",
@ -525,9 +512,9 @@ async def test_c1700_dynamips_template_create(controller_api):
"image": "c1700-adventerprisek9-mz.124-25d.image",
"template_type": "dynamips"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"template_type": "dynamips",
"auto_delete_disks": False,
@ -559,11 +546,10 @@ async def test_c1700_dynamips_template_create(controller_api):
"system_id": "FTX0945W0MY"}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_c1700_dynamips_template_create_wrong_chassis(controller_api):
async def test_c1700_dynamips_template_create_wrong_chassis(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Cisco c1700 template",
"platform": "c1700",
@ -572,12 +558,11 @@ async def test_c1700_dynamips_template_create_wrong_chassis(controller_api):
"image": "c1700-adventerprisek9-mz.124-25d.image",
"template_type": "dynamips"}
response = await controller_api.post("/templates", params)
assert response.status_code == 409
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_409_CONFLICT
@pytest.mark.asyncio
async def test_dynamips_template_create_wrong_platform(controller_api):
async def test_dynamips_template_create_wrong_platform(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Cisco c3900 template",
"platform": "c3900",
@ -585,12 +570,11 @@ async def test_dynamips_template_create_wrong_platform(controller_api):
"image": "c3900-test.124-25d.image",
"template_type": "dynamips"}
response = await controller_api.post("/templates", params)
assert response.status_code == 409
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_409_CONFLICT
@pytest.mark.asyncio
async def test_iou_template_create(controller_api):
async def test_iou_template_create(app: FastAPI, client: AsyncClient) -> None:
image_path = str(Path("/path/to/i86bi_linux-ipbase-ms-12.4.bin"))
params = {"name": "IOU template",
@ -598,9 +582,9 @@ async def test_iou_template_create(controller_api):
"path": image_path,
"template_type": "iou"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"template_type": "iou",
"builtin": False,
@ -622,20 +606,19 @@ async def test_iou_template_create(controller_api):
"l1_keepalives": False}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_docker_template_create(controller_api):
async def test_docker_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Docker template",
"compute_id": "local",
"image": "gns3/endhost:latest",
"template_type": "docker"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"adapters": 1,
"template_type": "docker",
@ -657,11 +640,10 @@ async def test_docker_template_create(controller_api):
"custom_adapters": []}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_qemu_template_create(controller_api):
async def test_qemu_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Qemu template",
"compute_id": "local",
@ -670,9 +652,9 @@ async def test_qemu_template_create(controller_api):
"ram": 512,
"template_type": "qemu"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"adapter_type": "e1000",
"adapters": 1,
@ -717,11 +699,10 @@ async def test_qemu_template_create(controller_api):
"custom_adapters": []}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_vmware_template_create(controller_api):
async def test_vmware_template_create(app: FastAPI, client: AsyncClient) -> None:
vmx_path = str(Path("/path/to/vm.vmx"))
params = {"name": "VMware template",
@ -729,9 +710,9 @@ async def test_vmware_template_create(controller_api):
"template_type": "vmware",
"vmx_path": vmx_path}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"adapter_type": "e1000",
"adapters": 1,
@ -755,20 +736,19 @@ async def test_vmware_template_create(controller_api):
"custom_adapters": []}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_virtualbox_template_create(controller_api):
async def test_virtualbox_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "VirtualBox template",
"compute_id": "local",
"template_type": "virtualbox",
"vmname": "My VirtualBox VM"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"adapter_type": "Intel PRO/1000 MT Desktop (82540EM)",
"adapters": 1,
@ -793,19 +773,18 @@ async def test_virtualbox_template_create(controller_api):
"custom_adapters": []}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_vpcs_template_create(controller_api):
async def test_vpcs_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "VPCS template",
"compute_id": "local",
"template_type": "vpcs"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"template_type": "vpcs",
"base_script_file": "vpcs_base_config.txt",
@ -819,19 +798,18 @@ async def test_vpcs_template_create(controller_api):
"symbol": ":/symbols/vpcs_guest.svg"}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_ethernet_switch_template_create(controller_api):
async def test_ethernet_switch_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Ethernet switch template",
"compute_id": "local",
"template_type": "ethernet_switch"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"template_type": "ethernet_switch",
"builtin": False,
@ -840,49 +818,49 @@ async def test_ethernet_switch_template_create(controller_api):
"console_type": "none",
"default_name_format": "Switch{0}",
"name": "Ethernet switch template",
"ports_mapping": [{"ethertype": "",
"ports_mapping": [{"ethertype": "0x8100",
"name": "Ethernet0",
"port_number": 0,
"type": "access",
"vlan": 1
},
{"ethertype": "",
{"ethertype": "0x8100",
"name": "Ethernet1",
"port_number": 1,
"type": "access",
"vlan": 1
},
{"ethertype": "",
{"ethertype": "0x8100",
"name": "Ethernet2",
"port_number": 2,
"type": "access",
"vlan": 1
},
{"ethertype": "",
{"ethertype": "0x8100",
"name": "Ethernet3",
"port_number": 3,
"type": "access",
"vlan": 1
},
{"ethertype": "",
{"ethertype": "0x8100",
"name": "Ethernet4",
"port_number": 4,
"type": "access",
"vlan": 1
},
{"ethertype": "",
{"ethertype": "0x8100",
"name": "Ethernet5",
"port_number": 5,
"type": "access",
"vlan": 1
},
{"ethertype": "",
{"ethertype": "0x8100",
"name": "Ethernet6",
"port_number": 6,
"type": "access",
"vlan": 1
},
{"ethertype": "",
{"ethertype": "0x8100",
"name": "Ethernet7",
"port_number": 7,
"type": "access",
@ -891,19 +869,18 @@ async def test_ethernet_switch_template_create(controller_api):
"symbol": ":/symbols/ethernet_switch.svg"}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_cloud_template_create(controller_api):
async def test_cloud_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Cloud template",
"compute_id": "local",
"template_type": "cloud"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"template_type": "cloud",
"builtin": False,
@ -919,19 +896,18 @@ async def test_cloud_template_create(controller_api):
"remote_console_http_path": "/"}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
@pytest.mark.asyncio
async def test_ethernet_hub_template_create(controller_api):
async def test_ethernet_hub_template_create(app: FastAPI, client: AsyncClient) -> None:
params = {"name": "Ethernet hub template",
"compute_id": "local",
"template_type": "ethernet_hub"}
response = await controller_api.post("/templates", params)
assert response.status_code == 201
assert response.json["template_id"] is not None
response = await client.post(app.url_path_for("create_template"), json=params)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["template_id"] is not None
expected_response = {"ports_mapping": [{"port_number": 0,
"name": "Ethernet0"
@ -966,7 +942,7 @@ async def test_ethernet_hub_template_create(controller_api):
"builtin": False}
for item, value in expected_response.items():
assert response.json.get(item) == value
assert response.json().get(item) == value
# @pytest.mark.asyncio
@ -982,9 +958,9 @@ async def test_ethernet_hub_template_create(controller_api):
# "compute_id": "example.com"
# })}
# with asyncio_patch("gns3server.controller.project.Project.add_node_from_template", return_value={"name": "test", "node_type": "qemu", "compute_id": "example.com"}) as mock:
# response = await controller_api.post("/projects/{}/templates/{}".format(project.id, id), {
# response = await client.post("/projects/{}/templates/{}".format(project.id, id), {
# "x": 42,
# "y": 12
# })
# mock.assert_called_with(id, x=42, y=12, compute_id=None)
# assert response.status_code == 201
# assert response.status_code == status.HTTP_201_CREATED

@ -0,0 +1,62 @@
#!/usr/bin/env python
#
# Copyright (C) 2020 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
from fastapi import FastAPI, status
from fastapi.encoders import jsonable_encoder
from httpx import AsyncClient
from gns3server.db.repositories.users import UsersRepository
from gns3server.schemas.users import User
pytestmark = pytest.mark.asyncio
# async def test_route_exist(app: FastAPI, client: AsyncClient) -> None:
#
# params = {"username": "test_username", "email": "user@email.com", "password": "test_password"}
# response = await client.post(app.url_path_for("create_user"), json=params)
# assert response.status_code != status.HTTP_404_NOT_FOUND
#
#
# async def test_users_can_register_successfully(app: FastAPI, client: AsyncClient) -> None:
#
# user_repo = UsersRepository()
# params = {"username": "test_username2", "email": "user2@email.com", "password": "test_password2"}
#
# # make sure the user doesn't exist in the database
# user_in_db = await user_repo.get_user_by_username(params["username"])
# assert user_in_db is None
#
# # register the user
# res = await client.post(app.url_path_for("create_user"), json=params)
# assert res.status_code == status.HTTP_201_CREATED
#
# # make sure the user does exists in the database now
# user_in_db = await user_repo.get_user_by_username(params["username"])
# assert user_in_db is not None
# assert user_in_db.email == params["email"]
# assert user_in_db.username == params["username"]
#
# # check that the user returned in the response is equal to the user in the database
# created_user = User(**res.json()).json()
# print(created_user)
# #print(user_in_db.__dict__)
# test = jsonable_encoder(user_in_db.__dict__, exclude={"_sa_instance_state", "hashed_password"})
# print(test)
# assert created_user == test

@ -17,47 +17,47 @@
import pytest
from fastapi import FastAPI, status
from httpx import AsyncClient
from gns3server.version import __version__
pytestmark = pytest.mark.asyncio
@pytest.mark.asyncio
async def test_version_output(controller_api, config):
async def test_version_output(app: FastAPI, client: AsyncClient, config) -> None:
config.set("Server", "local", "true")
response = await controller_api.get('/version')
assert response.status_code == 200
assert response.json == {'local': True, 'version': __version__}
response = await client.get(app.url_path_for("get_version"))
assert response.status_code == status.HTTP_200_OK
assert response.json() == {'local': True, 'version': __version__}
@pytest.mark.asyncio
async def test_version_input(controller_api):
async def test_version_input(app: FastAPI, client: AsyncClient) -> None:
params = {'version': __version__}
response = await controller_api.post('/version', params)
assert response.status_code == 200
assert response.json == {'version': __version__}
response = await client.post(app.url_path_for("check_version"), json=params)
assert response.status_code == status.HTTP_200_OK
assert response.json() == {'version': __version__}
@pytest.mark.asyncio
async def test_version_invalid_input(controller_api):
async def test_version_invalid_input(app: FastAPI, client: AsyncClient) -> None:
params = {'version': "0.4.2"}
response = await controller_api.post('/version', params)
assert response.status_code == 409
assert response.json == {'message': 'Client version 0.4.2 is not the same as server version {}'.format(__version__)}
response = await client.post(app.url_path_for("check_version"), json=params)
assert response.status_code == status.HTTP_409_CONFLICT
assert response.json() == {'message': 'Client version 0.4.2 is not the same as server version {}'.format(__version__)}
@pytest.mark.asyncio
async def test_version_invalid_input_schema(controller_api):
async def test_version_invalid_input_schema(app: FastAPI, client: AsyncClient) -> None:
params = {'version': "0.4.2", "bla": "blu"}
response = await controller_api.post('/version', params)
assert response.status_code == 409
response = await client.post(app.url_path_for("check_version"), json=params)
assert response.status_code == status.HTTP_409_CONFLICT
@pytest.mark.asyncio
async def test_version_invalid_json(controller_api):
async def test_version_invalid_json(app: FastAPI, client: AsyncClient) -> None:
params = "BOUM"
response = await controller_api.post('/version', params, raw=True)
assert response.status_code == 422
response = await client.post(app.url_path_for("check_version"), json=params)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY

@ -18,12 +18,16 @@
import pytest
import os
from fastapi import FastAPI, status
from httpx import AsyncClient
from unittest.mock import patch
from gns3server.version import __version__
from gns3server.controller import Controller
from gns3server.utils.get_resource import get_resource
pytestmark = pytest.mark.asyncio
def get_static(filename):
@ -31,15 +35,13 @@ def get_static(filename):
return os.path.join(os.path.abspath(os.path.join(current_dir, '../..', '..', 'gns3server', 'static')), filename)
@pytest.mark.asyncio
async def test_debug(http_client):
async def test_debug(app: FastAPI, client: AsyncClient) -> None:
async with http_client as client:
response = await client.get('/debug')
assert response.status_code == 200
html = response.text
assert "Website" in html
assert __version__ in html
response = await client.get(app.url_path_for("debug"))
assert response.status_code == status.HTTP_200_OK
html = response.read().decode()
assert "Website" in html
assert __version__ in html
# @pytest.mark.asyncio
@ -66,20 +68,16 @@ async def test_debug(http_client):
# assert response.status_code == 200
@pytest.mark.asyncio
async def test_web_ui(http_client):
async def test_web_ui(app: FastAPI, client: AsyncClient) -> None:
async with http_client as client:
response = await client.get('/static/web-ui/index.html')
assert response.status_code == 200
response = await client.get(app.url_path_for("web_ui", file_path="index.html"))
assert response.status_code == status.HTTP_200_OK
@pytest.mark.asyncio
async def test_web_ui_not_found(http_client, tmpdir):
async def test_web_ui_not_found(app: FastAPI, client: AsyncClient, tmpdir: str) -> None:
with patch('gns3server.utils.get_resource.get_resource') as mock:
mock.return_value = str(tmpdir)
async with http_client as client:
response = await client.get('/static/web-ui/not-found.txt')
# should serve web-ui/index.html
assert response.status_code == 200
response = await client.get(app.url_path_for("web_ui", file_path="not-found.txt"))
# should serve web-ui/index.html
assert response.status_code == status.HTTP_200_OK

@ -5,6 +5,10 @@ import shutil
import sys
import os
from fastapi import FastAPI
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from asgi_lifespan import LifespanManager
from httpx import AsyncClient
from unittest.mock import MagicMock, patch
from pathlib import Path
@ -13,16 +17,23 @@ from gns3server.config import Config
from gns3server.compute import MODULES
from gns3server.compute.port_manager import PortManager
from gns3server.compute.project_manager import ProjectManager
from tests.api.routes.base import Query
from gns3server.db.database import Base
sys._called_from_test = True
sys.original_platform = sys.platform
from fastapi.testclient import TestClient
from gns3server.api.server import app
from httpx import AsyncClient
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_async_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
async def start_db():
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
await conn.run_sync(Base.metadata.create_all)
if sys.platform.startswith("win") and sys.version_info < (3, 8):
@ -39,16 +50,39 @@ if sys.platform.startswith("win") and sys.version_info < (3, 8):
asyncio.set_event_loop(None)
@pytest.fixture(scope='function')
def http_client():
# @pytest.mark.asyncio
# @pytest.fixture(scope="session", autouse=True)
# async def database_connection() -> None:
#
# from gns3server.db.tasks import connect_to_db
# os.environ["DATABASE_URI"] = "sqlite:///./sql_app_test.db"
# await connect_to_db()
# yield
@pytest.fixture#(scope="session")
async def app() -> FastAPI:
return AsyncClient(app=app, base_url="http://test-api")
from gns3server.api.server import app as gns3_app
gns3_app.add_event_handler("startup", start_db())
return gns3_app
@pytest.fixture(scope='function')
def ws_client():
# Grab a reference to our database when needed
#@pytest.fixture
#def db(app: FastAPI) -> Database:
# return app.state._db
return TestClient(app)
@pytest.fixture
async def client(app: FastAPI) -> AsyncClient:
#async with LifespanManager(app):
async with AsyncClient(
app=app,
base_url="http://test-api",
headers={"Content-Type": "application/json"}
) as client:
yield client
@pytest.fixture
@ -91,24 +125,6 @@ def compute_project(tmpdir):
return ProjectManager.instance().create_project(project_id="a1e920ca-338a-4e9f-b363-aa607b09dd80")
@pytest.fixture
def compute_api(http_client, ws_client):
"""
Return an helper allowing you to call the hypervisor API via HTTP
"""
return Query(http_client, ws_client, prefix="/compute", api_version=3)
@pytest.fixture
def controller_api(http_client, ws_client, controller):
"""
Return an helper allowing you to call the server API without any prefix
"""
return Query(http_client, ws_client, api_version=3)
@pytest.fixture
def config():
@ -285,3 +301,4 @@ def run_around_tests(monkeypatch, config, port_manager):#port_manager, controlle
shutil.rmtree(tmppath)
except BaseException:
pass

Loading…
Cancel
Save