From d7ed37ce0200be9c5f0a52b5bf2b86704fb536e4 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Mon, 18 Apr 2016 17:36:38 +0200 Subject: [PATCH] Update and delete VM on controller --- gns3server/controller/compute.py | 5 ++ gns3server/controller/vm.py | 72 ++++++++++++++++--- .../handlers/api/controller/vm_handler.py | 41 +++++++++++ tests/controller/test_vm.py | 23 ++++++ tests/handlers/api/controller/test_vm.py | 26 +++++++ 5 files changed, 156 insertions(+), 11 deletions(-) diff --git a/gns3server/controller/compute.py b/gns3server/controller/compute.py index fc39fd35..e35d0d58 100644 --- a/gns3server/controller/compute.py +++ b/gns3server/controller/compute.py @@ -203,6 +203,11 @@ class Compute: response = yield from self.httpQuery("POST", path, data) return response + @asyncio.coroutine + def put(self, path, data={}): + response = yield from self.httpQuery("PUT", path, data) + return response + @asyncio.coroutine def delete(self, path): return (yield from self.httpQuery("DELETE", path)) diff --git a/gns3server/controller/vm.py b/gns3server/controller/vm.py index 79b4b33c..c16254d3 100644 --- a/gns3server/controller/vm.py +++ b/gns3server/controller/vm.py @@ -89,6 +89,51 @@ class VM: @asyncio.coroutine def create(self): + """ + Create the VM on the compute Node + """ + data = self._vm_data() + response = yield from self._compute.post("/projects/{}/{}/vms".format(self._project.id, self._vm_type), data=data) + self._parse_vm_response(response) + + @asyncio.coroutine + def update(self, name=None, console=None, console_type="telnet", properties={}): + """ + Update the VM on the compute Node + + :param vm_id: UUID of the vm. Integer id + :param vm_type: Type of emulator + :param name: Name of the vm + :param console: TCP port of the console + :param console_type: Type of the console (telnet, vnc, serial..) + :param properties: Emulator specific properties of the VM + + """ + self._name = name + self._console = console + self._console_type = console_type + self._properties = properties + + data = self._vm_data() + response = yield from self._compute.put("/projects/{}/{}/vms".format(self._project.id, self._vm_type), data=data) + self._parse_vm_response(response) + + def _parse_vm_response(self, response): + """ + Update the object with the remote VM object + """ + for key, value in response.json.items(): + if key == "console": + self._console = value + elif key in ["console_type", "name", "vm_id"]: + pass + else: + self._properties[key] = value + + def _vm_data(self): + """ + Prepare VM data to send to the remote controller + """ data = copy.copy(self._properties) data["vm_id"] = self._id data["name"] = self._name @@ -99,16 +144,11 @@ class VM: for key in list(data.keys()): if data[key] is None: del data[key] + return data - response = yield from self._compute.post("/projects/{}/{}/vms".format(self._project.id, self._vm_type), data=data) - - for key, value in response.json.items(): - if key == "console": - self._console = value - elif key in ["console_type", "name", "vm_id"]: - pass - else: - self._properties[key] = value + @asyncio.coroutine + def destroy(self): + yield from self.delete() @asyncio.coroutine def start(self): @@ -136,7 +176,7 @@ class VM: """ Suspend a VM """ - yield from self.post("/suspend") + yield from self.post("/reload") @asyncio.coroutine def post(self, path, data=None): @@ -149,7 +189,17 @@ class VM: return (yield from self._compute.post("/projects/{}/{}/vms/{}{}".format(self._project.id, self._vm_type, self._id, path))) @asyncio.coroutine - def delete(self, path): + def put(self, path, data=None): + """ + HTTP post on the VM + """ + if data: + return (yield from self._compute.put("/projects/{}/{}/vms/{}{}".format(self._project.id, self._vm_type, self._id, path), data=data)) + else: + return (yield from self._compute.put("/projects/{}/{}/vms/{}{}".format(self._project.id, self._vm_type, self._id, path))) + + @asyncio.coroutine + def delete(self, path=None): """ HTTP post on the VM """ diff --git a/gns3server/handlers/api/controller/vm_handler.py b/gns3server/handlers/api/controller/vm_handler.py index 5bb4fac5..c8cdd885 100644 --- a/gns3server/handlers/api/controller/vm_handler.py +++ b/gns3server/handlers/api/controller/vm_handler.py @@ -48,6 +48,29 @@ class VMHandler: response.set_status(201) response.json(vm) + @classmethod + @Route.put( + r"/projects/{project_id}/vms/{vm_id}", + status_codes={ + 201: "Instance created", + 400: "Invalid request" + }, + description="Update a VM instance", + input=VM_OBJECT_SCHEMA, + output=VM_OBJECT_SCHEMA) + def update(request, response): + project = Controller.instance().getProject(request.match_info["project_id"]) + vm = project.getVM(request.match_info["vm_id"]) + + # Ignore this, because we use it only in create + request.json.pop("vm_id", None) + request.json.pop("vm_type", None) + request.json.pop("compute_id", None) + + yield from vm.update(**request.json) + response.set_status(201) + response.json(vm) + @classmethod @Route.post( r"/projects/{project_id}/vms/{vm_id}/start", @@ -131,3 +154,21 @@ class VMHandler: yield from vm.reload() response.set_status(201) response.json(vm) + + @classmethod + @Route.delete( + r"/projects/{project_id}/vms/{vm_id}", + parameters={ + "project_id": "UUID for the project", + "vm_id": "UUID for the VM" + }, + status_codes={ + 201: "Instance deleted", + 400: "Invalid request" + }, + description="Delete a VM instance") + def delete(request, response): + project = Controller.instance().getProject(request.match_info["project_id"]) + vm = project.getVM(request.match_info["vm_id"]) + yield from vm.destroy() + response.set_status(201) diff --git a/tests/controller/test_vm.py b/tests/controller/test_vm.py index 645cb010..2bf39865 100644 --- a/tests/controller/test_vm.py +++ b/tests/controller/test_vm.py @@ -86,6 +86,24 @@ def test_create(vm, compute, project, async_run): assert vm._properties == {"startup_script": "echo test"} +def test_update(vm, compute, project, async_run): + response = MagicMock() + response.json = {"console": 2048} + compute.put = AsyncioMagicMock(return_value=response) + + async_run(vm.update(console=2048, console_type="vnc", properties={"startup_script" :"echo test"}, name="demo")) + data = { + "console": 2048, + "console_type": "vnc", + "vm_id": vm.id, + "startup_script": "echo test", + "name": "demo" + } + compute.put.assert_called_with("/projects/{}/vpcs/vms".format(vm.project.id), data=data) + assert vm._console == 2048 + assert vm._properties == {"startup_script": "echo test"} + + def test_start(vm, compute, project, async_run): compute.post = AsyncioMagicMock() @@ -139,6 +157,11 @@ def test_create_without_console(vm, compute, project, async_run): assert vm._properties == {"test_value": "success", "startup_script": "echo test"} +def test_delete(vm, compute, async_run): + async_run(vm.destroy()) + compute.delete.assert_called_with("/projects/{}/vpcs/vms/{}".format(vm.project.id, vm.id)) + + def test_post(vm, compute, async_run): async_run(vm.post("/test", {"a": "b"})) compute.post.assert_called_with("/projects/{}/vpcs/vms/{}/test".format(vm.project.id, vm.id), data={"a": "b"}) diff --git a/tests/handlers/api/controller/test_vm.py b/tests/handlers/api/controller/test_vm.py index 3dd314a8..fd9ffc18 100644 --- a/tests/handlers/api/controller/test_vm.py +++ b/tests/handlers/api/controller/test_vm.py @@ -72,6 +72,24 @@ def test_create_vm(http_controller, tmpdir, project, compute): assert "name" not in response.json["properties"] +def test_update_vm(http_controller, tmpdir, project, compute, vm): + response = MagicMock() + response.json = {"console": 2048} + compute.put = AsyncioMagicMock(return_value=response) + + response = http_controller.put("/projects/{}/vms/{}".format(project.id, vm.id), { + "name": "test", + "vm_type": "vpcs", + "compute_id": "example.com", + "properties": { + "startup_script": "echo test" + } + }, example=True) + assert response.status == 201 + assert response.json["name"] == "test" + assert "name" not in response.json["properties"] + + def test_start_vm(http_controller, tmpdir, project, compute, vm): response = MagicMock() compute.post = AsyncioMagicMock() @@ -106,3 +124,11 @@ def test_reload_vm(http_controller, tmpdir, project, compute, vm): response = http_controller.post("/projects/{}/vms/{}/reload".format(project.id, vm.id), example=True) assert response.status == 201 assert response.json["name"] == vm.name + + +def test_delete_vm(http_controller, tmpdir, project, compute, vm): + response = MagicMock() + compute.post = AsyncioMagicMock() + + response = http_controller.delete("/projects/{}/vms/{}".format(project.id, vm.id), example=True) + assert response.status == 201