mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-15 12:59:06 +00:00
More VirtualBox work.
This commit is contained in:
parent
d9b02efbfa
commit
05c0efe39b
@ -75,19 +75,17 @@ class VirtualBoxVM(BaseVM):
|
||||
|
||||
def __json__(self):
|
||||
|
||||
# TODO: send more info
|
||||
# {"name": self._name,
|
||||
# "vmname": self._vmname,
|
||||
# "adapters": self.adapters,
|
||||
# TODO: send adapters info
|
||||
# "adapter_start_index": self._adapter_start_index,
|
||||
# "adapter_type": "Intel PRO/1000 MT Desktop (82540EM)",
|
||||
# "console": self._console,
|
||||
# "enable_remote_console": self._enable_remote_console,
|
||||
# "headless": self._headless}
|
||||
|
||||
return {"name": self.name,
|
||||
"uuid": self.uuid,
|
||||
"project_uuid": self.project.uuid}
|
||||
"project_uuid": self.project.uuid,
|
||||
"vmname": self.vmname,
|
||||
"linked_clone": self.linked_clone,
|
||||
"headless": self.headless,
|
||||
"enable_remote_console": self.enable_remote_console}
|
||||
|
||||
@asyncio.coroutine
|
||||
def _execute(self, subcommand, args, timeout=60):
|
||||
@ -167,8 +165,7 @@ class VirtualBoxVM(BaseVM):
|
||||
args = shlex.split(params)
|
||||
yield from self._execute("modifyvm", [self._vmname] + args)
|
||||
|
||||
@asyncio.coroutine
|
||||
def create(self):
|
||||
def _find_vboxmanage(self):
|
||||
|
||||
# look for VBoxManage
|
||||
self._vboxmanage_path = self.manager.config.get_section_config("VirtualBox").get("vboxmanage_path")
|
||||
@ -185,22 +182,27 @@ class VirtualBoxVM(BaseVM):
|
||||
|
||||
if not self._vboxmanage_path:
|
||||
raise VirtualBoxError("Could not find VBoxManage")
|
||||
if not os.path.isfile(self._vboxmanage_path):
|
||||
raise VirtualBoxError("VBoxManage {} is not accessible".format(self._vboxmanage_path))
|
||||
if not os.access(self._vboxmanage_path, os.X_OK):
|
||||
raise VirtualBoxError("VBoxManage is not executable")
|
||||
|
||||
@asyncio.coroutine
|
||||
def create(self):
|
||||
|
||||
self._find_vboxmanage()
|
||||
yield from self._get_system_properties()
|
||||
if parse_version(self._system_properties["API version"]) < parse_version("4_3"):
|
||||
raise VirtualBoxError("The VirtualBox API version is lower than 4.3")
|
||||
log.info("VirtualBox VM '{name}' [{uuid}] created".format(name=self.name, uuid=self.uuid))
|
||||
|
||||
if self._linked_clone:
|
||||
# TODO: finish linked clone support
|
||||
if self.uuid and os.path.isdir(os.path.join(self.working_dir, self._vmname)):
|
||||
vbox_file = os.path.join(self.working_dir, self._vmname, self._vmname + ".vbox")
|
||||
self._execute("registervm", [vbox_file])
|
||||
self._reattach_hdds()
|
||||
yield from self._execute("registervm", [vbox_file])
|
||||
yield from self._reattach_hdds()
|
||||
else:
|
||||
self._create_linked_clone()
|
||||
yield from self._create_linked_clone()
|
||||
|
||||
@asyncio.coroutine
|
||||
def start(self):
|
||||
@ -323,14 +325,15 @@ class VirtualBoxVM(BaseVM):
|
||||
self._console = console
|
||||
self._allocated_console_ports.append(self._console)
|
||||
|
||||
log.info("VirtualBox VM {name} [id={id}]: console port set to {port}".format(name=self._name,
|
||||
id=self._id,
|
||||
port=console))
|
||||
log.info("VirtualBox VM '{name}' [{uuid}]: console port set to {port}".format(name=self.name,
|
||||
uuid=self.uuid,
|
||||
port=console))
|
||||
|
||||
@asyncio.coroutine
|
||||
def _get_all_hdd_files(self):
|
||||
|
||||
hdds = []
|
||||
properties = self._execute("list", ["hdds"])
|
||||
properties = yield from self._execute("list", ["hdds"])
|
||||
for prop in properties:
|
||||
try:
|
||||
name, value = prop.split(':', 1)
|
||||
@ -340,33 +343,32 @@ class VirtualBoxVM(BaseVM):
|
||||
hdds.append(value.strip())
|
||||
return hdds
|
||||
|
||||
@asyncio.coroutine
|
||||
def _reattach_hdds(self):
|
||||
|
||||
hdd_info_file = os.path.join(self._working_dir, self._vmname, "hdd_info.json")
|
||||
hdd_info_file = os.path.join(self.working_dir, self._vmname, "hdd_info.json")
|
||||
try:
|
||||
with open(hdd_info_file, "r") as f:
|
||||
# log.info("loading project: {}".format(path))
|
||||
hdd_table = json.load(f)
|
||||
except OSError as e:
|
||||
raise VirtualBoxError("Could not read HDD info file: {}".format(e))
|
||||
|
||||
for hdd_info in hdd_table:
|
||||
hdd_file = os.path.join(self._working_dir, self._vmname, "Snapshots", hdd_info["hdd"])
|
||||
hdd_file = os.path.join(self.working_dir, self._vmname, "Snapshots", hdd_info["hdd"])
|
||||
if os.path.exists(hdd_file):
|
||||
log.debug("reattaching hdd {}".format(hdd_file))
|
||||
self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium "{}"'.format(hdd_info["controller"],
|
||||
hdd_info["port"],
|
||||
hdd_info["device"],
|
||||
hdd_file))
|
||||
yield from self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium "{}"'.format(hdd_info["controller"],
|
||||
hdd_info["port"],
|
||||
hdd_info["device"],
|
||||
hdd_file))
|
||||
|
||||
def delete(self):
|
||||
@asyncio.coroutine
|
||||
def close(self):
|
||||
"""
|
||||
Deletes this VirtualBox VM.
|
||||
Closes this VirtualBox VM.
|
||||
"""
|
||||
|
||||
self.stop()
|
||||
if self._id in self._instances:
|
||||
self._instances.remove(self._id)
|
||||
|
||||
if self.console and self.console in self._allocated_console_ports:
|
||||
self._allocated_console_ports.remove(self.console)
|
||||
@ -374,7 +376,7 @@ class VirtualBoxVM(BaseVM):
|
||||
if self._linked_clone:
|
||||
hdd_table = []
|
||||
if os.path.exists(self._working_dir):
|
||||
hdd_files = self._get_all_hdd_files()
|
||||
hdd_files = yield from self._get_all_hdd_files()
|
||||
vm_info = self._get_vm_info()
|
||||
for entry, value in vm_info.items():
|
||||
match = re.search("^([\s\w]+)\-(\d)\-(\d)$", entry)
|
||||
@ -383,7 +385,7 @@ class VirtualBoxVM(BaseVM):
|
||||
port = match.group(2)
|
||||
device = match.group(3)
|
||||
if value in hdd_files:
|
||||
self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium none'.format(controller, port, device))
|
||||
yield from self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium none'.format(controller, port, device))
|
||||
hdd_table.append(
|
||||
{
|
||||
"hdd": os.path.basename(value),
|
||||
@ -404,17 +406,15 @@ class VirtualBoxVM(BaseVM):
|
||||
except OSError as e:
|
||||
raise VirtualBoxError("Could not write HDD info file: {}".format(e))
|
||||
|
||||
log.info("VirtualBox VM {name} [id={id}] has been deleted".format(name=self._name,
|
||||
id=self._id))
|
||||
log.info("VirtualBox VM '{name}' [{uuid}] closed".format(name=self.name,
|
||||
uuid=self.uuid))
|
||||
|
||||
def clean_delete(self):
|
||||
def delete(self):
|
||||
"""
|
||||
Deletes this VirtualBox VM & all files.
|
||||
"""
|
||||
|
||||
self.stop()
|
||||
if self._id in self._instances:
|
||||
self._instances.remove(self._id)
|
||||
|
||||
if self.console:
|
||||
self._allocated_console_ports.remove(self.console)
|
||||
@ -506,6 +506,16 @@ class VirtualBoxVM(BaseVM):
|
||||
self._modify_vm('--name "{}"'.format(vmname))
|
||||
self._vmname = vmname
|
||||
|
||||
@property
|
||||
def linked_clone(self):
|
||||
"""
|
||||
Returns either the VM is a linked clone.
|
||||
|
||||
:returns: boolean
|
||||
"""
|
||||
|
||||
return self._linked_clone
|
||||
|
||||
@property
|
||||
def adapters(self):
|
||||
"""
|
||||
@ -651,6 +661,7 @@ class VirtualBoxVM(BaseVM):
|
||||
args = [self._vmname, "--uartmode1", "server", pipe_name]
|
||||
yield from self._execute("modifyvm", args)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _storage_attach(self, params):
|
||||
"""
|
||||
Change storage medium in this VM.
|
||||
@ -659,7 +670,7 @@ class VirtualBoxVM(BaseVM):
|
||||
"""
|
||||
|
||||
args = shlex.split(params)
|
||||
self._execute("storageattach", [self._vmname] + args)
|
||||
yield from self._execute("storageattach", [self._vmname] + args)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _get_nic_attachements(self, maximum_adapters):
|
||||
@ -760,9 +771,9 @@ class VirtualBoxVM(BaseVM):
|
||||
"--options",
|
||||
"link",
|
||||
"--name",
|
||||
self._name,
|
||||
self.name,
|
||||
"--basefolder",
|
||||
self._working_dir,
|
||||
self.working_dir,
|
||||
"--register"]
|
||||
|
||||
result = yield from self._execute("clonevm", args)
|
||||
|
@ -82,6 +82,23 @@ VBOX_OBJECT_SCHEMA = {
|
||||
"maxLength": 36,
|
||||
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
|
||||
},
|
||||
"vmname": {
|
||||
"description": "VirtualBox VM name (in VirtualBox itself)",
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
},
|
||||
"linked_clone": {
|
||||
"description": "either the VM is a linked clone or not",
|
||||
"type": "boolean"
|
||||
},
|
||||
"enable_remote_console": {
|
||||
"description": "enable the remote console",
|
||||
"type": "boolean"
|
||||
},
|
||||
"headless": {
|
||||
"description": "headless mode",
|
||||
"type": "boolean"
|
||||
},
|
||||
"console": {
|
||||
"description": "console TCP port",
|
||||
"minimum": 1,
|
||||
|
@ -70,3 +70,10 @@ def test_vbox_resume(server, vm):
|
||||
response = server.post("/virtualbox/{}/resume".format(vm["uuid"]))
|
||||
assert mock.called
|
||||
assert response.status == 204
|
||||
|
||||
|
||||
def test_vbox_reload(server, vm):
|
||||
with asyncio_patch("gns3server.modules.virtualbox.virtualbox_vm.VirtualBoxVM.reload", return_value=True) as mock:
|
||||
response = server.post("/virtualbox/{}/reload".format(vm["uuid"]))
|
||||
assert mock.called
|
||||
assert response.status == 204
|
||||
|
77
tests/modules/virtualbox/test_virtualbox_vm.py
Normal file
77
tests/modules/virtualbox/test_virtualbox_vm.py
Normal file
@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import pytest
|
||||
import asyncio
|
||||
import tempfile
|
||||
from tests.utils import asyncio_patch
|
||||
|
||||
from unittest.mock import patch, MagicMock
|
||||
from gns3server.modules.virtualbox.virtualbox_vm import VirtualBoxVM
|
||||
from gns3server.modules.virtualbox.virtualbox_error import VirtualBoxError
|
||||
from gns3server.modules.virtualbox import VirtualBox
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def manager(port_manager):
|
||||
m = VirtualBox.instance()
|
||||
m.port_manager = port_manager
|
||||
return m
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def vm(project, manager):
|
||||
return VirtualBoxVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager, "test", False)
|
||||
|
||||
|
||||
def test_vm(project, manager):
|
||||
vm = VirtualBoxVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager, "test", False)
|
||||
assert vm.name == "test"
|
||||
assert vm.uuid == "00010203-0405-0607-0809-0a0b0c0d0e0f"
|
||||
assert vm.vmname == "test"
|
||||
assert vm.linked_clone is False
|
||||
|
||||
|
||||
@patch("gns3server.config.Config.get_section_config", return_value={"vboxmanage_path": "/bin/test_fake"})
|
||||
def test_vm_invalid_vboxmanage_path(project, manager):
|
||||
with pytest.raises(VirtualBoxError):
|
||||
vm = VirtualBoxVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0e", project, manager, "test", False)
|
||||
vm._find_vboxmanage()
|
||||
|
||||
|
||||
tmpfile = tempfile.NamedTemporaryFile()
|
||||
@patch("gns3server.config.Config.get_section_config", return_value={"vboxmanage_path": tmpfile.name})
|
||||
def test_vm_non_executable_vboxmanage_path(project, manager, loop):
|
||||
with pytest.raises(VirtualBoxError):
|
||||
vm = VirtualBoxVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0e", project, manager, "test", False)
|
||||
vm._find_vboxmanage()
|
||||
|
||||
|
||||
def test_vm_valid_virtualbox_api_version(loop, project, manager):
|
||||
with asyncio_patch("gns3server.modules.virtualbox.virtualbox_vm.VirtualBoxVM._execute", return_value=["API version: 4_3"]):
|
||||
vm = VirtualBoxVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager, "test", False)
|
||||
loop.run_until_complete(asyncio.async(vm.create()))
|
||||
|
||||
|
||||
def test_vm_invalid_virtualbox_api_version(loop, project, manager):
|
||||
with asyncio_patch("gns3server.modules.virtualbox.virtualbox_vm.VirtualBoxVM._execute", return_value=["API version: 4_2"]):
|
||||
with pytest.raises(VirtualBoxError):
|
||||
vm = VirtualBoxVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager, "test", False)
|
||||
loop.run_until_complete(asyncio.async(vm.create()))
|
||||
|
||||
|
||||
# TODO: find a way to test start, stop, suspend, resume and reload
|
Loading…
Reference in New Issue
Block a user