From 54fc873be58d9e279254f1068004b291a7b9ae6a Mon Sep 17 00:00:00 2001 From: Jeremy Date: Wed, 25 Feb 2015 16:05:57 -0700 Subject: [PATCH] Prevent multiple projects with the same ID to be created. --- gns3server/handlers/api/project_handler.py | 7 ++++++ gns3server/modules/base_manager.py | 21 +++++++++------- gns3server/modules/dynamips/nodes/router.py | 1 + gns3server/modules/iou/iou_vm.py | 2 ++ gns3server/modules/project.py | 5 +++- gns3server/modules/project_manager.py | 17 +++++++++++-- gns3server/modules/qemu/qemu_vm.py | 1 + .../modules/virtualbox/virtualbox_vm.py | 1 + gns3server/modules/vpcs/vpcs_vm.py | 9 +++---- tests/handlers/api/test_project.py | 24 +++++++++---------- 10 files changed, 61 insertions(+), 27 deletions(-) diff --git a/gns3server/handlers/api/project_handler.py b/gns3server/handlers/api/project_handler.py index 00559dca..f9bd0e72 100644 --- a/gns3server/handlers/api/project_handler.py +++ b/gns3server/handlers/api/project_handler.py @@ -27,6 +27,10 @@ class ProjectHandler: @Route.post( r"/projects", description="Create a new project on the server", + status_codes={ + 201: "Project created", + 409: "Project already created" + }, output=PROJECT_OBJECT_SCHEMA, input=PROJECT_CREATE_SCHEMA) def create_project(request, response): @@ -37,6 +41,7 @@ class ProjectHandler: project_id=request.json.get("project_id"), temporary=request.json.get("temporary", False) ) + response.set_status(201) response.json(p) @classmethod @@ -115,6 +120,7 @@ class ProjectHandler: yield from project.close() for module in MODULES: yield from module.instance().project_closed(project.path) + pm.remove_project(project.id) response.set_status(204) @classmethod @@ -133,4 +139,5 @@ class ProjectHandler: pm = ProjectManager.instance() project = pm.get_project(request.match_info["project_id"]) yield from project.delete() + pm.remove_project(project.id) response.set_status(204) diff --git a/gns3server/modules/base_manager.py b/gns3server/modules/base_manager.py index 4bafe78c..41e63ced 100644 --- a/gns3server/modules/base_manager.py +++ b/gns3server/modules/base_manager.py @@ -165,6 +165,7 @@ class BaseManager: if hasattr(self, "get_legacy_vm_workdir_name"): # move old project VM files to a new location + log.info("Converting old project...") project_name = os.path.basename(project.path) project_files_dir = os.path.join(project.path, "{}-files".format(project_name)) module_path = os.path.join(project_files_dir, self.module_name.lower()) @@ -175,17 +176,21 @@ class BaseManager: except OSError as e: raise aiohttp.web.HTTPInternalServerError(text="Could not move VM working directory: {} to {} {}".format(vm_working_dir, new_vm_working_dir, e)) - if os.listdir(module_path) == []: - try: + try: + if not os.listdir(module_path): os.rmdir(module_path) - except OSError as e: - raise aiohttp.web.HTTPInternalServerError(text="Could not delete {}: {}".format(module_path, e)) + except OSError as e: + raise aiohttp.web.HTTPInternalServerError(text="Could not delete {}: {}".format(module_path, e)) + except FileNotFoundError as e: + log.warning(e) - if os.listdir(project_files_dir) == []: - try: + try: + if not os.listdir(project_files_dir): os.rmdir(project_files_dir) - except OSError as e: - raise aiohttp.web.HTTPInternalServerError(text="Could not delete {}: {}".format(project_files_dir, e)) + except OSError as e: + raise aiohttp.web.HTTPInternalServerError(text="Could not delete {}: {}".format(project_files_dir, e)) + except FileNotFoundError as e: + log.warning(e) if not vm_id: vm_id = str(uuid4()) diff --git a/gns3server/modules/dynamips/nodes/router.py b/gns3server/modules/dynamips/nodes/router.py index 7b456fed..3d53b16b 100644 --- a/gns3server/modules/dynamips/nodes/router.py +++ b/gns3server/modules/dynamips/nodes/router.py @@ -310,6 +310,7 @@ class Router(BaseVM): # router is already closed return + log.debug('Router "{name}" [{id}] is closing'.format(name=self._name, id=self._id)) if self._dynamips_id in self._dynamips_ids[self._project.id]: self._dynamips_ids[self._project.id].remove(self._dynamips_id) diff --git a/gns3server/modules/iou/iou_vm.py b/gns3server/modules/iou/iou_vm.py index e2f3ea80..fa6d4377 100644 --- a/gns3server/modules/iou/iou_vm.py +++ b/gns3server/modules/iou/iou_vm.py @@ -105,6 +105,8 @@ class IOUVM(BaseVM): @asyncio.coroutine def close(self): + log.debug('IOU "{name}" [{id}] is closing'.format(name=self._name, id=self._id)) + if self._console: self._manager.port_manager.release_tcp_port(self._console) self._console = None diff --git a/gns3server/modules/project.py b/gns3server/modules/project.py index 52aea2db..e2044ec1 100644 --- a/gns3server/modules/project.py +++ b/gns3server/modules/project.py @@ -70,7 +70,7 @@ class Project: raise aiohttp.web.HTTPInternalServerError(text="Could not create project directory: {}".format(e)) self.path = path - log.debug("Create project {id} in directory {path}".format(path=self._path, id=self._id)) + log.info("Project {id} with path '{path}' created".format(path=self._path, id=self._id)) def __json__(self): @@ -278,8 +278,11 @@ class Project: if cleanup and os.path.exists(self.path): try: yield from wait_run_in_executor(shutil.rmtree, self.path) + log.info("Project {id} with path '{path}' deleted".format(path=self._path, id=self._id)) except OSError as e: raise aiohttp.web.HTTPInternalServerError(text="Could not delete the project directory: {}".format(e)) + else: + log.info("Project {id} with path '{path}' closed".format(path=self._path, id=self._id)) port_manager = PortManager.instance() if port_manager.tcp_ports: diff --git a/gns3server/modules/project_manager.py b/gns3server/modules/project_manager.py index 87ed9288..82737b2c 100644 --- a/gns3server/modules/project_manager.py +++ b/gns3server/modules/project_manager.py @@ -60,13 +60,26 @@ class ProjectManager: raise aiohttp.web.HTTPNotFound(text="Project ID {} doesn't exist".format(project_id)) return self._projects[project_id] - def create_project(self, **kwargs): + def create_project(self, project_id=None, path=None, temporary=False): """ Create a project and keep a references to it in project manager. See documentation of Project for arguments """ - project = Project(**kwargs) + if project_id is not None and project_id in self._projects: + raise aiohttp.web.HTTPConflict(text="Project ID {} is already in use on this server".format(project_id)) + project = Project(project_id=project_id, path=path, temporary=temporary) self._projects[project.id] = project return project + + def remove_project(self, project_id): + """ + Removes a Project instance from the list of projects in use. + + :param project_id: Project identifier + """ + + if project_id not in self._projects: + raise aiohttp.web.HTTPNotFound(text="Project ID {} doesn't exist".format(project_id)) + del self._projects[project_id] diff --git a/gns3server/modules/qemu/qemu_vm.py b/gns3server/modules/qemu/qemu_vm.py index 4a07d587..5321aac6 100644 --- a/gns3server/modules/qemu/qemu_vm.py +++ b/gns3server/modules/qemu/qemu_vm.py @@ -647,6 +647,7 @@ class QemuVM(BaseVM): @asyncio.coroutine def close(self): + log.debug('QEMU VM "{name}" [{id}] is closing'.format(name=self._name, id=self._id)) yield from self.stop() if self._console: self._manager.port_manager.release_tcp_port(self._console) diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index a7728f94..a7b61fe6 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -297,6 +297,7 @@ class VirtualBoxVM(BaseVM): # VM is already closed return + log.debug("VirtualBox VM '{name}' [{id}] is closing".format(name=self.name, id=self.id)) if self._console: self._manager.port_manager.release_tcp_port(self._console) self._console = None diff --git a/gns3server/modules/vpcs/vpcs_vm.py b/gns3server/modules/vpcs/vpcs_vm.py index 6549ebbc..e72d1dde 100644 --- a/gns3server/modules/vpcs/vpcs_vm.py +++ b/gns3server/modules/vpcs/vpcs_vm.py @@ -72,6 +72,7 @@ class VPCSVM(BaseVM): @asyncio.coroutine def close(self): + log.debug("VPCS {name} [{id}] is closing".format(name=self._name, id=self._id)) if self._console: self._manager.port_manager.release_tcp_port(self._console) self._console = None @@ -297,10 +298,10 @@ class VPCSVM(BaseVM): port_number=port_number)) self._ethernet_adapter.add_nio(port_number, nio) - log.info("VPCS {name} {id}]: {nio} added to port {port_number}".format(name=self._name, - id=self.id, - nio=nio, - port_number=port_number)) + log.info("VPCS {name} [{id}]: {nio} added to port {port_number}".format(name=self._name, + id=self.id, + nio=nio, + port_number=port_number)) return nio def port_remove_nio_binding(self, port_number): diff --git a/tests/handlers/api/test_project.py b/tests/handlers/api/test_project.py index a82834a0..659c78a8 100644 --- a/tests/handlers/api/test_project.py +++ b/tests/handlers/api/test_project.py @@ -27,14 +27,14 @@ from tests.utils import asyncio_patch def test_create_project_with_path(server, tmpdir): with patch("gns3server.config.Config.get_section_config", return_value={"local": True}): response = server.post("/projects", {"path": str(tmpdir)}) - assert response.status == 200 + assert response.status == 201 assert response.json["path"] == str(tmpdir) def test_create_project_without_dir(server): query = {} response = server.post("/projects", query, example=True) - assert response.status == 200 + assert response.status == 201 assert response.json["project_id"] is not None assert response.json["temporary"] is False @@ -42,7 +42,7 @@ def test_create_project_without_dir(server): def test_create_temporary_project(server): query = {"temporary": True} response = server.post("/projects", query) - assert response.status == 200 + assert response.status == 201 assert response.json["project_id"] is not None assert response.json["temporary"] is True @@ -50,18 +50,18 @@ def test_create_temporary_project(server): def test_create_project_with_uuid(server): query = {"project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"} response = server.post("/projects", query) - assert response.status == 200 + assert response.status == 201 assert response.json["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e0f" def test_show_project(server): - query = {"project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f", "temporary": False} + query = {"project_id": "00010203-0405-0607-0809-0a0b0c0d0e02", "temporary": False} response = server.post("/projects", query) - assert response.status == 200 - response = server.get("/projects/00010203-0405-0607-0809-0a0b0c0d0e0f", example=True) + assert response.status == 201 + response = server.get("/projects/00010203-0405-0607-0809-0a0b0c0d0e02", example=True) assert len(response.json.keys()) == 4 assert len(response.json["location"]) > 0 - assert response.json["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e0f" + assert response.json["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e02" assert response.json["temporary"] is False @@ -73,7 +73,7 @@ def test_show_project_invalid_uuid(server): def test_update_temporary_project(server): query = {"temporary": True} response = server.post("/projects", query) - assert response.status == 200 + assert response.status == 201 query = {"temporary": False} response = server.put("/projects/{project_id}".format(project_id=response.json["project_id"]), query, example=True) assert response.status == 200 @@ -84,7 +84,7 @@ def test_update_path_project(server, tmpdir): with patch("gns3server.config.Config.get_section_config", return_value={"local": True}): response = server.post("/projects", {}) - assert response.status == 200 + assert response.status == 201 query = {"path": str(tmpdir)} response = server.put("/projects/{project_id}".format(project_id=response.json["project_id"]), query, example=True) assert response.status == 200 @@ -95,7 +95,7 @@ def test_update_path_project_non_local(server, tmpdir): with patch("gns3server.config.Config.get_section_config", return_value={"local": False}): response = server.post("/projects", {}) - assert response.status == 200 + assert response.status == 201 query = {"path": str(tmpdir)} response = server.put("/projects/{project_id}".format(project_id=response.json["project_id"]), query, example=True) assert response.status == 403 @@ -132,6 +132,6 @@ def test_close_project(server, project): assert mock.called -def test_close_project_invalid_uuid(server, project): +def test_close_project_invalid_uuid(server): response = server.post("/projects/{project_id}/close".format(project_id=uuid.uuid4())) assert response.status == 404