From 368d1ff70b214e04ad85ecc3b8e421cbcce2abac Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 21 Jan 2015 21:46:16 +0100 Subject: [PATCH] Update VPCS instance --- docs/api/examples/get_vpcsuuid.txt | 2 +- docs/api/examples/post_vpcs.txt | 2 +- gns3server/handlers/vpcs_handler.py | 22 ++++++++++++++++++ gns3server/modules/base_vm.py | 4 ++++ gns3server/modules/vpcs/vpcs_vm.py | 36 +++++++++++++++-------------- gns3server/schemas/vpcs.py | 27 ++++++++++++++++++++++ tests/api/base.py | 6 +++-- tests/api/test_project.py | 2 +- tests/api/test_version.py | 2 +- tests/api/test_virtualbox.py | 2 +- tests/api/test_vpcs.py | 23 ++++++++++++++---- tests/modules/vpcs/test_vpcs_vm.py | 27 +++++++++++++++++++++- tests/utils.py | 18 +++++++++++++++ 13 files changed, 144 insertions(+), 29 deletions(-) diff --git a/docs/api/examples/get_vpcsuuid.txt b/docs/api/examples/get_vpcsuuid.txt index 797f00bd..1abe5fd9 100644 --- a/docs/api/examples/get_vpcsuuid.txt +++ b/docs/api/examples/get_vpcsuuid.txt @@ -18,5 +18,5 @@ X-ROUTE: /vpcs/{uuid} "project_uuid": "a1e920ca-338a-4e9f-b363-aa607b09dd80", "script_file": null, "startup_script": null, - "uuid": "40f76457-de2b-4399-8853-a35393a72a2d" + "uuid": "f8155d67-c0bf-4229-be4c-97edaaae7b0b" } diff --git a/docs/api/examples/post_vpcs.txt b/docs/api/examples/post_vpcs.txt index 1977fc1d..26ee627f 100644 --- a/docs/api/examples/post_vpcs.txt +++ b/docs/api/examples/post_vpcs.txt @@ -21,5 +21,5 @@ X-ROUTE: /vpcs "project_uuid": "a1e920ca-338a-4e9f-b363-aa607b09dd80", "script_file": null, "startup_script": null, - "uuid": "a022aa0d-acab-4554-b2a6-6e6f51c9d65e" + "uuid": "5a9aac64-5b62-41bd-955a-fcef90a2fac5" } diff --git a/gns3server/handlers/vpcs_handler.py b/gns3server/handlers/vpcs_handler.py index df6788f7..633a668e 100644 --- a/gns3server/handlers/vpcs_handler.py +++ b/gns3server/handlers/vpcs_handler.py @@ -17,6 +17,7 @@ from ..web.route import Route from ..schemas.vpcs import VPCS_CREATE_SCHEMA +from ..schemas.vpcs import VPCS_UPDATE_SCHEMA from ..schemas.vpcs import VPCS_OBJECT_SCHEMA from ..schemas.vpcs import VPCS_NIO_SCHEMA from ..modules.vpcs import VPCS @@ -67,6 +68,27 @@ class VPCSHandler: vm = vpcs_manager.get_vm(request.match_info["uuid"]) response.json(vm) + @classmethod + @Route.put( + r"/vpcs/{uuid}", + status_codes={ + 200: "VPCS instance updated", + 409: "Conflict" + }, + description="Update a VPCS instance", + input=VPCS_UPDATE_SCHEMA, + output=VPCS_OBJECT_SCHEMA) + def update(request, response): + + vpcs_manager = VPCS.instance() + vm = vpcs_manager.get_vm(request.match_info["uuid"]) + vm.name = request.json.get("name", vm.name) + vm.console = request.json.get("console", vm.console) + vm.script_file = request.json.get("script_file", vm.script_file) + vm.startup_script = request.json.get("startup_script", vm.startup_script) + + response.json(vm) + @classmethod @Route.post( r"/vpcs/{uuid}/start", diff --git a/gns3server/modules/base_vm.py b/gns3server/modules/base_vm.py index e1e19d1d..c4531fba 100644 --- a/gns3server/modules/base_vm.py +++ b/gns3server/modules/base_vm.py @@ -59,6 +59,10 @@ class BaseVM: :param new_name: name """ + log.info("{module} {name} [{uuid}]: renamed to {new_name}".format(module=self.module_name, + name=self._name, + uuid=self.uuid, + new_name=new_name)) self._name = new_name @property diff --git a/gns3server/modules/vpcs/vpcs_vm.py b/gns3server/modules/vpcs/vpcs_vm.py index cabc6df4..df7dc5fc 100644 --- a/gns3server/modules/vpcs/vpcs_vm.py +++ b/gns3server/modules/vpcs/vpcs_vm.py @@ -123,7 +123,17 @@ class VPCSVM(BaseVM): return self._console - # FIXME: correct way to subclass a property? + @console.setter + def console(self, console): + """ + Change console port + + :params console: Console port (integer) + """ + if self._console: + self._manager.port_manager.release_console_port(self._console) + self._console = self._manager.port_manager.reserve_console_port(console) + @BaseVM.name.setter def name(self, new_name): """ @@ -133,22 +143,11 @@ class VPCSVM(BaseVM): """ if self._script_file: - # update the startup.vpc - config_path = os.path.join(self.working_dir, "startup.vpc") - if os.path.isfile(config_path): - try: - with open(config_path, "r+", errors="replace") as f: - old_config = f.read() - new_config = old_config.replace(self._name, new_name) - f.seek(0) - f.write(new_config) - except OSError as e: - raise VPCSError("Could not amend the configuration {}: {}".format(config_path, e)) + content = self.startup_script + content = content.replace(self._name, new_name) + self.startup_script = content - log.info("VPCS {name} [{uuid}]: renamed to {new_name}".format(name=self._name, - uuid=self.uuid, - new_name=new_name)) - BaseVM.name = new_name + super(VPCSVM, VPCSVM).name.__set__(self, new_name) @property def startup_script(self): @@ -173,7 +172,10 @@ class VPCSVM(BaseVM): self._script_file = os.path.join(self.working_dir, 'startup.vpcs') try: with open(self._script_file, '+w') as f: - f.write(startup_script) + if startup_script is None: + f.write('') + else: + f.write(startup_script) except OSError as e: raise VPCSError("Can't write VPCS startup file '{}'".format(self._script_file)) diff --git a/gns3server/schemas/vpcs.py b/gns3server/schemas/vpcs.py index a0190ee6..437651bb 100644 --- a/gns3server/schemas/vpcs.py +++ b/gns3server/schemas/vpcs.py @@ -63,6 +63,33 @@ VPCS_CREATE_SCHEMA = { "required": ["name", "project_uuid"] } +VPCS_UPDATE_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Request validation to update a VPCS instance", + "type": "object", + "properties": { + "name": { + "description": "VPCS device name", + "type": ["string", "null"], + "minLength": 1, + }, + "console": { + "description": "console TCP port", + "minimum": 1, + "maximum": 65535, + "type": ["integer", "null"] + }, + "script_file": { + "description": "VPCS startup script", + "type": ["string", "null"] + }, + "startup_script": { + "description": "Content of the VPCS startup script", + "type": ["string", "null"] + }, + }, + "additionalProperties": False, +} VPCS_NIO_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", diff --git a/tests/api/base.py b/tests/api/base.py index 6de05b45..4664f41a 100644 --- a/tests/api/base.py +++ b/tests/api/base.py @@ -45,6 +45,9 @@ class Query: def post(self, path, body={}, **kwargs): return self._fetch("POST", path, body, **kwargs) + def put(self, path, body={}, **kwargs): + return self._fetch("PUT", path, body, **kwargs) + def get(self, path, **kwargs): return self._fetch("GET", path, **kwargs) @@ -147,11 +150,10 @@ def loop(request): @pytest.fixture(scope="session") -def server(request, loop): +def server(request, loop, port_manager): port = _get_unused_port() host = "localhost" app = web.Application() - port_manager = PortManager("127.0.0.1", False) for method, route, handler in Route.get_routes(): app.router.add_route(method, route, handler) for module in MODULES: diff --git a/tests/api/test_project.py b/tests/api/test_project.py index cf2297c0..b7ae8b55 100644 --- a/tests/api/test_project.py +++ b/tests/api/test_project.py @@ -20,7 +20,7 @@ This test suite check /project endpoint """ -from tests.utils import asyncio_patch +from tests.utils import asyncio_patch, port_manager from tests.api.base import server, loop from gns3server.version import __version__ diff --git a/tests/api/test_version.py b/tests/api/test_version.py index 110f7b1a..5b479006 100644 --- a/tests/api/test_version.py +++ b/tests/api/test_version.py @@ -20,7 +20,7 @@ This test suite check /version endpoint It's also used for unittest the HTTP implementation. """ -from tests.utils import asyncio_patch +from tests.utils import asyncio_patch, port_manager from tests.api.base import server, loop from gns3server.version import __version__ diff --git a/tests/api/test_virtualbox.py b/tests/api/test_virtualbox.py index c7b8856c..3d9e56c3 100644 --- a/tests/api/test_virtualbox.py +++ b/tests/api/test_virtualbox.py @@ -16,7 +16,7 @@ # along with this program. If not, see . from tests.api.base import server, loop, project -from tests.utils import asyncio_patch +from tests.utils import asyncio_patch, port_manager from gns3server.modules.virtualbox.virtualbox_vm import VirtualBoxVM diff --git a/tests/api/test_vpcs.py b/tests/api/test_vpcs.py index 0b24753f..56b5bc56 100644 --- a/tests/api/test_vpcs.py +++ b/tests/api/test_vpcs.py @@ -18,7 +18,7 @@ import pytest import os from tests.api.base import server, loop, project -from tests.utils import asyncio_patch +from tests.utils import asyncio_patch, free_console_port, port_manager from unittest.mock import patch, Mock from gns3server.modules.vpcs.vpcs_vm import VPCSVM @@ -68,13 +68,13 @@ def test_vpcs_create_startup_script(server, project): assert response.json["startup_script"] == "ip 192.168.1.2\necho TEST" -def test_vpcs_create_port(server, project): - response = server.post("/vpcs", {"name": "PC TEST 1", "project_uuid": project.uuid, "console": 4242}) +def test_vpcs_create_port(server, project, free_console_port): + response = server.post("/vpcs", {"name": "PC TEST 1", "project_uuid": project.uuid, "console": free_console_port}) assert response.status == 200 assert response.route == "/vpcs" assert response.json["name"] == "PC TEST 1" assert response.json["project_uuid"] == project.uuid - assert response.json["console"] == 4242 + assert response.json["console"] == free_console_port def test_vpcs_nio_create_udp(server, vm): @@ -119,3 +119,18 @@ def test_vpcs_stop(server, vm): response = server.post("/vpcs/{}/stop".format(vm["uuid"])) assert mock.called assert response.status == 200 + + +def test_vpcs_update(server, vm, tmpdir, free_console_port): + path = os.path.join(str(tmpdir), 'startup2.vpcs') + with open(path, 'w+') as f: + f.write(path) + response = server.put("/vpcs/{}".format(vm["uuid"]), {"name": "test", + "console": free_console_port, + "script_file": path, + "startup_script": "ip 192.168.1.1"}) + assert response.status == 200 + assert response.json["name"] == "test" + assert response.json["console"] == free_console_port + assert response.json["script_file"] == path + assert response.json["startup_script"] == "ip 192.168.1.1" diff --git a/tests/modules/vpcs/test_vpcs_vm.py b/tests/modules/vpcs/test_vpcs_vm.py index a26b99b9..1cd27646 100644 --- a/tests/modules/vpcs/test_vpcs_vm.py +++ b/tests/modules/vpcs/test_vpcs_vm.py @@ -18,7 +18,7 @@ import pytest import asyncio import os -from tests.utils import asyncio_patch +from tests.utils import asyncio_patch, port_manager, free_console_port # TODO: Move loop to util from tests.api.base import loop, project @@ -136,3 +136,28 @@ def test_get_startup_script(vm): content = "echo GNS3 VPCS\nip 192.168.1.2\n" vm.startup_script = content assert vm.startup_script == content + + +def test_change_console_port(vm, free_console_port): + vm.console = free_console_port + vm.console = free_console_port + 1 + assert vm.console == free_console_port + PortManager.instance().reserve_console_port(free_console_port + 1) + + +def test_change_name(vm, tmpdir): + path = os.path.join(str(tmpdir), 'startup.vpcs') + vm.name = "world" + with open(path, 'w+') as f: + f.write("name world") + vm.script_file = path + vm.name = "hello" + assert vm.name == "hello" + with open(path) as f: + assert f.read() == "name hello" + + +def test_change_script_file(vm, tmpdir): + path = os.path.join(str(tmpdir), 'startup2.vpcs') + vm.script_file = path + assert vm.script_file == path diff --git a/tests/utils.py b/tests/utils.py index bc8dc5dc..55069f5e 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -16,7 +16,10 @@ # along with this program. If not, see . import asyncio +import pytest from unittest.mock import patch +from gns3server.modules.project_manager import ProjectManager +from gns3server.modules.port_manager import PortManager class _asyncio_patch: @@ -54,3 +57,18 @@ class _asyncio_patch: def asyncio_patch(function, *args, **kwargs): return _asyncio_patch(function, *args, **kwargs) + + +@pytest.fixture(scope="session") +def port_manager(): + return PortManager("127.0.0.1", False) + + +@pytest.fixture(scope="function") +def free_console_port(request, port_manager): + # In case of already use ports we will raise an exception + port = port_manager.get_free_console_port() + # We release the port immediately in order to allow + # the test do whatever the test want + port_manager.release_console_port(port) + return port