mirror of
https://github.com/GNS3/gns3-server
synced 2025-01-16 11:00:58 +00:00
Update VPCS instance
This commit is contained in:
parent
7abb426d04
commit
368d1ff70b
@ -18,5 +18,5 @@ X-ROUTE: /vpcs/{uuid}
|
|||||||
"project_uuid": "a1e920ca-338a-4e9f-b363-aa607b09dd80",
|
"project_uuid": "a1e920ca-338a-4e9f-b363-aa607b09dd80",
|
||||||
"script_file": null,
|
"script_file": null,
|
||||||
"startup_script": null,
|
"startup_script": null,
|
||||||
"uuid": "40f76457-de2b-4399-8853-a35393a72a2d"
|
"uuid": "f8155d67-c0bf-4229-be4c-97edaaae7b0b"
|
||||||
}
|
}
|
||||||
|
@ -21,5 +21,5 @@ X-ROUTE: /vpcs
|
|||||||
"project_uuid": "a1e920ca-338a-4e9f-b363-aa607b09dd80",
|
"project_uuid": "a1e920ca-338a-4e9f-b363-aa607b09dd80",
|
||||||
"script_file": null,
|
"script_file": null,
|
||||||
"startup_script": null,
|
"startup_script": null,
|
||||||
"uuid": "a022aa0d-acab-4554-b2a6-6e6f51c9d65e"
|
"uuid": "5a9aac64-5b62-41bd-955a-fcef90a2fac5"
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
|
|
||||||
from ..web.route import Route
|
from ..web.route import Route
|
||||||
from ..schemas.vpcs import VPCS_CREATE_SCHEMA
|
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_OBJECT_SCHEMA
|
||||||
from ..schemas.vpcs import VPCS_NIO_SCHEMA
|
from ..schemas.vpcs import VPCS_NIO_SCHEMA
|
||||||
from ..modules.vpcs import VPCS
|
from ..modules.vpcs import VPCS
|
||||||
@ -67,6 +68,27 @@ class VPCSHandler:
|
|||||||
vm = vpcs_manager.get_vm(request.match_info["uuid"])
|
vm = vpcs_manager.get_vm(request.match_info["uuid"])
|
||||||
response.json(vm)
|
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
|
@classmethod
|
||||||
@Route.post(
|
@Route.post(
|
||||||
r"/vpcs/{uuid}/start",
|
r"/vpcs/{uuid}/start",
|
||||||
|
@ -59,6 +59,10 @@ class BaseVM:
|
|||||||
:param new_name: name
|
: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
|
self._name = new_name
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -123,7 +123,17 @@ class VPCSVM(BaseVM):
|
|||||||
|
|
||||||
return self._console
|
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
|
@BaseVM.name.setter
|
||||||
def name(self, new_name):
|
def name(self, new_name):
|
||||||
"""
|
"""
|
||||||
@ -133,22 +143,11 @@ class VPCSVM(BaseVM):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
if self._script_file:
|
if self._script_file:
|
||||||
# update the startup.vpc
|
content = self.startup_script
|
||||||
config_path = os.path.join(self.working_dir, "startup.vpc")
|
content = content.replace(self._name, new_name)
|
||||||
if os.path.isfile(config_path):
|
self.startup_script = content
|
||||||
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))
|
|
||||||
|
|
||||||
log.info("VPCS {name} [{uuid}]: renamed to {new_name}".format(name=self._name,
|
super(VPCSVM, VPCSVM).name.__set__(self, new_name)
|
||||||
uuid=self.uuid,
|
|
||||||
new_name=new_name))
|
|
||||||
BaseVM.name = new_name
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def startup_script(self):
|
def startup_script(self):
|
||||||
@ -173,7 +172,10 @@ class VPCSVM(BaseVM):
|
|||||||
self._script_file = os.path.join(self.working_dir, 'startup.vpcs')
|
self._script_file = os.path.join(self.working_dir, 'startup.vpcs')
|
||||||
try:
|
try:
|
||||||
with open(self._script_file, '+w') as f:
|
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:
|
except OSError as e:
|
||||||
raise VPCSError("Can't write VPCS startup file '{}'".format(self._script_file))
|
raise VPCSError("Can't write VPCS startup file '{}'".format(self._script_file))
|
||||||
|
|
||||||
|
@ -63,6 +63,33 @@ VPCS_CREATE_SCHEMA = {
|
|||||||
"required": ["name", "project_uuid"]
|
"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 = {
|
VPCS_NIO_SCHEMA = {
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
|
@ -45,6 +45,9 @@ class Query:
|
|||||||
def post(self, path, body={}, **kwargs):
|
def post(self, path, body={}, **kwargs):
|
||||||
return self._fetch("POST", 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):
|
def get(self, path, **kwargs):
|
||||||
return self._fetch("GET", path, **kwargs)
|
return self._fetch("GET", path, **kwargs)
|
||||||
|
|
||||||
@ -147,11 +150,10 @@ def loop(request):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def server(request, loop):
|
def server(request, loop, port_manager):
|
||||||
port = _get_unused_port()
|
port = _get_unused_port()
|
||||||
host = "localhost"
|
host = "localhost"
|
||||||
app = web.Application()
|
app = web.Application()
|
||||||
port_manager = PortManager("127.0.0.1", False)
|
|
||||||
for method, route, handler in Route.get_routes():
|
for method, route, handler in Route.get_routes():
|
||||||
app.router.add_route(method, route, handler)
|
app.router.add_route(method, route, handler)
|
||||||
for module in MODULES:
|
for module in MODULES:
|
||||||
|
@ -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 tests.api.base import server, loop
|
||||||
from gns3server.version import __version__
|
from gns3server.version import __version__
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ This test suite check /version endpoint
|
|||||||
It's also used for unittest the HTTP implementation.
|
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 tests.api.base import server, loop
|
||||||
from gns3server.version import __version__
|
from gns3server.version import __version__
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from tests.api.base import server, loop, project
|
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
|
from gns3server.modules.virtualbox.virtualbox_vm import VirtualBoxVM
|
||||||
|
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import os
|
import os
|
||||||
from tests.api.base import server, loop, project
|
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 unittest.mock import patch, Mock
|
||||||
from gns3server.modules.vpcs.vpcs_vm import VPCSVM
|
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"
|
assert response.json["startup_script"] == "ip 192.168.1.2\necho TEST"
|
||||||
|
|
||||||
|
|
||||||
def test_vpcs_create_port(server, project):
|
def test_vpcs_create_port(server, project, free_console_port):
|
||||||
response = server.post("/vpcs", {"name": "PC TEST 1", "project_uuid": project.uuid, "console": 4242})
|
response = server.post("/vpcs", {"name": "PC TEST 1", "project_uuid": project.uuid, "console": free_console_port})
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert response.route == "/vpcs"
|
assert response.route == "/vpcs"
|
||||||
assert response.json["name"] == "PC TEST 1"
|
assert response.json["name"] == "PC TEST 1"
|
||||||
assert response.json["project_uuid"] == project.uuid
|
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):
|
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"]))
|
response = server.post("/vpcs/{}/stop".format(vm["uuid"]))
|
||||||
assert mock.called
|
assert mock.called
|
||||||
assert response.status == 200
|
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"
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import asyncio
|
import asyncio
|
||||||
import os
|
import os
|
||||||
from tests.utils import asyncio_patch
|
from tests.utils import asyncio_patch, port_manager, free_console_port
|
||||||
|
|
||||||
# TODO: Move loop to util
|
# TODO: Move loop to util
|
||||||
from tests.api.base import loop, project
|
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"
|
content = "echo GNS3 VPCS\nip 192.168.1.2\n"
|
||||||
vm.startup_script = content
|
vm.startup_script = content
|
||||||
assert 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
|
||||||
|
@ -16,7 +16,10 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import pytest
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
from gns3server.modules.project_manager import ProjectManager
|
||||||
|
from gns3server.modules.port_manager import PortManager
|
||||||
|
|
||||||
|
|
||||||
class _asyncio_patch:
|
class _asyncio_patch:
|
||||||
@ -54,3 +57,18 @@ class _asyncio_patch:
|
|||||||
|
|
||||||
def asyncio_patch(function, *args, **kwargs):
|
def asyncio_patch(function, *args, **kwargs):
|
||||||
return _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
|
||||||
|
Loading…
Reference in New Issue
Block a user