1
0
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:
Jeremy 2015-01-22 19:07:09 -07:00
parent d9b02efbfa
commit 05c0efe39b
4 changed files with 151 additions and 39 deletions

View File

@ -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)

View File

@ -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,

View File

@ -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

View 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