From 2055cdea5e3743655893d13e8ee14175ff47c99e Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Tue, 12 Apr 2016 18:02:36 +0200 Subject: [PATCH] /start /stop and /suspend API for VM via container --- gns3server/controller/vm.py | 21 +++++++ .../handlers/api/controller/vm_handler.py | 63 +++++++++++++++++++ tests/controller/test_vm.py | 24 +++++++ tests/handlers/api/controller/test_vm.py | 35 +++++++++++ tests/handlers/api/hypervisor/test_network.py | 2 +- 5 files changed, 144 insertions(+), 1 deletion(-) diff --git a/gns3server/controller/vm.py b/gns3server/controller/vm.py index 22d48f5b..f51f9214 100644 --- a/gns3server/controller/vm.py +++ b/gns3server/controller/vm.py @@ -110,6 +110,27 @@ class VM: else: self._properties[key] = value + @asyncio.coroutine + def start(self): + """ + Start a VM + """ + yield from self._hypervisor.post("/projects/{}/{}/vms/{}/start".format(self._project.id, self._vm_type, self._id)) + + @asyncio.coroutine + def stop(self): + """ + Stop a VM + """ + yield from self._hypervisor.post("/projects/{}/{}/vms/{}/stop".format(self._project.id, self._vm_type, self._id)) + + @asyncio.coroutine + def suspend(self): + """ + Suspend a VM + """ + yield from self._hypervisor.post("/projects/{}/{}/vms/{}/suspend".format(self._project.id, self._vm_type, self._id)) + @asyncio.coroutine def post(self, path, data={}): """ diff --git a/gns3server/handlers/api/controller/vm_handler.py b/gns3server/handlers/api/controller/vm_handler.py index 5ff6efb7..b7655af6 100644 --- a/gns3server/handlers/api/controller/vm_handler.py +++ b/gns3server/handlers/api/controller/vm_handler.py @@ -47,3 +47,66 @@ class VMHandler: vm = yield from project.addVM(hypervisor, request.json.pop("vm_id", None), **request.json) response.set_status(201) response.json(vm) + + @classmethod + @Route.post( + r"/projects/{project_id}/vms/{vm_id}/start", + parameters={ + "project_id": "UUID for the project", + "vm_id": "UUID for the VM" + }, + status_codes={ + 201: "Instance created", + 400: "Invalid request" + }, + description="Start a VM instance", + output=VM_OBJECT_SCHEMA) + def start(request, response): + + project = Controller.instance().getProject(request.match_info["project_id"]) + vm = project.getVM(request.match_info["vm_id"]) + yield from vm.start() + response.set_status(201) + response.json(vm) + + @classmethod + @Route.post( + r"/projects/{project_id}/vms/{vm_id}/stop", + parameters={ + "project_id": "UUID for the project", + "vm_id": "UUID for the VM" + }, + status_codes={ + 201: "Instance created", + 400: "Invalid request" + }, + description="Start a VM instance", + output=VM_OBJECT_SCHEMA) + def stop(request, response): + + project = Controller.instance().getProject(request.match_info["project_id"]) + vm = project.getVM(request.match_info["vm_id"]) + yield from vm.stop() + response.set_status(201) + response.json(vm) + + @classmethod + @Route.post( + r"/projects/{project_id}/vms/{vm_id}/suspend", + parameters={ + "project_id": "UUID for the project", + "vm_id": "UUID for the VM" + }, + status_codes={ + 201: "Instance created", + 400: "Invalid request" + }, + description="Start a VM instance", + output=VM_OBJECT_SCHEMA) + def suspend(request, response): + + project = Controller.instance().getProject(request.match_info["project_id"]) + vm = project.getVM(request.match_info["vm_id"]) + yield from vm.suspend() + response.set_status(201) + response.json(vm) diff --git a/tests/controller/test_vm.py b/tests/controller/test_vm.py index 0759a7e5..bc649f43 100644 --- a/tests/controller/test_vm.py +++ b/tests/controller/test_vm.py @@ -86,6 +86,30 @@ def test_create(vm, hypervisor, project, async_run): assert vm._properties == {"startup_script": "echo test"} +def test_start(vm, hypervisor, project, async_run): + + hypervisor.post = AsyncioMagicMock() + + async_run(vm.start()) + hypervisor.post.assert_called_with("/projects/{}/vpcs/vms/{}/start".format(vm.project.id, vm.id)) + + +def test_stop(vm, hypervisor, project, async_run): + + hypervisor.post = AsyncioMagicMock() + + async_run(vm.stop()) + hypervisor.post.assert_called_with("/projects/{}/vpcs/vms/{}/stop".format(vm.project.id, vm.id)) + + +def test_suspend(vm, hypervisor, project, async_run): + + hypervisor.post = AsyncioMagicMock() + + async_run(vm.suspend()) + hypervisor.post.assert_called_with("/projects/{}/vpcs/vms/{}/suspend".format(vm.project.id, vm.id)) + + def test_create_without_console(vm, hypervisor, project, async_run): """ None properties should be send. Because it can mean the emulator doesn"t support it diff --git a/tests/handlers/api/controller/test_vm.py b/tests/handlers/api/controller/test_vm.py index f181c14b..16bccb94 100644 --- a/tests/handlers/api/controller/test_vm.py +++ b/tests/handlers/api/controller/test_vm.py @@ -31,6 +31,7 @@ from tests.utils import asyncio_patch, AsyncioMagicMock from gns3server.handlers.api.controller.project_handler import ProjectHandler from gns3server.controller import Controller +from gns3server.controller.vm import VM @pytest.fixture @@ -46,6 +47,13 @@ def project(http_controller, async_run): return async_run(Controller.instance().addProject()) +@pytest.fixture +def vm(project, hypervisor, async_run): + vm = VM(project, hypervisor, name="test", vm_type="vpcs") + project._vms[vm.id] = vm + return vm + + def test_create_vm(http_controller, tmpdir, project, hypervisor): response = MagicMock() response.json = {"console": 2048} @@ -62,3 +70,30 @@ def test_create_vm(http_controller, tmpdir, project, hypervisor): assert response.status == 201 assert response.json["name"] == "test" assert "name" not in response.json["properties"] + + +def test_start_vm(http_controller, tmpdir, project, hypervisor, vm): + response = MagicMock() + hypervisor.post = AsyncioMagicMock() + + response = http_controller.post("/projects/{}/vms/{}/start".format(project.id, vm.id), example=True) + assert response.status == 201 + assert response.json["name"] == vm.name + + +def test_stop_vm(http_controller, tmpdir, project, hypervisor, vm): + response = MagicMock() + hypervisor.post = AsyncioMagicMock() + + response = http_controller.post("/projects/{}/vms/{}/stop".format(project.id, vm.id), example=True) + assert response.status == 201 + assert response.json["name"] == vm.name + + +def test_suspend_vm(http_controller, tmpdir, project, hypervisor, vm): + response = MagicMock() + hypervisor.post = AsyncioMagicMock() + + response = http_controller.post("/projects/{}/vms/{}/suspend".format(project.id, vm.id), example=True) + assert response.status == 201 + assert response.json["name"] == vm.name diff --git a/tests/handlers/api/hypervisor/test_network.py b/tests/handlers/api/hypervisor/test_network.py index 7314239d..ccaf7958 100644 --- a/tests/handlers/api/hypervisor/test_network.py +++ b/tests/handlers/api/hypervisor/test_network.py @@ -22,7 +22,7 @@ import pytest def test_udp_allocation(http_hypervisor, project): response = http_hypervisor.post('/projects/{}/ports/udp'.format(project.id), {}, example=True) assert response.status == 201 - assert response.json == {'udp_port': 10000} + assert response.json['udp_port'] is not None # Netfifaces is not available on Travis