"""
Provide a transparent abstraction for interacting with virtualizers.
"""
from abc import ABCMeta, abstractmethod
import logging
import re
import subprocess
import sys
from vmupdate.constants import *
log = logging.getLogger(__name__)
[docs]def get_virtualizer(name, path):
"""
Return an instance of a virtualizer.
The virtualizer should extend :class:`Virtualizer`.
:param str name: name of the virtualizer class to instantiate
:param str path: path of the virtualizer to pass to the constructor
"""
virtualizer_class = getattr(sys.modules[__name__], name)
return virtualizer_class(path)
[docs]class Virtualizer(object):
"""
Abstract virtualizer control.
This class must be inherited and cannot be used directly.
"""
__metaclass__ = ABCMeta
@abstractmethod
[docs] def list_vms(self):
"""
Return all virtual machines.
This is a virtualizer-specific command and must be overridden.
:return: list of tuple (name, id)
:rtype: list(str, str)
"""
pass
@abstractmethod
[docs] def start_vm(self, uid):
"""
Start the virtual machine.
This is a virtualizer-specific command and must be overridden.
:param str uid: identifier of the machine
:return: exitcode
:rtype: int
"""
pass
@abstractmethod
[docs] def stop_vm(self, uid):
"""
Stop the virtual machine.
This is a virtualizer-specific command and must be overridden.
:param str uid: identifier of the machine
:return: exitcode
:rtype: int
"""
pass
@abstractmethod
[docs] def get_vm_status(self, uid):
"""
Return the status of the virtual machine.
This is a virtualizer-specific command and must be overridden.
Possible values can be found in :mod:`.constants`.
:param str uid: identifier of the machine
:rtype: str
"""
pass
@abstractmethod
[docs] def get_vm_os(self, uid):
"""
Return the operating system of the virtual machine.
This is a virtualizer-specific command and must be overridden.
Possible values can be found in :mod:`.constants`.
:param str uid: identifier of the machine
:rtype: str
"""
pass
@abstractmethod
[docs] def get_ssh_info(self, uid, ssh_port):
"""
Return the SSH connection information for the virtual machine.
This is a virtualizer-specific command and must be overridden.
:param str uid: identifier of the machine
:param int ssh_port: expected SSH port of the guest
:return: tuple of (hostname, port)
:rtype: (str, int)
"""
pass
@abstractmethod
[docs] def enable_ssh(self, uid, host_port, guest_port):
"""
Enable SSH port forwarding for the virtual machine.
This is a virtualizer-specific command and must be overridden.
:param str uid: identifier of the machine
:param int host_port: the post on the host to forward to the guest
:param int guest_port: SSH port of the guest
:return: exitcode
:rtype: int
"""
pass
[docs]class VirtualBox(Virtualizer):
"""Control the VirtualBox virtualizer."""
def __init__(self, manager_path):
"""
Return an instance of :class:`VirtualBox`.
:param str manager_path: path to the VirtualBox control utility
:rtype:`VirtualBox`
"""
self._manager_path = manager_path
[docs] def list_vms(self):
"""
Return all virtual machines.
:return: list of tuple (name, id)
:rtype: list(str, str)
"""
cmd = subprocess.Popen([self._manager_path, 'list', 'vms'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
vms = []
stdoutdata, stderrdata = cmd.communicate()
if stderrdata:
log.error(stderrdata)
if stdoutdata:
matches = re.finditer(r"""^"(?P<name>[^"]+)"\s+\{(?P<uuid>[^}]+)\}""",
stdoutdata,
flags=re.IGNORECASE | re.MULTILINE)
if matches:
for match in matches:
vms.append((match.group('name'), match.group('uuid')))
return vms
[docs] def start_vm(self, uid):
"""
Start the virtual machine.
:param str uid: identifier of the machine
:return: exitcode
:rtype: int
"""
cmd = subprocess.Popen([self._manager_path, 'startvm', uid, '--type', 'headless'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
stdoutdata, stderrdata = cmd.communicate()
if stderrdata:
log.error(stderrdata)
return cmd.wait()
[docs] def stop_vm(self, uid):
"""
Stop the virtual machine.
:param str uid: identifier of the machine
:return: exitcode
:rtype: int
"""
cmd = subprocess.Popen([self._manager_path, 'controlvm', uid, 'poweroff'],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
return cmd.wait()
[docs] def get_vm_status(self, uid):
"""
Return the status of the virtual machine.
Possible values can be found in :mod:`.constants`.
:param str uid: identifier of the machine
:rtype: str
"""
cmd = subprocess.Popen([self._manager_path, 'showvminfo', uid],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
stdoutdata, stderrdata = cmd.communicate()
if stderrdata:
log.error(stderrdata)
if stdoutdata:
match = re.search(r"""^State:\s*(?P<state>[a-z\s]*)""",
stdoutdata, flags=re.IGNORECASE | re.MULTILINE)
if match:
state = match.group('state').strip().lower()
if state == 'powered off' or state == 'aborted':
return VM_STOPPED
elif state == 'running':
return VM_RUNNING
elif state == 'saved':
return VM_SUSPENDED
elif state == 'paused':
return VM_PAUSED
return VM_UNKNOWN
[docs] def get_vm_os(self, uid):
"""
Return the operating system of the virtual machine.
Possible values can be found in :mod:`.constants`.
:param str uid: identifier of the machine
:rtype: str
"""
cmd = subprocess.Popen([self._manager_path, 'showvminfo', uid],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
stdoutdata, stderrdata = cmd.communicate()
if stderrdata:
log.error(stderrdata)
if stdoutdata:
match = re.search(r"""^Guest OS:\s*(?P<os>[a-z\s]*)""", stdoutdata, flags=re.IGNORECASE | re.MULTILINE)
if match:
state = match.group('os').strip().lower()
if state in ('windows', 'windows xp', 'windows vista', 'other windows'):
return OS_WINDOWS
elif state == 'mac os x':
return OS_MAC_OS_X
elif state == 'linux':
return OS_LINUX
elif state == 'arch linux':
return OS_ARCH
elif state == 'ubuntu':
return OS_UBUNTU
elif state == 'red hat':
return OS_REDHAT
elif state == 'debian':
return OS_DEBIAN
elif state == 'fedora':
return OS_FEDORA
elif state == 'gentoo':
return OS_GENTOO
elif state == 'opensuse':
return OS_OPENSUSE
elif state == 'mandriva':
return OS_MANDRIVA
elif state == 'turbolinux':
return OS_TURBOLINUX
elif state == 'xandros':
return OS_XANDROS
elif state == 'oracle':
return OS_ORACLE
return OS_UNKNOWN
[docs] def get_ssh_info(self, uid, ssh_port):
"""
Return the SSH connection information for the virtual machine.
:param str uid: identifier of the machine
:param int ssh_port: expected SSH port of the guest
:return: tuple of (hostname, port)
:rtype: (str, int)
"""
cmd = subprocess.Popen([self._manager_path, 'showvminfo', uid], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
stdoutdata, stderrdata = cmd.communicate()
if stderrdata:
log.error(stderrdata)
if stdoutdata:
matches = re.finditer(r"""^NIC \d+ Rule\(\d+\):\s*name = (?P<name>[^,]*), """
"""protocol = (?P<protocol>(tcp|udp)), """
"""host ip = (?P<hostname>(\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3})?), """
"""host port = (?P<hostport>\d*), """
"""guest ip = (?P<guestip>(\d{0,3}\.\d{0,3}\.\d{0,3}\.\d{0,3})?), """
"""guest port = (?P<guestport>\d*)""",
stdoutdata, flags=re.IGNORECASE | re.MULTILINE)
if matches:
for match in matches:
if match.group('protocol').lower() == 'tcp' and int(match.group('guestport')) == ssh_port:
return match.group('hostname') or '127.0.0.1', int(match.group('hostport'))
return None, None
[docs] def enable_ssh(self, uid, host_port, guest_port):
"""
Enable SSH port forwarding for the virtual machine.
:param str uid: identifier of the machine
:param int host_port: the post on the host to forward to the guest
:param int guest_port: SSH port of the guest
:return: exitcode
:rtype: int
"""
cmd = subprocess.Popen([self._manager_path, 'modifyvm', uid,
'--natpf1', 'ssh,tcp,,{0},,{1}'.format(host_port, guest_port)],
stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
stdoutdata, stderrdata = cmd.communicate()
if stderrdata:
log.error(stderrdata)
return cmd.wait()