diff --git a/docs/notifications.rst b/docs/notifications.rst index 4739bb82..0e1ed6fd 100644 --- a/docs/notifications.rst +++ b/docs/notifications.rst @@ -126,6 +126,14 @@ Drawing has been deleted. .. literalinclude:: api/notifications/drawing.deleted.json +project.updated +--------------- + +Project has been updated. + +.. literalinclude:: api/notifications/project.updated.json + + project.closed --------------- diff --git a/gns3server/compute/base_manager.py b/gns3server/compute/base_manager.py index a3a708e0..ba9d97b0 100644 --- a/gns3server/compute/base_manager.py +++ b/gns3server/compute/base_manager.py @@ -278,16 +278,6 @@ class BaseManager: if node.id in self._nodes: del self._nodes[node.id] - @asyncio.coroutine - def project_moved(self, project): - """ - Called when a project is moved - - :param project: project instance - """ - - pass - @asyncio.coroutine def delete_node(self, node_id): """ diff --git a/gns3server/compute/dynamips/__init__.py b/gns3server/compute/dynamips/__init__.py index 5ffc01c2..c8ffb9d2 100644 --- a/gns3server/compute/dynamips/__init__.py +++ b/gns3server/compute/dynamips/__init__.py @@ -223,22 +223,6 @@ class Dynamips(BaseManager): if project.id in self._dynamips_ids: del self._dynamips_ids[project.id] - @asyncio.coroutine - def project_moved(self, project): - """ - Called when a project is moved. - - :param project: Project instance - """ - - for node in self._nodes.values(): - if node.project.id == project.id: - yield from node.hypervisor.set_working_dir(project.module_working_directory(self.module_name.lower())) - - for device in self._devices.values(): - if device.project.id == project.id: - yield from device.hypervisor.set_working_dir(project.module_working_directory(self.module_name.lower())) - @property def dynamips_path(self): """ diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 2cf5b519..65b3b2b1 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -98,6 +98,24 @@ class Project: if not os.path.exists(self._topology_file()): self.dump() + @asyncio.coroutine + def update(self, **kwargs): + """ + Update the node on the compute server + + :param kwargs: Node properties + """ + + old_json = self.__json__() + + for prop in kwargs: + setattr(self, prop, kwargs[prop]) + + # We send notif only if object has changed + if old_json != self.__json__(): + self.controller.notification.emit("project.updated", self.__json__()) + self.dump() + def reset(self): """ Called when open/close a project. Cleanup internal stuff @@ -131,6 +149,10 @@ class Project: def name(self): return self._name + @name.setter + def name(self, val): + self._name = val + @property def id(self): return self._id @@ -446,7 +468,7 @@ class Project: self._cleanPictures() self._status = "closed" if not ignore_notification: - self.controller.notification.emit("project.closed", self.__json__()) + self.controller.notification.emit("project.closed", self.__json__()) def _cleanPictures(self): """ diff --git a/gns3server/handlers/api/compute/project_handler.py b/gns3server/handlers/api/compute/project_handler.py index 78e1f6b6..77a9e8d6 100644 --- a/gns3server/handlers/api/compute/project_handler.py +++ b/gns3server/handlers/api/compute/project_handler.py @@ -95,32 +95,6 @@ class ProjectHandler: project = pm.get_project(request.match_info["project_id"]) response.json(project) - @Route.put( - r"/projects/{project_id}", - description="Update a project", - parameters={ - "project_id": "Project UUID", - }, - status_codes={ - 200: "Project updated", - 403: "Forbidden to update this project", - 404: "The project doesn't exist" - }, - output=PROJECT_OBJECT_SCHEMA, - input=PROJECT_UPDATE_SCHEMA) - def update(request, response): - - pm = ProjectManager.instance() - project = pm.get_project(request.match_info["project_id"]) - project.name = request.json.get("name", project.name) - project_path = request.json.get("path", project.path) - if project_path != project.path: - old_path = project.path - project.path = project_path - for module in MODULES: - yield from module.instance().project_moved(project) - response.json(project) - @Route.post( r"/projects/{project_id}/close", description="Close a project", diff --git a/gns3server/handlers/api/controller/project_handler.py b/gns3server/handlers/api/controller/project_handler.py index 0287e434..e3ea4b91 100644 --- a/gns3server/handlers/api/controller/project_handler.py +++ b/gns3server/handlers/api/controller/project_handler.py @@ -30,6 +30,7 @@ from gns3server.config import Config from gns3server.schemas.project import ( PROJECT_OBJECT_SCHEMA, + PROJECT_UPDATE_SCHEMA, PROJECT_LOAD_SCHEMA, PROJECT_CREATE_SCHEMA ) @@ -83,6 +84,26 @@ class ProjectHandler: project = controller.get_project(request.match_info["project_id"]) response.json(project) + @Route.put( + r"/projects/{project_id}", + status_codes={ + 200: "Node updated", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Update a project instance", + input=PROJECT_UPDATE_SCHEMA, + output=PROJECT_OBJECT_SCHEMA) + def update(request, response): + project = Controller.instance().get_project(request.match_info["project_id"]) + + # Ignore these because we only use them when creating a project + request.json.pop("project_id", None) + + yield from project.update(**request.json) + response.set_status(200) + response.json(project) + @Route.post( r"/projects/{project_id}/close", description="Close a project", diff --git a/tests/controller/test_project.py b/tests/controller/test_project.py index 3ec1b187..771f8ac2 100644 --- a/tests/controller/test_project.py +++ b/tests/controller/test_project.py @@ -62,6 +62,16 @@ def test_json(tmpdir): assert p.__json__() == {"name": "Test", "project_id": p.id, "path": p.path, "status": "opened", "filename": "Test.gns3"} +def test_update(controller, async_run): + project = Project(controller=controller, name="Hello") + controller._notification = MagicMock() + + assert project.name == "Hello" + async_run(project.update(name="World")) + assert project.name == "World" + controller.notification.emit.assert_any_call("project.updated", project.__json__()) + + def test_path(tmpdir): directory = Config.instance().get_section_config("Server").get("projects_path") diff --git a/tests/controller/test_snapshot.py b/tests/controller/test_snapshot.py index f2bc88db..7f999ae7 100644 --- a/tests/controller/test_snapshot.py +++ b/tests/controller/test_snapshot.py @@ -97,7 +97,6 @@ def test_restore(project, controller, async_run): # project.closed notification should not be send when restoring snapshots assert "project.closed" not in [c[0][0] for c in controller.notification.emit.call_args_list] - project = controller.get_project(project.id) assert not os.path.exists(test_file) assert len(project.nodes) == 1 diff --git a/tests/handlers/api/controller/test_project.py b/tests/handlers/api/controller/test_project.py index 80427908..23275c5a 100644 --- a/tests/handlers/api/controller/test_project.py +++ b/tests/handlers/api/controller/test_project.py @@ -67,6 +67,18 @@ def test_create_project_with_uuid(http_controller): assert response.json["name"] == "test" +def test_update_project(http_controller): + query = {"name": "test", "project_id": "10010203-0405-0607-0809-0a0b0c0d0e0f"} + response = http_controller.post("/projects", query) + assert response.status == 201 + assert response.json["project_id"] == "10010203-0405-0607-0809-0a0b0c0d0e0f" + assert response.json["name"] == "test" + query = {"name": "test2"} + response = http_controller.put("/projects/10010203-0405-0607-0809-0a0b0c0d0e0f", query, example=True) + assert response.status == 200 + assert response.json["name"] == "test2" + + def test_list_projects(http_controller, tmpdir): http_controller.post("/projects", {"name": "test", "path": str(tmpdir), "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"}) response = http_controller.get("/projects", example=True)