Commit 65a6f9b7 authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

Split hypervisor.py into several files

Before it was a huge file with more than 700 lines. No code changes were
made except for changing imports and class names where needed. In the
future, we could even think about only importing the hypervisors we
actually need. The patch finished QA scripts and distcheck successfully.

Reviewed-by: iustinp
parent a4af651e
......@@ -9,6 +9,7 @@ ganeti:
pre-check: ganeti
$(MAKE) -C lib $@
$(MAKE) -C lib/hypervisor $@
# a dist hook rule for catching revision control directories
distcheck-hook:
......
......@@ -117,6 +117,7 @@ AC_CONFIG_FILES([
doc/Makefile
doc/examples/Makefile
lib/Makefile
lib/hypervisor/Makefile
man/Makefile
qa/Makefile
qa/hooks/Makefile
......
CLEANFILES = $(nodist_pkgpython_PYTHON) *.py[oc]
SUBDIRS = hypervisor
nodist_pkgpython_PYTHON = _autoconf.py
pkgpython_PYTHON = __init__.py backend.py cli.py cmdlib.py config.py \
objects.py errors.py logger.py ssh.py utils.py rpc.py \
bdev.py hypervisor.py opcodes.py mcpu.py constants.py \
bdev.py opcodes.py mcpu.py constants.py \
ssconf.py locking.py luxi.py jqueue.py serializer.py
python_files = $(pkgpython_PYTHON)
......
#
#
# Copyright (C) 2006, 2007, 2008 Google 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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
"""Base class for all hypervisors
"""
class BaseHypervisor(object):
"""Abstract virtualisation technology interface
The goal is that all aspects of the virtualisation technology must
be abstracted away from the rest of code.
"""
def __init__(self):
pass
def StartInstance(self, instance, block_devices, extra_args):
"""Start an instance."""
raise NotImplementedError
def StopInstance(self, instance, force=False):
"""Stop an instance."""
raise NotImplementedError
def RebootInstance(self, instance):
"""Reboot an instance."""
raise NotImplementedError
def ListInstances(self):
"""Get the list of running instances."""
raise NotImplementedError
def GetInstanceInfo(self, instance_name):
"""Get instance properties.
Args:
instance_name: the instance name
Returns:
(name, id, memory, vcpus, state, times)
"""
raise NotImplementedError
def GetAllInstancesInfo(self):
"""Get properties of all instances.
Returns:
[(name, id, memory, vcpus, stat, times),...]
"""
raise NotImplementedError
def GetNodeInfo(self):
"""Return information about the node.
The return value is a dict, which has to have the following items:
(all values in MiB)
- memory_total: the total memory size on the node
- memory_free: the available memory on the node for instances
- memory_dom0: the memory used by the node itself, if available
"""
raise NotImplementedError
@staticmethod
def GetShellCommandForConsole(instance):
"""Return a command for connecting to the console of an instance.
"""
raise NotImplementedError
def Verify(self):
"""Verify the hypervisor.
"""
raise NotImplementedError
#
#
# Copyright (C) 2006, 2007, 2008 Google 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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
"""Fake hypervisor
"""
import os
import os.path
from ganeti import utils
from ganeti import constants
from ganeti import errors
from ganeti.hypervisor import BaseHypervisor
class FakeHypervisor(BaseHypervisor.BaseHypervisor):
"""Fake hypervisor interface.
This can be used for testing the ganeti code without having to have
a real virtualisation software installed.
"""
_ROOT_DIR = constants.RUN_DIR + "/ganeti-fake-hypervisor"
def __init__(self):
BaseHypervisor.__init__(self)
if not os.path.exists(self._ROOT_DIR):
os.mkdir(self._ROOT_DIR)
def ListInstances(self):
"""Get the list of running instances.
"""
return os.listdir(self._ROOT_DIR)
def GetInstanceInfo(self, instance_name):
"""Get instance properties.
Args:
instance_name: the instance name
Returns:
(name, id, memory, vcpus, stat, times)
"""
file_name = "%s/%s" % (self._ROOT_DIR, instance_name)
if not os.path.exists(file_name):
return None
try:
fh = file(file_name, "r")
try:
inst_id = fh.readline().strip()
memory = fh.readline().strip()
vcpus = fh.readline().strip()
stat = "---b-"
times = "0"
return (instance_name, inst_id, memory, vcpus, stat, times)
finally:
fh.close()
except IOError, err:
raise errors.HypervisorError("Failed to list instance %s: %s" %
(instance_name, err))
def GetAllInstancesInfo(self):
"""Get properties of all instances.
Returns:
[(name, id, memory, vcpus, stat, times),...]
"""
data = []
for file_name in os.listdir(self._ROOT_DIR):
try:
fh = file(self._ROOT_DIR+"/"+file_name, "r")
inst_id = "-1"
memory = "0"
stat = "-----"
times = "-1"
try:
inst_id = fh.readline().strip()
memory = fh.readline().strip()
vcpus = fh.readline().strip()
stat = "---b-"
times = "0"
finally:
fh.close()
data.append((file_name, inst_id, memory, vcpus, stat, times))
except IOError, err:
raise errors.HypervisorError("Failed to list instances: %s" % err)
return data
def StartInstance(self, instance, force, extra_args):
"""Start an instance.
For the fake hypervisor, it just creates a file in the base dir,
creating an exception if it already exists. We don't actually
handle race conditions properly, since these are *FAKE* instances.
"""
file_name = self._ROOT_DIR + "/%s" % instance.name
if os.path.exists(file_name):
raise errors.HypervisorError("Failed to start instance %s: %s" %
(instance.name, "already running"))
try:
fh = file(file_name, "w")
try:
fh.write("0\n%d\n%d\n" % (instance.memory, instance.vcpus))
finally:
fh.close()
except IOError, err:
raise errors.HypervisorError("Failed to start instance %s: %s" %
(instance.name, err))
def StopInstance(self, instance, force=False):
"""Stop an instance.
For the fake hypervisor, this just removes the file in the base
dir, if it exist, otherwise we raise an exception.
"""
file_name = self._ROOT_DIR + "/%s" % instance.name
if not os.path.exists(file_name):
raise errors.HypervisorError("Failed to stop instance %s: %s" %
(instance.name, "not running"))
utils.RemoveFile(file_name)
def RebootInstance(self, instance):
"""Reboot an instance.
For the fake hypervisor, this does nothing.
"""
return
def GetNodeInfo(self):
"""Return information about the node.
The return value is a dict, which has to have the following items:
(all values in MiB)
- memory_total: the total memory size on the node
- memory_free: the available memory on the node for instances
- memory_dom0: the memory used by the node itself, if available
"""
# global ram usage from the xm info command
# memory : 3583
# free_memory : 747
# note: in xen 3, memory has changed to total_memory
try:
fh = file("/proc/meminfo")
try:
data = fh.readlines()
finally:
fh.close()
except IOError, err:
raise errors.HypervisorError("Failed to list node info: %s" % err)
result = {}
sum_free = 0
for line in data:
splitfields = line.split(":", 1)
if len(splitfields) > 1:
key = splitfields[0].strip()
val = splitfields[1].strip()
if key == 'MemTotal':
result['memory_total'] = int(val.split()[0])/1024
elif key in ('MemFree', 'Buffers', 'Cached'):
sum_free += int(val.split()[0])/1024
elif key == 'Active':
result['memory_dom0'] = int(val.split()[0])/1024
result['memory_free'] = sum_free
return result
@staticmethod
def GetShellCommandForConsole(instance):
"""Return a command for connecting to the console of an instance.
"""
return "echo Console not available for fake hypervisor"
def Verify(self):
"""Verify the hypervisor.
For the fake hypervisor, it just checks the existence of the base
dir.
"""
if not os.path.exists(self._ROOT_DIR):
return "The required directory '%s' does not exist." % self._ROOT_DIR
CLEANFILES = *.py[oc]
hypervisordir = $(pkgpythondir)/hypervisor
hypervisor_PYTHON = __init__.py XenHypervisor.py FakeHypervisor.py \
BaseHypervisor.py
python_files = $(hypervisor_PYTHON)
include $(srcdir)/../Makefile.libcommon
#
#
# Copyright (C) 2006, 2007, 2008 Google 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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.
"""Xen hypervisors
"""
import os
import os.path
import time
from cStringIO import StringIO
from ganeti import constants
from ganeti import errors
from ganeti import logger
from ganeti import utils
from ganeti.hypervisor import BaseHypervisor
class XenHypervisor(BaseHypervisor.BaseHypervisor):
"""Xen generic hypervisor interface
This is the Xen base class used for both Xen PVM and HVM. It contains
all the functionality that is identical for both.
"""
@staticmethod
def _WriteConfigFile(instance, block_devices, extra_args):
"""Write the Xen config file for the instance.
"""
raise NotImplementedError
@staticmethod
def _RemoveConfigFile(instance):
"""Remove the xen configuration file.
"""
utils.RemoveFile("/etc/xen/%s" % instance.name)
@staticmethod
def _GetXMList(include_node):
"""Return the list of running instances.
If the `include_node` argument is True, then we return information
for dom0 also, otherwise we filter that from the return value.
The return value is a list of (name, id, memory, vcpus, state, time spent)
"""
for dummy in range(5):
result = utils.RunCmd(["xm", "list"])
if not result.failed:
break
logger.Error("xm list failed (%s): %s" % (result.fail_reason,
result.output))
time.sleep(1)
if result.failed:
raise errors.HypervisorError("xm list failed, retries"
" exceeded (%s): %s" %
(result.fail_reason, result.stderr))
# skip over the heading
lines = result.stdout.splitlines()[1:]
result = []
for line in lines:
# The format of lines is:
# Name ID Mem(MiB) VCPUs State Time(s)
# Domain-0 0 3418 4 r----- 266.2
data = line.split()
if len(data) != 6:
raise errors.HypervisorError("Can't parse output of xm list,"
" line: %s" % line)
try:
data[1] = int(data[1])
data[2] = int(data[2])
data[3] = int(data[3])
data[5] = float(data[5])
except ValueError, err:
raise errors.HypervisorError("Can't parse output of xm list,"
" line: %s, error: %s" % (line, err))
# skip the Domain-0 (optional)
if include_node or data[0] != 'Domain-0':
result.append(data)
return result
def ListInstances(self):
"""Get the list of running instances.
"""
xm_list = self._GetXMList(False)
names = [info[0] for info in xm_list]
return names
def GetInstanceInfo(self, instance_name):
"""Get instance properties.
Args:
instance_name: the instance name
Returns:
(name, id, memory, vcpus, stat, times)
"""
xm_list = self._GetXMList(instance_name=="Domain-0")
result = None
for data in xm_list:
if data[0] == instance_name:
result = data
break
return result
def GetAllInstancesInfo(self):
"""Get properties of all instances.
Returns:
[(name, id, memory, vcpus, stat, times),...]
"""
xm_list = self._GetXMList(False)
return xm_list
def StartInstance(self, instance, block_devices, extra_args):
"""Start an instance."""
self._WriteConfigFile(instance, block_devices, extra_args)
result = utils.RunCmd(["xm", "create", instance.name])
if result.failed:
raise errors.HypervisorError("Failed to start instance %s: %s (%s)" %
(instance.name, result.fail_reason,
result.output))
def StopInstance(self, instance, force=False):
"""Stop an instance."""
self._RemoveConfigFile(instance)
if force:
command = ["xm", "destroy", instance.name]
else:
command = ["xm", "shutdown", instance.name]
result = utils.RunCmd(command)
if result.failed:
raise errors.HypervisorError("Failed to stop instance %s: %s" %
(instance.name, result.fail_reason))
def RebootInstance(self, instance):
"""Reboot an instance."""
result = utils.RunCmd(["xm", "reboot", instance.name])
if result.failed:
raise errors.HypervisorError("Failed to reboot instance %s: %s" %
(instance.name, result.fail_reason))
def GetNodeInfo(self):
"""Return information about the node.
The return value is a dict, which has to have the following items:
(all values in MiB)
- memory_total: the total memory size on the node
- memory_free: the available memory on the node for instances
- memory_dom0: the memory used by the node itself, if available
"""
# note: in xen 3, memory has changed to total_memory
result = utils.RunCmd(["xm", "info"])
if result.failed:
logger.Error("Can't run 'xm info': %s" % result.fail_reason)
return None
xmoutput = result.stdout.splitlines()
result = {}
for line in xmoutput:
splitfields = line.split(":", 1)
if len(splitfields) > 1:
key = splitfields[0].strip()
val = splitfields[1].strip()
if key == 'memory' or key == 'total_memory':
result['memory_total'] = int(val)
elif key == 'free_memory':
result['memory_free'] = int(val)
dom0_info = self.GetInstanceInfo("Domain-0")
if dom0_info is not None:
result['memory_dom0'] = dom0_info[2]
return result
@staticmethod
def GetShellCommandForConsole(instance):
"""Return a command for connecting to the console of an instance.
"""
raise NotImplementedError
def Verify(self):
"""Verify the hypervisor.
For Xen, this verifies that the xend process is running.
"""
if not utils.CheckDaemonAlive('/var/run/xend.pid', 'xend'):
return "xend daemon is not running"
@staticmethod
def _GetConfigFileDiskData(disk_template, block_devices):
"""Get disk directive for xen config file.
This method builds the xen config disk directive according to the
given disk_template and block_devices.
Args:
disk_template: String containing instance disk template
block_devices: List[tuple1,tuple2,...]
tuple: (cfdev, rldev)
cfdev: dict containing ganeti config disk part
rldev: ganeti.bdev.BlockDev object
Returns:
String containing disk directive for xen instance config file
"""
FILE_DRIVER_MAP = {
constants.FD_LOOP: "file",
constants.FD_BLKTAP: "tap:aio",
}
disk_data = []
for cfdev, rldev in block_devices:
if cfdev.dev_type == constants.LD_FILE:
line = "'%s:%s,%s,w'" % (FILE_DRIVER_MAP[cfdev.physical_id[0]],
rldev.dev_path, cfdev.iv_name)
else:
line = "'phy:%s,%s,w'" % (rldev.dev_path, cfdev.iv_name)
disk_data.append(line)
return disk_data
class XenPvmHypervisor(XenHypervisor):
"""Xen PVM hypervisor interface"""
@classmethod
def _WriteConfigFile(cls, instance, block_devices, extra_args):
"""Write the Xen config file for the instance.
"""
config = StringIO()
config.write("# this is autogenerated by Ganeti, please do not edit\n#\n")
# kernel handling
if instance.kernel_path in (None, constants.VALUE_DEFAULT):
kpath = constants.XEN_KERNEL
else:
if not os.path.exists(instance.kernel_path):
raise errors.HypervisorError("The kernel %s for instance %s is"
" missing" % (instance.kernel_path,
instance.name))