diff --git a/gns3server/handlers/project_handler.py b/gns3server/handlers/project_handler.py index 79d1b68e..caf3524f 100644 --- a/gns3server/handlers/project_handler.py +++ b/gns3server/handlers/project_handler.py @@ -30,9 +30,28 @@ class ProjectHandler: output=PROJECT_OBJECT_SCHEMA, input=PROJECT_OBJECT_SCHEMA) def create_project(request, response): + pm = ProjectManager.instance() p = pm.create_project( location=request.json.get("location"), uuid=request.json.get("uuid") ) response.json(p) + + @classmethod + @Route.post( + r"/project/{uuid}/commit", + description="Write changes on disk", + parameters={ + "uuid": "Project instance UUID", + }, + status_codes={ + 204: "Changes write on disk", + 404: "Project instance doesn't exist" + }) + def create_project(request, response): + + pm = ProjectManager.instance() + project = pm.get_project(request.match_info["uuid"]) + project.commit() + response.set_status(204) diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py index 9710ace8..641ed790 100644 --- a/gns3server/modules/base_manager.py +++ b/gns3server/modules/base_manager.py @@ -149,14 +149,13 @@ class BaseManager: self._vms[vm.uuid] = vm return vm - # FIXME: should be named close_vm and we should have a - # delete_vm when a user deletes a VM (including files in workdir) @asyncio.coroutine - def delete_vm(self, uuid): + def close_vm(self, uuid): """ Delete a VM :param uuid: VM UUID + :returns: VM instance """ vm = self.get_vm(uuid) @@ -164,7 +163,22 @@ class BaseManager: yield from vm.close() else: vm.close() + return vm + + @asyncio.coroutine + def delete_vm(self, uuid): + """ + Delete a VM. VM working directory will be destroy when + we receive a commit. + + :param uuid: VM UUID + :returns: VM instance + """ + + vm = yield from self.close_vm(uuid) + vm.project.mark_vm_for_destruction(vm) del self._vms[vm.uuid] + return vm @staticmethod def _has_privileged_access(executable): diff --git a/gns3server/modules/base_vm.py b/gns3server/modules/base_vm.py index 51fd0461..52c003b2 100644 --- a/gns3server/modules/base_vm.py +++ b/gns3server/modules/base_vm.py @@ -96,7 +96,7 @@ class BaseVM: Return VM working directory """ - return self._project.vm_working_directory(self.manager.module_name.lower(), self._uuid) + return self._project.vm_working_directory(self) def create(self): """ diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py index 9e83ffae..00159fbf 100644 --- a/gns3server/modules/project.py +++ b/gns3server/modules/project.py @@ -18,6 +18,7 @@ import aiohttp import os import tempfile +import shutil from uuid import UUID, uuid4 @@ -45,6 +46,7 @@ class Project: if location is None: self._location = tempfile.mkdtemp() + self._vms_to_destroy = set() self._path = os.path.join(self._location, self._uuid) try: os.makedirs(os.path.join(self._path, "vms"), exist_ok=True) @@ -66,25 +68,40 @@ class Project: return self._path - def vm_working_directory(self, module, vm_uuid): + def vm_working_directory(self, vm): """ Return a working directory for a specific VM. If the directory doesn't exist, the directory is created. - :param module: The module name (vpcs, dynamips...) - :param vm_uuid: VM UUID + :param vm: An instance of VM + :returns: A string with a VM working directory """ - workdir = os.path.join(self._path, module, vm_uuid) + workdir = os.path.join(self._path, vm.manager.module_name.lower(), vm.uuid) try: os.makedirs(workdir, exist_ok=True) except OSError as e: raise aiohttp.web.HTTPInternalServerError(text="Could not create VM working directory: {}".format(e)) return workdir + def mark_vm_for_destruction(self, vm): + """ + :param vm: An instance of VM + """ + + self._vms_to_destroy.add(vm) + def __json__(self): return { "uuid": self._uuid, "location": self._location } + + def commit(self): + """Write project changes on disk""" + while self._vms_to_destroy: + vm = self._vms_to_destroy.pop() + directory = self.vm_working_directory(vm) + if os.path.exists(directory): + shutil.rmtree(directory) diff --git a/tests/api/test_project.py b/tests/api/test_project.py index 1c42bd63..d8312cb0 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -19,6 +19,8 @@ This test suite check /project endpoint """ +import uuid + def test_create_project_with_dir(server, tmpdir): response = server.post("/project", {"location": str(tmpdir)}) @@ -46,3 +48,13 @@ def test_create_project_with_uuid(server): assert response.status == 200 assert response.json["uuid"] == "00010203-0405-0607-0809-0a0b0c0d0e0f" assert response.json["location"] == "/tmp" + + +def test_commit_project(server, project): + response = server.post("/project/{uuid}/commit".format(uuid=project.uuid)) + assert response.status == 204 + + +def test_commit_project_invalid_project_uuid(server, project): + response = server.post("/project/{uuid}/commit".format(uuid=uuid.uuid4())) + assert response.status == 404 diff --git a/tests/modules/test_project.py b/tests/modules/test_project.py index d641a37e..fa269d28 100644 --- a/tests/modules/test_project.py +++ b/tests/modules/test_project.py @@ -17,7 +17,21 @@ # along with this program. If not, see . import os +import pytest from gns3server.modules.project import Project +from gns3server.modules.vpcs import VPCS, VPCSVM + + +@pytest.fixture(scope="module") +def manager(port_manager): + m = VPCS.instance() + m.port_manager = port_manager + return m + + +@pytest.fixture(scope="function") +def vm(project, manager): + return VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager) def test_affect_uuid(): @@ -45,7 +59,25 @@ def test_json(tmpdir): assert p.__json__() == {"location": p.location, "uuid": p.uuid} -def test_vm_working_directory(tmpdir): +def test_vm_working_directory(tmpdir, vm): p = Project(location=str(tmpdir)) - assert os.path.exists(p.vm_working_directory('vpcs', '00010203-0405-0607-0809-0a0b0c0d0e0f')) - assert os.path.exists(os.path.join(str(tmpdir), p.uuid, 'vpcs', '00010203-0405-0607-0809-0a0b0c0d0e0f')) + assert os.path.exists(p.vm_working_directory(vm)) + assert os.path.exists(os.path.join(str(tmpdir), p.uuid, vm.module_name, vm.uuid)) + + +def test_mark_vm_for_destruction(tmpdir, vm): + p = Project(location=str(tmpdir)) + p.mark_vm_for_destruction(vm) + assert len(p._vms_to_destroy) == 1 + + +def test_commit(tmpdir, manager): + project = Project(location=str(tmpdir)) + vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager) + directory = project.vm_working_directory(vm) + project.mark_vm_for_destruction(vm) + assert len(project._vms_to_destroy) == 1 + assert os.path.exists(directory) + project.commit() + assert len(project._vms_to_destroy) == 0 + assert os.path.exists(directory) is False