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