From ff63530f521ad77d9d7b9bc8a56e9735e90df9dd Mon Sep 17 00:00:00 2001 From: Jeremy Date: Fri, 23 Jan 2015 17:57:54 -0700 Subject: [PATCH] Get all available VirtualBox VMs on the server. --- gns3server/handlers/virtualbox_handler.py | 18 ++- gns3server/modules/virtualbox/__init__.py | 107 ++++++++++++++++++ .../modules/virtualbox/virtualbox_vm.py | 101 ++++------------- .../virtualbox/test_virtualbox_manager.py | 44 +++++++ .../modules/virtualbox/test_virtualbox_vm.py | 20 +--- tests/modules/vpcs/test_vpcs_manager.py | 1 - 6 files changed, 187 insertions(+), 104 deletions(-) create mode 100644 tests/modules/virtualbox/test_virtualbox_manager.py diff --git a/gns3server/handlers/virtualbox_handler.py b/gns3server/handlers/virtualbox_handler.py index b0c21334..6049ec3e 100644 --- a/gns3server/handlers/virtualbox_handler.py +++ b/gns3server/handlers/virtualbox_handler.py @@ -29,6 +29,19 @@ class VirtualBoxHandler: API entry points for VirtualBox. """ + @classmethod + @Route.get( + r"/virtualbox/list", + status_codes={ + 200: "Success", + }, + description="Get all VirtualBox VMs available") + def show(request, response): + + vbox_manager = VirtualBox.instance() + vms = yield from vbox_manager.get_list() + response.json(vms) + @classmethod @Route.post( r"/virtualbox", @@ -48,8 +61,7 @@ class VirtualBoxHandler: request.json.get("uuid"), request.json["vmname"], request.json["linked_clone"], - console=request.json.get("console"), - vbox_user=request.json.get("vbox_user")) + console=request.json.get("console")) response.set_status(201) response.json(vm) @@ -226,7 +238,7 @@ class VirtualBoxHandler: vbox_manager = VirtualBox.instance() vm = vbox_manager.get_vm(request.match_info["uuid"]) - nio = vbox_manager.create_nio(vm.vboxmanage_path, request.json) + nio = vbox_manager.create_nio(vbox_manager.vboxmanage_path, request.json) vm.port_add_nio_binding(int(request.match_info["port_id"]), nio) response.set_status(201) response.json(nio) diff --git a/gns3server/modules/virtualbox/__init__.py b/gns3server/modules/virtualbox/__init__.py index 072d8ca9..152d72e2 100644 --- a/gns3server/modules/virtualbox/__init__.py +++ b/gns3server/modules/virtualbox/__init__.py @@ -19,9 +19,116 @@ VirtualBox server module. """ +import os +import sys +import shutil +import asyncio +import subprocess + from ..base_manager import BaseManager from .virtualbox_vm import VirtualBoxVM +from .virtualbox_error import VirtualBoxError class VirtualBox(BaseManager): + _VM_CLASS = VirtualBoxVM + + def __init__(self): + + super().__init__() + self._vboxmanage_path = None + self._vbox_user = None + + @property + def vboxmanage_path(self): + """ + Returns the path to VBoxManage. + + :returns: path + """ + + return self._vboxmanage_path + + @property + def vbox_user(self): + """ + Returns the VirtualBox user + + :returns: username + """ + + return self._vbox_user + + def find_vboxmanage(self): + + # look for VBoxManage + vboxmanage_path = self.config.get_section_config("VirtualBox").get("vboxmanage_path") + if not vboxmanage_path: + if sys.platform.startswith("win"): + if "VBOX_INSTALL_PATH" in os.environ: + vboxmanage_path = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe") + elif "VBOX_MSI_INSTALL_PATH" in os.environ: + vboxmanage_path = os.path.join(os.environ["VBOX_MSI_INSTALL_PATH"], "VBoxManage.exe") + elif sys.platform.startswith("darwin"): + vboxmanage_path = "/Applications/VirtualBox.app/Contents/MacOS/VBoxManage" + else: + vboxmanage_path = shutil.which("vboxmanage") + + if not vboxmanage_path: + raise VirtualBoxError("Could not find VBoxManage") + if not os.path.isfile(vboxmanage_path): + raise VirtualBoxError("VBoxManage {} is not accessible".format(vboxmanage_path)) + if not os.access(vboxmanage_path, os.X_OK): + raise VirtualBoxError("VBoxManage is not executable") + + self._vboxmanage_path = vboxmanage_path + return vboxmanage_path + + @asyncio.coroutine + def execute(self, subcommand, args, timeout=60): + + vboxmanage_path = self.vboxmanage_path + if not vboxmanage_path: + vboxmanage_path = self.find_vboxmanage() + command = [vboxmanage_path, "--nologo", subcommand] + command.extend(args) + try: + if self.vbox_user: + # TODO: test & review this part + sudo_command = "sudo -i -u {}".format(self.vbox_user) + " ".join(command) + process = yield from asyncio.create_subprocess_shell(sudo_command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) + else: + process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) + except (OSError, subprocess.SubprocessError) as e: + raise VirtualBoxError("Could not execute VBoxManage: {}".format(e)) + + try: + stdout_data, stderr_data = yield from asyncio.wait_for(process.communicate(), timeout=timeout) + except asyncio.TimeoutError: + raise VirtualBoxError("VBoxManage has timed out after {} seconds!".format(timeout)) + + if process.returncode: + # only the first line of the output is useful + vboxmanage_error = stderr_data.decode("utf-8", errors="ignore").splitlines()[0] + raise VirtualBoxError(vboxmanage_error) + + return stdout_data.decode("utf-8", errors="ignore").splitlines() + + @asyncio.coroutine + def get_list(self): + """ + Gets VirtualBox VM list. + """ + + vms = [] + result = yield from self.execute("list", ["vms"]) + for line in result: + vmname, uuid = line.rsplit(' ', 1) + vmname = vmname.strip('"') + if vmname == "": + continue # ignore inaccessible VMs + extra_data = yield from self.execute("getextradata", [vmname, "GNS3/Clone"]) + if not extra_data[0].strip() == "Value: yes": + vms.append(vmname) + return vms diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index 2ec67434..aa08a54a 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -28,7 +28,6 @@ import tempfile import json import socket import asyncio -import shutil from pkg_resources import parse_version from .virtualbox_error import VirtualBoxError @@ -49,14 +48,12 @@ class VirtualBoxVM(BaseVM): VirtualBox VM implementation. """ - def __init__(self, name, uuid, project, manager, vmname, linked_clone, console=None, vbox_user=None): + def __init__(self, name, uuid, project, manager, vmname, linked_clone, console=None): super().__init__(name, uuid, project, manager) - self._vboxmanage_path = None self._maximum_adapters = 8 self._linked_clone = linked_clone - self._vbox_user = vbox_user self._system_properties = {} self._telnet_server_thread = None self._serial_pipe = None @@ -89,37 +86,10 @@ class VirtualBoxVM(BaseVM): "adapter_type": self.adapter_type, "adapter_start_index": self.adapter_start_index} - @asyncio.coroutine - def _execute(self, subcommand, args, timeout=60): - - command = [self._vboxmanage_path, "--nologo", subcommand] - command.extend(args) - try: - if self._vbox_user and self._vbox_user.strip(): - # TODO: test & review this part - sudo_command = "sudo -i -u {}".format(self._vbox_user.strip()) + " ".join(command) - process = yield from asyncio.create_subprocess_shell(sudo_command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) - else: - process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE) - except (OSError, subprocess.SubprocessError) as e: - raise VirtualBoxError("Could not execute VBoxManage: {}".format(e)) - - try: - stdout_data, stderr_data = yield from asyncio.wait_for(process.communicate(), timeout=timeout) - except asyncio.TimeoutError: - raise VirtualBoxError("VBoxManage has timed out after {} seconds!".format(timeout)) - - if process.returncode: - # only the first line of the output is useful - vboxmanage_error = stderr_data.decode("utf-8", errors="ignore").splitlines()[0] - raise VirtualBoxError(vboxmanage_error) - - return stdout_data.decode("utf-8", errors="ignore").splitlines() - @asyncio.coroutine def _get_system_properties(self): - properties = yield from self._execute("list", ["systemproperties"]) + properties = yield from self.manager.execute("list", ["systemproperties"]) for prop in properties: try: name, value = prop.split(':', 1) @@ -135,7 +105,7 @@ class VirtualBoxVM(BaseVM): :returns: state (string) """ - results = yield from self._execute("showvminfo", [self._vmname, "--machinereadable"]) + results = yield from self.manager.execute("showvminfo", [self._vmname, "--machinereadable"]) for info in results: name, value = info.split('=', 1) if name == "VMState": @@ -153,7 +123,7 @@ class VirtualBoxVM(BaseVM): """ args = shlex.split(params) - result = yield from self._execute("controlvm", [self._vmname] + args) + result = yield from self.manager.execute("controlvm", [self._vmname] + args) return result @asyncio.coroutine @@ -165,34 +135,11 @@ class VirtualBoxVM(BaseVM): """ args = shlex.split(params) - yield from self._execute("modifyvm", [self._vmname] + args) - - def _find_vboxmanage(self): - - # look for VBoxManage - self._vboxmanage_path = self.manager.config.get_section_config("VirtualBox").get("vboxmanage_path") - if not self._vboxmanage_path: - if sys.platform.startswith("win"): - if "VBOX_INSTALL_PATH" in os.environ: - self._vboxmanage_path = os.path.join(os.environ["VBOX_INSTALL_PATH"], "VBoxManage.exe") - elif "VBOX_MSI_INSTALL_PATH" in os.environ: - self._vboxmanage_path = os.path.join(os.environ["VBOX_MSI_INSTALL_PATH"], "VBoxManage.exe") - elif sys.platform.startswith("darwin"): - self._vboxmanage_path = "/Applications/VirtualBox.app/Contents/MacOS/VBoxManage" - else: - self._vboxmanage_path = shutil.which("vboxmanage") - - 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") + yield from self.manager.execute("modifyvm", [self._vmname] + args) @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") @@ -201,7 +148,7 @@ class VirtualBoxVM(BaseVM): if self._linked_clone: 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") - yield from self._execute("registervm", [vbox_file]) + yield from self.manager.execute("registervm", [vbox_file]) yield from self._reattach_hdds() else: yield from self._create_linked_clone() @@ -231,14 +178,14 @@ class VirtualBoxVM(BaseVM): args = [self._vmname] if self._headless: args.extend(["--type", "headless"]) - result = yield from self._execute("startvm", args) + result = yield from self.manager.execute("startvm", args) log.info("VirtualBox VM '{name}' [{uuid}] started".format(name=self.name, uuid=self.uuid)) log.debug("Start result: {}".format(result)) # add a guest property to let the VM know about the GNS3 name - yield from self._execute("guestproperty", ["set", self._vmname, "NameInGNS3", self.name]) + yield from self.manager.execute("guestproperty", ["set", self._vmname, "NameInGNS3", self.name]) # add a guest property to let the VM know about the GNS3 project directory - yield from self._execute("guestproperty", ["set", self._vmname, "ProjectDirInGNS3", self.working_dir]) + yield from self.manager.execute("guestproperty", ["set", self._vmname, "ProjectDirInGNS3", self.working_dir]) if self._enable_remote_console: self._start_remote_console() @@ -305,16 +252,6 @@ class VirtualBoxVM(BaseVM): log.info("VirtualBox VM '{name}' [{uuid}] reloaded".format(name=self.name, uuid=self.uuid)) log.debug("Reload result: {}".format(result)) - @property - def vboxmanage_path(self): - """ - Returns the path to VBoxManage. - - :returns: path - """ - - return self._vboxmanage_path - @property def console(self): """ @@ -345,7 +282,7 @@ class VirtualBoxVM(BaseVM): def _get_all_hdd_files(self): hdds = [] - properties = yield from self._execute("list", ["hdds"]) + properties = yield from self.manager.execute("list", ["hdds"]) for prop in properties: try: name, value = prop.split(':', 1) @@ -408,7 +345,7 @@ class VirtualBoxVM(BaseVM): } ) - self._execute("unregistervm", [self._vmname]) + yield from self.manager.execute("unregistervm", [self._vmname]) if hdd_table: try: @@ -597,7 +534,7 @@ class VirtualBoxVM(BaseVM): """ vm_info = {} - results = yield from self._execute("showvminfo", [self._vmname, "--machinereadable"]) + results = yield from self.manager.execute("showvminfo", [self._vmname, "--machinereadable"]) for info in results: try: name, value = info.split('=', 1) @@ -649,7 +586,7 @@ class VirtualBoxVM(BaseVM): # set server mode with a pipe on the first serial port pipe_name = self._get_pipe_name() args = [self._vmname, "--uartmode1", "server", pipe_name] - yield from self._execute("modifyvm", args) + yield from self.manager.execute("modifyvm", args) @asyncio.coroutine def _storage_attach(self, params): @@ -660,7 +597,7 @@ class VirtualBoxVM(BaseVM): """ args = shlex.split(params) - yield from self._execute("storageattach", [self._vmname] + args) + yield from self.manager.execute("storageattach", [self._vmname] + args) @asyncio.coroutine def _get_nic_attachements(self, maximum_adapters): @@ -714,7 +651,7 @@ class VirtualBoxVM(BaseVM): vbox_adapter_type = "virtio" args = [self._vmname, "--nictype{}".format(adapter_id + 1), vbox_adapter_type] - yield from self._execute("modifyvm", args) + yield from self.manager.execute("modifyvm", args) yield from self._modify_vm("--nictrace{} off".format(adapter_id + 1)) nio = self._ethernet_adapters[adapter_id].get_nio(0) @@ -752,7 +689,7 @@ class VirtualBoxVM(BaseVM): gns3_snapshot_exists = True if not gns3_snapshot_exists: - result = yield from self._execute("snapshot", [self._vmname, "take", "GNS3 Linked Base for clones"]) + result = yield from self.manager.execute("snapshot", [self._vmname, "take", "GNS3 Linked Base for clones"]) log.debug("GNS3 snapshot created: {}".format(result)) args = [self._vmname, @@ -766,14 +703,14 @@ class VirtualBoxVM(BaseVM): self.working_dir, "--register"] - result = yield from self._execute("clonevm", args) + result = yield from self.manager.execute("clonevm", args) log.debug("cloned VirtualBox VM: {}".format(result)) self._vmname = self._name - yield from self._execute("setextradata", [self._vmname, "GNS3/Clone", "yes"]) + yield from self.manager.execute("setextradata", [self._vmname, "GNS3/Clone", "yes"]) args = [self._name, "take", "reset"] - result = yield from self._execute("snapshot", args) + result = yield from self.manager.execute("snapshot", args) log.debug("Snapshot reset created: {}".format(result)) def _start_remote_console(self): diff --git a/tests/modules/virtualbox/test_virtualbox_manager.py b/tests/modules/virtualbox/test_virtualbox_manager.py new file mode 100644 index 00000000..4521691b --- /dev/null +++ b/tests/modules/virtualbox/test_virtualbox_manager.py @@ -0,0 +1,44 @@ +# -*- 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 . + + +import pytest +import tempfile + +from gns3server.modules.virtualbox import VirtualBox +from gns3server.modules.virtualbox.virtualbox_error import VirtualBoxError +from unittest.mock import patch + + +@pytest.fixture(scope="module") +def manager(port_manager): + m = VirtualBox.instance() + m.port_manager = port_manager + return m + + +@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): + manager.find_vboxmanage() + + +def test_vm_non_executable_vboxmanage_path(project, manager): + tmpfile = tempfile.NamedTemporaryFile() + with patch("gns3server.config.Config.get_section_config", return_value={"vboxmanage_path": tmpfile.name}): + with pytest.raises(VirtualBoxError): + manager.find_vboxmanage() \ No newline at end of file diff --git a/tests/modules/virtualbox/test_virtualbox_vm.py b/tests/modules/virtualbox/test_virtualbox_vm.py index 56a31b5b..f4c889bf 100644 --- a/tests/modules/virtualbox/test_virtualbox_vm.py +++ b/tests/modules/virtualbox/test_virtualbox_vm.py @@ -17,10 +17,8 @@ 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 @@ -46,28 +44,14 @@ def test_vm(project, manager): 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() - - -def test_vm_non_executable_vboxmanage_path(project, manager, loop): - tmpfile = tempfile.NamedTemporaryFile() - with patch("gns3server.config.Config.get_section_config", return_value={"vboxmanage_path": tmpfile.name}): - 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"]): + with asyncio_patch("gns3server.modules.virtualbox.VirtualBox.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 asyncio_patch("gns3server.modules.virtualbox.VirtualBox.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())) diff --git a/tests/modules/vpcs/test_vpcs_manager.py b/tests/modules/vpcs/test_vpcs_manager.py index 7632ef4e..eb90876e 100644 --- a/tests/modules/vpcs/test_vpcs_manager.py +++ b/tests/modules/vpcs/test_vpcs_manager.py @@ -17,7 +17,6 @@ import pytest -import asyncio import uuid