#
#
-# Copyright (C) 2006, 2007, 2008 Google Inc.
+# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2012, 2013 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
import os
import re
+import logging
from ganeti import errors
from ganeti import utils
+from ganeti import constants
+
+
+def _IsCpuMaskWellFormed(cpu_mask):
+ """Verifies if the given single CPU mask is valid
+
+ The single CPU mask should be in the form "a,b,c,d", where each
+ letter is a positive number or range.
+
+ """
+ try:
+ cpu_list = utils.ParseCpuMask(cpu_mask)
+ except errors.ParseError, _:
+ return False
+ return isinstance(cpu_list, list) and len(cpu_list) > 0
+
+
+def _IsMultiCpuMaskWellFormed(cpu_mask):
+ """Verifies if the given multiple CPU mask is valid
+
+ A valid multiple CPU mask is in the form "a:b:c:d", where each
+ letter is a single CPU mask.
+
+ """
+ try:
+ utils.ParseMultiCpuMask(cpu_mask)
+ except errors.ParseError, _:
+ return False
+
+ return True
# Read the BaseHypervisor.PARAMETERS docstring for the syntax of the
# _CHECK values
# must be afile
-_FILE_CHECK = (os.path.isabs, "must be an absolute path",
- os.path.isfile, "not found or not a file")
+_FILE_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
+ os.path.isfile, "not found or not a file")
# must be a directory
-_DIR_CHECK = (os.path.isabs, "must be an absolute path",
- os.path.isdir, "not found or not a directory")
+_DIR_CHECK = (utils.IsNormAbsPath, "must be an absolute normalized path",
+ os.path.isdir, "not found or not a directory")
+
+# CPU mask must be well-formed
+# TODO: implement node level check for the CPU mask
+_CPU_MASK_CHECK = (_IsCpuMaskWellFormed,
+ "CPU mask definition is not well-formed",
+ None, None)
+
+# Multiple CPU mask must be well-formed
+_MULTI_CPU_MASK_CHECK = (_IsMultiCpuMaskWellFormed,
+ "Multiple CPU mask definition is not well-formed",
+ None, None)
+
+# Check for validity of port number
+_NET_PORT_CHECK = (lambda x: 0 < x < 65535, "invalid port number",
+ None, None)
+
+# Check that an integer is non negative
+_NONNEGATIVE_INT_CHECK = (lambda x: x >= 0, "cannot be negative", None, None)
# nice wrappers for users
REQ_FILE_CHECK = (True, ) + _FILE_CHECK
OPT_FILE_CHECK = (False, ) + _FILE_CHECK
REQ_DIR_CHECK = (True, ) + _DIR_CHECK
OPT_DIR_CHECK = (False, ) + _DIR_CHECK
+REQ_NET_PORT_CHECK = (True, ) + _NET_PORT_CHECK
+OPT_NET_PORT_CHECK = (False, ) + _NET_PORT_CHECK
+REQ_CPU_MASK_CHECK = (True, ) + _CPU_MASK_CHECK
+OPT_CPU_MASK_CHECK = (False, ) + _CPU_MASK_CHECK
+REQ_MULTI_CPU_MASK_CHECK = (True, ) + _MULTI_CPU_MASK_CHECK
+OPT_MULTI_CPU_MASK_CHECK = (False, ) + _MULTI_CPU_MASK_CHECK
+REQ_NONNEGATIVE_INT_CHECK = (True, ) + _NONNEGATIVE_INT_CHECK
+OPT_NONNEGATIVE_INT_CHECK = (False, ) + _NONNEGATIVE_INT_CHECK
# no checks at all
NO_CHECK = (False, None, None, None, None)
# required, but no other checks
REQUIRED_CHECK = (True, None, None, None, None)
+# migration type
+MIGRATION_MODE_CHECK = (True, lambda x: x in constants.HT_MIGRATION_MODES,
+ "invalid migration mode", None, None)
+
+
def ParamInSet(required, my_set):
"""Builds parameter checker for set membership.
- a function to check for parameter validity on the remote node,
in the L{ValidateParameters} function
- an error message for the above function
+ @type CAN_MIGRATE: boolean
+ @cvar CAN_MIGRATE: whether this hypervisor can do migration (either
+ live or non-live)
"""
PARAMETERS = {}
+ ANCILLARY_FILES = []
+ ANCILLARY_FILES_OPT = []
+ CAN_MIGRATE = False
- def __init__(self):
- pass
-
- def StartInstance(self, instance, block_devices):
+ def StartInstance(self, instance, block_devices, startup_paused):
"""Start an instance."""
raise NotImplementedError
- def StopInstance(self, instance, force=False):
- """Stop an instance."""
+ def StopInstance(self, instance, force=False, retry=False, name=None):
+ """Stop an instance
+
+ @type instance: L{objects.Instance}
+ @param instance: instance to stop
+ @type force: boolean
+ @param force: whether to do a "hard" stop (destroy)
+ @type retry: boolean
+ @param retry: whether this is just a retry call
+ @type name: string or None
+ @param name: if this parameter is passed, the the instance object
+ should not be used (will be passed as None), and the shutdown
+ must be done by name only
+
+ """
raise NotImplementedError
+ def CleanupInstance(self, instance_name):
+ """Cleanup after a stopped instance
+
+ This is an optional method, used by hypervisors that need to cleanup after
+ an instance has been stopped.
+
+ @type instance_name: string
+ @param instance_name: instance name to cleanup after
+
+ """
+ pass
+
def RebootInstance(self, instance):
"""Reboot an instance."""
raise NotImplementedError
- def ListInstances(self):
+ def ListInstances(self, hvparams=None):
"""Get the list of running instances."""
raise NotImplementedError
- def GetInstanceInfo(self, instance_name):
+ def GetInstanceInfo(self, instance_name, hvparams=None):
"""Get instance properties.
@type instance_name: string
@param instance_name: the instance name
+ @type hvparams: dict of strings
+ @param hvparams: hvparams to be used with this instance
@return: tuple (name, id, memory, vcpus, state, times)
"""
raise NotImplementedError
- def GetAllInstancesInfo(self):
+ def GetAllInstancesInfo(self, hvparams=None):
"""Get properties of all instances.
+ @type hvparams: dict of strings
+ @param hvparams: hypervisor parameter
@return: list of tuples (name, id, memory, vcpus, stat, times)
"""
raise NotImplementedError
- def GetNodeInfo(self):
+ def GetNodeInfo(self, hvparams=None):
"""Return information about the node.
+ @type hvparams: dict of strings
+ @param hvparams: hypervisor parameters
+
@return: a dict with the following keys (values in MiB):
- memory_total: the total memory size on the node
- memory_free: the available memory on the node for instances
raise NotImplementedError
@classmethod
- def GetShellCommandForConsole(cls, instance, hvparams, beparams):
- """Return a command for connecting to the console of an instance.
+ def GetInstanceConsole(cls, instance, hvparams, beparams):
+ """Return information for connecting to the console of an instance.
"""
raise NotImplementedError
- def Verify(self):
+ @classmethod
+ def GetAncillaryFiles(cls):
+ """Return a list of ancillary files to be copied to all nodes as ancillary
+ configuration files.
+
+ @rtype: (list of absolute paths, list of absolute paths)
+ @return: (all files, optional files)
+
+ """
+ # By default we return a member variable, so that if an hypervisor has just
+ # a static list of files it doesn't have to override this function.
+ assert set(cls.ANCILLARY_FILES).issuperset(cls.ANCILLARY_FILES_OPT), \
+ "Optional ancillary files must be a subset of ancillary files"
+
+ return (cls.ANCILLARY_FILES, cls.ANCILLARY_FILES_OPT)
+
+ def Verify(self, hvparams=None):
"""Verify the hypervisor.
+ @type hvparams: dict of strings
+ @param hvparams: hypervisor parameters to be verified against
+
+ @return: Problem description if something is wrong, C{None} otherwise
+
"""
raise NotImplementedError
- def MigrationInfo(self, instance):
+ def MigrationInfo(self, instance): # pylint: disable=R0201,W0613
"""Get instance information to perform a migration.
By default assume no information is needed.
@return: instance migration information - serialized form
"""
- return ''
+ return ""
def AcceptInstance(self, instance, info, target):
"""Prepare to accept an instance.
"""
pass
- def FinalizeMigration(self, instance, info, success):
- """Finalized an instance migration.
+ def BalloonInstanceMemory(self, instance, mem):
+ """Balloon an instance memory to a certain value.
+
+ @type instance: L{objects.Instance}
+ @param instance: instance to be accepted
+ @type mem: int
+ @param mem: actual memory size to use for instance runtime
+
+ """
+ raise NotImplementedError
+
+ def FinalizeMigrationDst(self, instance, info, success):
+ """Finalize the instance migration on the target node.
Should finalize or revert any preparation done to accept the instance.
Since by default we do no preparation, we also don't have anything to do
@type instance: L{objects.Instance}
- @param instance: instance whose migration is being aborted
+ @param instance: instance whose migration is being finalized
@type info: string/data (opaque)
@param info: migration information, from the source node
@type success: boolean
"""
pass
- def MigrateInstance(self, name, target, live):
+ def MigrateInstance(self, cluster_name, instance, target, live):
"""Migrate an instance.
- @type name: string
- @param name: name of the instance to be migrated
+ @type cluster_name: string
+ @param cluster_name: name of the cluster
+ @type instance: L{objects.Instance}
+ @param instance: the instance to be migrated
@type target: string
@param target: hostname (usually ip) of the target node
@type live: boolean
"""
raise NotImplementedError
+ def FinalizeMigrationSource(self, instance, success, live):
+ """Finalize the instance migration on the source node.
+
+ @type instance: L{objects.Instance}
+ @param instance: the instance that was migrated
+ @type success: bool
+ @param success: whether the migration succeeded or not
+ @type live: bool
+ @param live: whether the user requested a live migration or not
+
+ """
+ pass
+
+ def GetMigrationStatus(self, instance):
+ """Get the migration status
+
+ @type instance: L{objects.Instance}
+ @param instance: the instance that is being migrated
+ @rtype: L{objects.MigrationStatus}
+ @return: the status of the current migration (one of
+ L{constants.HV_MIGRATION_VALID_STATUSES}), plus any additional
+ progress info that can be retrieved from the hypervisor
+
+ """
+ raise NotImplementedError
+
+ def _InstanceStartupMemory(self, instance, hvparams=None):
+ """Get the correct startup memory for an instance
+
+ This function calculates how much memory an instance should be started
+ with, making sure it's a value between the minimum and the maximum memory,
+ but also trying to use no more than the current free memory on the node.
+
+ @type instance: L{objects.Instance}
+ @param instance: the instance that is being started
+ @rtype: integer
+ @return: memory the instance should be started with
+
+ """
+ free_memory = self.GetNodeInfo(hvparams=hvparams)["memory_free"]
+ max_start_mem = min(instance.beparams[constants.BE_MAXMEM], free_memory)
+ start_mem = max(instance.beparams[constants.BE_MINMEM], max_start_mem)
+ return start_mem
+
@classmethod
def CheckParameterSyntax(cls, hvparams):
"""Check the given parameters for validity.
" validation: %s (current value: '%s')" %
(name, errstr, value))
- def GetLinuxNodeInfo(self):
+ @classmethod
+ def PowercycleNode(cls, hvparams=None):
+ """Hard powercycle a node using hypervisor specific methods.
+
+ This method should hard powercycle the node, using whatever
+ methods the hypervisor provides. Note that this means that all
+ instances running on the node must be stopped too.
+
+ @type hvparams: dict of strings
+ @param hvparams: hypervisor params to be used on this node
+
+ """
+ raise NotImplementedError
+
+ @staticmethod
+ def GetLinuxNodeInfo():
"""For linux systems, return actual OS information.
This is an abstraction for all non-hypervisor-based classes, where
"""
try:
- fh = file("/proc/meminfo")
- try:
- data = fh.readlines()
- finally:
- fh.close()
+ data = utils.ReadFile("/proc/meminfo").splitlines()
except EnvironmentError, err:
raise errors.HypervisorError("Failed to list node info: %s" % (err,))
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
+ 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
except (ValueError, TypeError), err:
raise errors.HypervisorError("Failed to compute memory usage: %s" %
(err,))
- result['memory_free'] = sum_free
+ result["memory_free"] = sum_free
cpu_total = 0
try:
fh.close()
except EnvironmentError, err:
raise errors.HypervisorError("Failed to list node info: %s" % (err,))
- result['cpu_total'] = cpu_total
+ result["cpu_total"] = cpu_total
# FIXME: export correct data here
- result['cpu_nodes'] = 1
- result['cpu_sockets'] = 1
+ result["cpu_nodes"] = 1
+ result["cpu_sockets"] = 1
return result
+
+ @classmethod
+ def LinuxPowercycle(cls):
+ """Linux-specific powercycle method.
+
+ """
+ try:
+ fd = os.open("/proc/sysrq-trigger", os.O_WRONLY)
+ try:
+ os.write(fd, "b")
+ finally:
+ fd.close()
+ except OSError:
+ logging.exception("Can't open the sysrq-trigger file")
+ result = utils.RunCmd(["reboot", "-n", "-f"])
+ if not result:
+ logging.error("Can't run shutdown: %s", result.output)
+
+ @staticmethod
+ def _FormatVerifyResults(msgs):
+ """Formats the verification results, given a list of errors.
+
+ @param msgs: list of errors, possibly empty
+ @return: overall problem description if something is wrong,
+ C{None} otherwise
+
+ """
+ if msgs:
+ return "; ".join(msgs)
+ else:
+ return None