Refactor tests and start work on database integration.

This commit is contained in:
grossmj committed 2020-12-02 18:39:08 +10:30
1 parent ae55c0ec9c
commit bf7cf862af
69 files changed
+2955 -1877

No files matched your search

@@ -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:
+15 -15
View File
@@ -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.
+7 -7
View File
@@ -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.
"""
+14 -14
View File
@@ -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.
+15 -15
View File
@@ -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.
"""
+21 -21
View File
@@ -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.
+9 -9
View File
@@ -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)
+16 -8
View File
@@ -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.
+13 -13
View File
@@ -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.
+16 -8
View File
@@ -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.
"""
Whitespace-only changes.
@@ -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
+1 -1
View File
@@ -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.
"""
+1 -1
View File
@@ -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
+125
View File
@@ -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
+10
View File
@@ -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):
+2 -2
View File
@@ -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
+3 -5
View File
@@ -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
+26
View File
@@ -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()
+95
View File
@@ -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")
Whitespace-only changes.
+29
View File
@@ -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()
+110
View File
@@ -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
+34
View File
@@ -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}")
+2
View File
@@ -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
+26
View File
@@ -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")
]
+30
View File
@@ -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
+59
View File
@@ -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
+19
View File
@@ -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()
+74
View File
@@ -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