diff --git a/gns3server/handlers/project_handler.py b/gns3server/handlers/project_handler.py index e6fdbc59..83bfa401 100644 --- a/gns3server/handlers/project_handler.py +++ b/gns3server/handlers/project_handler.py @@ -16,7 +16,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. from ..web.route import Route -from ..schemas.project import PROJECT_OBJECT_SCHEMA +from ..schemas.project import PROJECT_OBJECT_SCHEMA, PROJECT_CREATE_SCHEMA from ..modules.project_manager import ProjectManager from aiohttp.web import HTTPConflict @@ -28,13 +28,14 @@ class ProjectHandler: r"/project", description="Create a project on the server", output=PROJECT_OBJECT_SCHEMA, - input=PROJECT_OBJECT_SCHEMA) + input=PROJECT_CREATE_SCHEMA) def create_project(request, response): pm = ProjectManager.instance() p = pm.create_project( location=request.json.get("location"), - uuid=request.json.get("uuid") + uuid=request.json.get("uuid"), + temporary=request.json.get("temporary", False) ) response.json(p) diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py index 4e8c61df..40ba0601 100644 --- a/gns3server/modules/project.py +++ b/gns3server/modules/project.py @@ -29,9 +29,10 @@ class Project: :param uuid: Force project uuid (None by default auto generate an UUID) :param location: Parent path of the project. (None should create a tmp directory) + :param temporary: Boolean the project is a temporary project (destroy when closed) """ - def __init__(self, uuid=None, location=None): + def __init__(self, uuid=None, location=None, temporary=False): if uuid is None: self._uuid = str(uuid4()) @@ -46,6 +47,7 @@ class Project: if location is None: self._location = tempfile.mkdtemp() + self._temporary = temporary self._vms = set() self._vms_to_destroy = set() self._path = os.path.join(self._location, self._uuid) @@ -102,7 +104,8 @@ class Project: return { "uuid": self._uuid, - "location": self._location + "location": self._location, + "temporary": self._temporary } def add_vm(self, vm): @@ -110,7 +113,7 @@ class Project: Add a VM to the project. In theory this should be called by the VM manager. - :params vm: A VM instance + :param vm: A VM instance """ self._vms.add(vm) @@ -120,7 +123,7 @@ class Project: Remove a VM from the project. In theory this should be called by the VM manager. - :params vm: A VM instance + :param vm: A VM instance """ if vm in self._vms: @@ -129,8 +132,19 @@ class Project: def close(self): """Close the project, but keep informations on disk""" + self._close_and_clean(self._temporary) + + def _close_and_clean(self, cleanup): + """ + Close the project, and cleanup the disk if cleanup is True + + :param cleanup: If True drop the project directory + """ + for vm in self._vms: vm.close() + if cleanup and os.path.exists(self.path): + shutil.rmtree(self.path) def commit(self): """Write project changes on disk""" @@ -145,6 +159,4 @@ class Project: def delete(self): """Remove project from disk""" - self.close() - if os.path.exists(self.path): - shutil.rmtree(self.path) + self._close_and_clean(True) diff --git a/gns3server/schemas/project.py b/gns3server/schemas/project.py index 1e363ec0..08ef042d 100644 --- a/gns3server/schemas/project.py +++ b/gns3server/schemas/project.py @@ -16,6 +16,31 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. +PROJECT_CREATE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to create a new Project instance", + "type": "object", + "properties": { + "location": { + "description": "Base directory where the project should be created on remote server", + "type": "string", + "minLength": 1 + }, + "uuid": { + "description": "Project UUID", + "type": "string", + "minLength": 36, + "maxLength": 36, + "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" + }, + "temporary": { + "description": "If project is a temporary project", + "type": "boolean" + }, + }, + "additionalProperties": False, +} + PROJECT_OBJECT_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "Request validation to create a new Project instance", @@ -33,6 +58,11 @@ PROJECT_OBJECT_SCHEMA = { "maxLength": 36, "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" }, + "temporary": { + "description": "If project is a temporary project", + "type": "boolean" + }, }, - "additionalProperties": False + "additionalProperties": False, + "required": ["location", "uuid", "temporary"] } diff --git a/tests/api/test_project.py b/tests/api/test_project.py index e343b735..28cbb046 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -34,6 +34,15 @@ def test_create_project_without_dir(server): response = server.post("/project", query) assert response.status == 200 assert response.json["uuid"] is not None + assert response.json["temporary"] is False + + +def test_create_temporary_project(server): + query = {"temporary": True} + response = server.post("/project", query) + assert response.status == 200 + assert response.json["uuid"] is not None + assert response.json["temporary"] is True def test_create_project_with_uuid(server): diff --git a/tests/modules/test_project.py b/tests/modules/test_project.py index d26488ac..4aaee655 100644 --- a/tests/modules/test_project.py +++ b/tests/modules/test_project.py @@ -58,7 +58,7 @@ def test_temporary_path(): def test_json(tmpdir): p = Project() - assert p.__json__() == {"location": p.location, "uuid": p.uuid} + assert p.__json__() == {"location": p.location, "uuid": p.uuid, "temporary": False} def test_vm_working_directory(tmpdir, vm): @@ -111,3 +111,13 @@ def test_project_close(tmpdir, manager): with patch("gns3server.modules.vpcs.vpcs_vm.VPCSVM.close") as mock: project.close() assert mock.called + + +def test_project_close_temporary_project(tmpdir, manager): + """A temporary project is deleted when closed""" + + project = Project(location=str(tmpdir), temporary=True) + directory = project.path + assert os.path.exists(directory) + project.close() + assert os.path.exists(directory) is False