1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-28 11:18:11 +00:00

Basic VMware support (start & stop a VM).

This commit is contained in:
Jeremy 2015-04-30 19:05:37 -06:00
parent f8f6f5dc5d
commit ab60d7929b
8 changed files with 701 additions and 2 deletions

View File

@ -25,6 +25,7 @@ from gns3server.handlers.api.dynamips_vm_handler import DynamipsVMHandler
from gns3server.handlers.api.qemu_handler import QEMUHandler from gns3server.handlers.api.qemu_handler import QEMUHandler
from gns3server.handlers.api.virtualbox_handler import VirtualBoxHandler from gns3server.handlers.api.virtualbox_handler import VirtualBoxHandler
from gns3server.handlers.api.vpcs_handler import VPCSHandler from gns3server.handlers.api.vpcs_handler import VPCSHandler
from gns3server.handlers.api.vmware_handler import VMwareHandler
from gns3server.handlers.api.config_handler import ConfigHandler from gns3server.handlers.api.config_handler import ConfigHandler
from gns3server.handlers.api.server_handler import ServerHandler from gns3server.handlers.api.server_handler import ServerHandler
from gns3server.handlers.api.file_handler import FileHandler from gns3server.handlers.api.file_handler import FileHandler

View File

@ -246,7 +246,7 @@ class VirtualBoxHandler:
404: "Instance doesn't exist" 404: "Instance doesn't exist"
}, },
description="Resume a suspended VirtualBox VM instance") description="Resume a suspended VirtualBox VM instance")
def suspend(request, response): def resume(request, response):
vbox_manager = VirtualBox.instance() vbox_manager = VirtualBox.instance()
vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"]) vm = vbox_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])

View File

@ -0,0 +1,229 @@
# -*- 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/>.
from ...web.route import Route
from ...schemas.vmware import VMWARE_CREATE_SCHEMA
from ...schemas.vmware import VMWARE_UPDATE_SCHEMA
from ...schemas.vmware import VMWARE_OBJECT_SCHEMA
from ...modules.vmware import VMware
from ...modules.project_manager import ProjectManager
class VMwareHandler:
"""
API entry points for VMware.
"""
@classmethod
@Route.post(
r"/projects/{project_id}/vmware/vms",
parameters={
"project_id": "UUID for the project"
},
status_codes={
201: "Instance created",
400: "Invalid request",
409: "Conflict"
},
description="Create a new VMware VM instance",
input=VMWARE_CREATE_SCHEMA,
output=VMWARE_OBJECT_SCHEMA)
def create(request, response):
vmware_manager = VMware.instance()
vm = yield from vmware_manager.create_vm(request.json.pop("name"),
request.match_info["project_id"],
request.json.get("vm_id"),
request.json.pop("vmx_path"),
request.json.pop("linked_clone"),
console=request.json.get("console", None))
# for name, value in request.json.items():
# if name != "vm_id":
# if hasattr(vm, name) and getattr(vm, name) != value:
# setattr(vm, name, value)
response.set_status(201)
response.json(vm)
@classmethod
@Route.get(
r"/projects/{project_id}/vmware/vms/{vm_id}",
parameters={
"project_id": "UUID for the project",
"vm_id": "UUID for the instance"
},
status_codes={
200: "Success",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Get a VMware VM instance",
output=VMWARE_OBJECT_SCHEMA)
def show(request, response):
vmware_manager = VMware.instance()
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
response.json(vm)
@classmethod
@Route.put(
r"/projects/{project_id}/vmware/vms/{vm_id}",
parameters={
"project_id": "UUID for the project",
"vm_id": "UUID for the instance"
},
status_codes={
200: "Instance updated",
400: "Invalid request",
404: "Instance doesn't exist",
409: "Conflict"
},
description="Update a VMware VM instance",
input=VMWARE_UPDATE_SCHEMA,
output=VMWARE_OBJECT_SCHEMA)
def update(request, response):
vmware_manager = VMware.instance()
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
# for name, value in request.json.items():
# if hasattr(vm, name) and getattr(vm, name) != value:
# setattr(vm, name, value)
response.json(vm)
@classmethod
@Route.delete(
r"/projects/{project_id}/vmware/vms/{vm_id}",
parameters={
"project_id": "UUID for the project",
"vm_id": "UUID for the instance"
},
status_codes={
204: "Instance deleted",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Delete a VMware VM instance")
def delete(request, response):
# check the project_id exists
ProjectManager.instance().get_project(request.match_info["project_id"])
yield from VMware.instance().delete_vm(request.match_info["vm_id"])
response.set_status(204)
@classmethod
@Route.post(
r"/projects/{project_id}/vmware/vms/{vm_id}/start",
parameters={
"project_id": "UUID for the project",
"vm_id": "UUID for the instance"
},
status_codes={
204: "Instance started",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Start a VMware VM instance")
def start(request, response):
vmware_manager = VMware.instance()
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
yield from vm.start()
response.set_status(204)
@classmethod
@Route.post(
r"/projects/{project_id}/vmware/vms/{vm_id}/stop",
parameters={
"project_id": "UUID for the project",
"vm_id": "UUID for the instance"
},
status_codes={
204: "Instance stopped",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Stop a VMware VM instance")
def stop(request, response):
vmware_manager = VMware.instance()
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
yield from vm.stop()
response.set_status(204)
@classmethod
@Route.post(
r"/projects/{project_id}/vmware/vms/{vm_id}/suspend",
parameters={
"project_id": "UUID for the project",
"vm_id": "UUID for the instance"
},
status_codes={
204: "Instance suspended",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Suspend a VMware VM instance")
def suspend(request, response):
vmware_manager = VMware.instance()
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
yield from vm.suspend()
response.set_status(204)
@classmethod
@Route.post(
r"/projects/{project_id}/vmware/vms/{vm_id}/resume",
parameters={
"project_id": "UUID for the project",
"vm_id": "UUID for the instance"
},
status_codes={
204: "Instance resumed",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Resume a suspended VMware VM instance")
def resume(request, response):
vmware_manager = VMware.instance()
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
yield from vm.resume()
response.set_status(204)
@classmethod
@Route.post(
r"/projects/{project_id}/vmware/vms/{vm_id}/reload",
parameters={
"project_id": "UUID for the project",
"vm_id": "UUID for the instance"
},
status_codes={
204: "Instance reloaded",
400: "Invalid request",
404: "Instance doesn't exist"
},
description="Reload a VMware VM instance")
def reload(request, response):
vmware_manager = VMware.instance()
vm = vmware_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
yield from vm.reload()
response.set_status(204)

View File

@ -21,8 +21,9 @@ from .vpcs import VPCS
from .virtualbox import VirtualBox from .virtualbox import VirtualBox
from .dynamips import Dynamips from .dynamips import Dynamips
from .qemu import Qemu from .qemu import Qemu
from .vmware import VMware
MODULES = [VPCS, VirtualBox, Dynamips, Qemu] MODULES = [VPCS, VirtualBox, Dynamips, Qemu, VMware]
if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1": if sys.platform.startswith("linux") or hasattr(sys, "_called_from_test") or os.environ.get("PYTEST_BUILD_DOCUMENTATION") == "1":

View File

@ -0,0 +1,106 @@
# -*- 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/>.
"""
VMware player/workstation server module.
"""
import os
import sys
import shutil
import asyncio
import subprocess
import logging
log = logging.getLogger(__name__)
from ..base_manager import BaseManager
from .vmware_vm import VMwareVM
from .vmware_error import VMwareError
class VMware(BaseManager):
_VM_CLASS = VMwareVM
def __init__(self):
super().__init__()
self._vmrun_path = None
self._host_type = "player"
@property
def vmrun_path(self):
"""
Returns the path vmrun utility.
:returns: path
"""
return self._vmrun_path
def find_vmrun(self):
# look for vmrun
vmrun_path = self.config.get_section_config("VMware").get("vmrun_path")
if not vmrun_path:
if sys.platform.startswith("win"):
pass # TODO: use registry to find vmrun
elif sys.platform.startswith("darwin"):
vmrun_path = "/Applications/VMware Fusion.app/Contents/Library/vmrun"
else:
vmrun_path = shutil.which("vmrun")
if not vmrun_path:
raise VMwareError("Could not find vmrun")
if not os.path.isfile(vmrun_path):
raise VMwareError("vmrun {} is not accessible".format(vmrun_path))
if not os.access(vmrun_path, os.X_OK):
raise VMwareError("vmrun is not executable")
if os.path.basename(vmrun_path) not in ["vmrun", "vmrun.exe"]:
raise VMwareError("Invalid vmrun executable name {}".format(os.path.basename(vmrun_path)))
self._vmrun_path = vmrun_path
return vmrun_path
@asyncio.coroutine
def execute(self, subcommand, args, timeout=60, host_type=None):
vmrun_path = self.vmrun_path
if not vmrun_path:
vmrun_path = self.find_vmrun()
if host_type is None:
host_type = self._host_type
command = [vmrun_path, "-T", host_type, subcommand]
command.extend(args)
log.debug("Executing vmrun with command: {}".format(command))
try:
process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
except (OSError, subprocess.SubprocessError) as e:
raise VMwareError("Could not execute vmrun: {}".format(e))
try:
stdout_data, _ = yield from asyncio.wait_for(process.communicate(), timeout=timeout)
except asyncio.TimeoutError:
raise VMwareError("vmrun has timed out after {} seconds!".format(timeout))
if process.returncode:
# vmrun print errors on stdout
vmrun_error = stdout_data.decode("utf-8", errors="ignore")
raise VMwareError("vmrun has returned an error: {}".format(vmrun_error))
return stdout_data.decode("utf-8", errors="ignore").splitlines()

View File

@ -0,0 +1,27 @@
# -*- 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/>.
"""
Custom exceptions for the VirtualBox module.
"""
from ..vm_error import VMError
class VMwareError(VMError):
pass

View File

@ -0,0 +1,192 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 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/>.
"""
VMware VM instance.
"""
import sys
import shlex
import re
import os
import tempfile
import json
import socket
import asyncio
from pkg_resources import parse_version
from .vmware_error import VMwareError
from ..nios.nio_udp import NIOUDP
from ..base_vm import BaseVM
import logging
log = logging.getLogger(__name__)
class VMwareVM(BaseVM):
"""
VMware VM implementation.
"""
def __init__(self, name, vm_id, project, manager, vmx_path, linked_clone, console=None):
super().__init__(name, vm_id, project, manager, console=console)
self._linked_clone = linked_clone
self._closed = False
# VMware VM settings
self._headless = False
self._vmx_path = vmx_path
def __json__(self):
return {"name": self.name,
"vm_id": self.id,
"console": self.console,
"project_id": self.project.id,
"vmx_path": self.vmx_path,
"headless": self.headless}
@asyncio.coroutine
def _control_vm(self, subcommand, *additional_args):
args = [self._vmx_path]
args.extend(additional_args)
result = yield from self.manager.execute(subcommand, args)
log.debug("Control VM '{}' result: {}".format(subcommand, result))
return result
@asyncio.coroutine
def start(self):
"""
Starts this VMware VM.
"""
if self._headless:
yield from self._control_vm("start", "nogui")
else:
yield from self._control_vm("start")
log.info("VMware VM '{name}' [{id}] started".format(name=self.name, id=self.id))
@asyncio.coroutine
def stop(self):
"""
Stops this VMware VM.
"""
yield from self._control_vm("stop")
log.info("VMware VM '{name}' [{id}] stopped".format(name=self.name, id=self.id))
@asyncio.coroutine
def suspend(self):
"""
Suspends this VMware VM.
"""
yield from self._control_vm("suspend")
log.info("VMware VM '{name}' [{id}] stopped".format(name=self.name, id=self.id))
@asyncio.coroutine
def resume(self):
"""
Resumes this VMware VM.
"""
yield from self.start()
log.info("VMware VM '{name}' [{id}] resumed".format(name=self.name, id=self.id))
@asyncio.coroutine
def reload(self):
"""
Reloads this VMware VM.
"""
yield from self._control_vm("reset")
log.info("VMware VM '{name}' [{id}] reloaded".format(name=self.name, id=self.id))
@asyncio.coroutine
def close(self):
"""
Closes this VirtualBox VM.
"""
if self._closed:
# VM is already closed
return
log.debug("VMware VM '{name}' [{id}] is closing".format(name=self.name, id=self.id))
if self._console:
self._manager.port_manager.release_tcp_port(self._console, self._project)
self._console = None
#for adapter in self._ethernet_adapters.values():
# if adapter is not None:
# for nio in adapter.ports.values():
# if nio and isinstance(nio, NIOUDP):
# self.manager.port_manager.release_udp_port(nio.lport, self._project)
yield from self.stop()
log.info("VirtualBox VM '{name}' [{id}] closed".format(name=self.name, id=self.id))
self._closed = True
@property
def headless(self):
"""
Returns either the VM will start in headless mode
:returns: boolean
"""
return self._headless
@headless.setter
def headless(self, headless):
"""
Sets either the VM will start in headless mode
:param headless: boolean
"""
if headless:
log.info("VMware VM '{name}' [{id}] has enabled the headless mode".format(name=self.name, id=self.id))
else:
log.info("VMware VM '{name}' [{id}] has disabled the headless mode".format(name=self.name, id=self.id))
self._headless = headless
@property
def vmx_path(self):
"""
Returns the path to the vmx file.
:returns: VMware vmx file
"""
return self._vmx_path
@vmx_path.setter
def vmx_path(self, vmx_path):
"""
Sets the path to the vmx file.
:param vmx_path: VMware vmx file
"""
log.info("VMware VM '{name}' [{id}] has set the vmx file path to '{vmx}'".format(name=self.name, id=self.id, vmx=vmx_path))
self._vmx_path = vmx_path

View File

@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2014 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/>.
VMWARE_CREATE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to create a new VMware VM instance",
"type": "object",
"properties": {
"vm_id": {
"description": "VMware VM instance identifier",
"type": "string",
"minLength": 36,
"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}$"
},
"linked_clone": {
"description": "either the VM is a linked clone or not",
"type": "boolean"
},
"name": {
"description": "VMware VM instance name",
"type": "string",
"minLength": 1,
},
"vmx_path": {
"description": "path to the vmx file",
"type": "string",
"minLength": 1,
},
"console": {
"description": "console TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
},
"enable_remote_console": {
"description": "enable the remote console",
"type": "boolean"
},
"headless": {
"description": "headless mode",
"type": "boolean"
},
},
"additionalProperties": False,
"required": ["name", "vmx_path", "linked_clone"],
}
VMWARE_UPDATE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Request validation to update a VMware VM instance",
"type": "object",
"properties": {
"name": {
"description": "VMware VM instance name",
"type": "string",
"minLength": 1,
},
"vmx_path": {
"description": "path to the vmx file",
"type": "string",
"minLength": 1,
},
"console": {
"description": "console TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
},
"enable_remote_console": {
"description": "enable the remote console",
"type": "boolean"
},
"headless": {
"description": "headless mode",
"type": "boolean"
},
},
"additionalProperties": False,
}
VMWARE_OBJECT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "VMware VM instance",
"type": "object",
"properties": {
"name": {
"description": "VMware VM instance name",
"type": "string",
"minLength": 1,
},
"vm_id": {
"description": "VMware VM instance UUID",
"type": "string",
"minLength": 36,
"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}$"
},
"project_id": {
"description": "Project UUID",
"type": "string",
"minLength": 36,
"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}$"
},
"vmx_path": {
"description": "path to the vmx file",
"type": "string",
"minLength": 1,
},
"enable_remote_console": {
"description": "enable the remote console",
"type": "boolean"
},
"headless": {
"description": "headless mode",
"type": "boolean"
},
"console": {
"description": "console TCP port",
"minimum": 1,
"maximum": 65535,
"type": "integer"
},
},
"additionalProperties": False,
"required": ["name", "vm_id", "project_id"]
}