From ca3bf592d6ce4ca7ea2a9ed7af06b7f46cc84b3a Mon Sep 17 00:00:00 2001 From: grossmj Date: Tue, 30 Aug 2022 22:49:47 +0200 Subject: [PATCH] Global project lock and unlock --- gns3server/api/routes/controller/projects.py | 20 +++++++- gns3server/controller/project.py | 32 ++++++++++++ tests/api/routes/controller/test_projects.py | 53 ++++++++++++++++++-- 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/gns3server/api/routes/controller/projects.py b/gns3server/api/routes/controller/projects.py index 60a7d8a5..b8797e3c 100644 --- a/gns3server/api/routes/controller/projects.py +++ b/gns3server/api/routes/controller/projects.py @@ -36,7 +36,6 @@ from fastapi.responses import StreamingResponse, FileResponse from websockets.exceptions import ConnectionClosed, WebSocketException from typing import List, Optional from uuid import UUID -from pathlib import Path from gns3server import schemas from gns3server.controller import Controller @@ -46,7 +45,6 @@ from gns3server.controller.import_project import import_project as import_contro from gns3server.controller.export_project import export_project as export_controller_project from gns3server.utils.asyncio import aiozipstream from gns3server.utils.path import is_safe_path -from gns3server.config import Config from gns3server.db.repositories.rbac import RbacRepository from gns3server.db.repositories.templates import TemplatesRepository from gns3server.services.templates import TemplatesService @@ -397,6 +395,24 @@ async def duplicate_project( return new_project.asdict() +@router.post("/{project_id}/lock", status_code=status.HTTP_204_NO_CONTENT) +async def lock_project(project: Project = Depends(dep_project)) -> None: + """ + Lock all drawings and nodes in a given project. + """ + + project.lock() + + +@router.post("/{project_id}/unlock", status_code=status.HTTP_204_NO_CONTENT) +async def unlock_project(project: Project = Depends(dep_project)) -> None: + """ + Unlock all drawings and nodes in a given project. + """ + + project.unlock() + + @router.get("/{project_id}/files/{file_path:path}") async def get_file(file_path: str, project: Project = Depends(dep_project)) -> FileResponse: """ diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index ff7238b2..37446cae 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -1112,6 +1112,38 @@ class Project: return True return False + @open_required + def lock(self): + """ + Lock all drawings and nodes + """ + + for drawing in self._drawings.values(): + if not drawing.locked: + drawing.locked = True + self.emit_notification("drawing.updated", drawing.asdict()) + for node in self.nodes.values(): + if not node.locked: + node.locked = True + self.emit_notification("node.updated", node.asdict()) + self.dump() + + @open_required + def unlock(self): + """ + Unlock all drawings and nodes + """ + + for drawing in self._drawings.values(): + if drawing.locked: + drawing.locked = False + self.emit_notification("drawing.updated", drawing.asdict()) + for node in self.nodes.values(): + if node.locked: + node.locked = False + self.emit_notification("node.updated", node.asdict()) + self.dump() + def dump(self): """ Dump topology to disk diff --git a/tests/api/routes/controller/test_projects.py b/tests/api/routes/controller/test_projects.py index ebbd219b..e94cc8fc 100644 --- a/tests/api/routes/controller/test_projects.py +++ b/tests/api/routes/controller/test_projects.py @@ -24,11 +24,12 @@ import pytest_asyncio from fastapi import FastAPI, status from httpx import AsyncClient from unittest.mock import patch, MagicMock -from tests.utils import asyncio_patch +from tests.utils import asyncio_patch, AsyncioMagicMock import gns3server.utils.zipfile_zstd as zipfile_zstd from gns3server.controller import Controller from gns3server.controller.project import Project +from gns3server.controller.compute import Compute pytestmark = pytest.mark.asyncio @@ -36,10 +37,10 @@ pytestmark = pytest.mark.asyncio @pytest_asyncio.fixture async def project(app: FastAPI, client: AsyncClient, controller: Controller) -> Project: - u = str(uuid.uuid4()) - params = {"name": "test", "project_id": u} + project_id = str(uuid.uuid4()) + params = {"name": "test", "project_id": project_id} await client.post(app.url_path_for("create_project"), json=params) - return controller.get_project(u) + return controller.get_project(project_id) async def test_create_project_with_path(app: FastAPI, client: AsyncClient, controller: Controller, config) -> None: @@ -473,3 +474,47 @@ async def test_duplicate(app: FastAPI, client: AsyncClient, project: Project) -> 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" + + +async def test_lock_unlock(app: FastAPI, client: AsyncClient, project: Project, compute: Compute) -> None: + + # add a drawing and node to the project + params = { + "svg": '', + "x": 10, + "y": 20, + "z": 0 + } + + response = await client.post(app.url_path_for("create_drawing", project_id=project.id), json=params) + assert response.status_code == status.HTTP_201_CREATED + + response = MagicMock() + response.json = {"console": 2048} + compute.post = AsyncioMagicMock(return_value=response) + + response = await client.post(app.url_path_for("create_node", project_id=project.id), json={ + "name": "test", + "node_type": "vpcs", + "compute_id": "example.com", + "properties": { + "startup_script": "echo test" + } + }) + assert response.status_code == status.HTTP_201_CREATED + + response = await client.post(app.url_path_for("lock_project", project_id=project.id)) + assert response.status_code == status.HTTP_204_NO_CONTENT + + for drawing in project.drawings.values(): + assert drawing.locked is True + for node in project.nodes.values(): + assert node.locked is True + + response = await client.post(app.url_path_for("unlock_project", project_id=project.id)) + assert response.status_code == status.HTTP_204_NO_CONTENT + + for drawing in project.drawings.values(): + assert drawing.locked is False + for node in project.nodes.values(): + assert node.locked is False