Commit 205ab586 authored by Iustin Pop's avatar Iustin Pop
Browse files

Move to data-based hvparam checks instead of code



Currently the hypervisor parameters are checked using hard-coded snippets in
each hypervisor. However, most parameter checks fall into three cases:
  - file check
  - directory check
  - string value in a set

And the remaining ones are checked using simple functions.

This patch moves to a declarative-style for these parameter checks; in
hv_base we add the necessary infrastructure for these checks, and the
above common cases.

This translates into complete removal of the Check/Verify functions for
the Xen hypervisors, and a drastic reduction for the KVM one (which has
inter-parameter dependencies and thus can't use a simple table).
Signed-off-by: default avatarIustin Pop <iustin@google.com>
Reviewed-by: default avatarGuido Trotter <ultrotter@google.com>
parent 622533d5
......@@ -21,12 +21,65 @@
"""Base class for all hypervisors
The syntax for the _CHECK variables and the contents of the PARAMETERS
dict is the same, see the docstring for L{BaseHypervisor.PARAMETERS}.
@var _FILE_CHECK: stub for file checks, without the required flag
@var _DIR_CHECK: stub for directory checks, without the required flag
@var REQ_FILE_CHECK: mandatory file parameter
@var OPT_FILE_CHECK: optional file parameter
@var REQ_DIR_CHECK: mandatory directory parametr
@var OPT_DIR_CHECK: optional directory parameter
@var NO_CHECK: parameter without any checks at all
@var REQUIRED_CHECK: parameter required to exist (and non-false), but
without other checks; beware that this can't be used for boolean
parameters, where you should use NO_CHECK or a custom checker
"""
import os
import re
from ganeti import errors
from ganeti import utils
# 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")
# must be a directory
_DIR_CHECK = (os.path.isabs, "must be an absolute path",
os.path.isdir, "not found or not a directory")
# 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
# no checks at all
NO_CHECK = (False, None, None, None, None)
# required, but no other checks
REQUIRED_CHECK = (True, None, None, None, None)
def ParamInSet(required, my_set):
"""Builds parameter checker for set membership.
@type required: boolean
@param required: whether this is a required parameter
@type my_set: tuple, list or set
@param my_set: allowed values set
"""
fn = lambda x: x in my_set
err = ("The value must be one of: %s" % utils.CommaJoin(my_set))
return (required, fn, err, None, None)
class BaseHypervisor(object):
......@@ -35,8 +88,18 @@ class BaseHypervisor(object):
The goal is that all aspects of the virtualisation technology are
abstracted away from the rest of code.
@cvar PARAMETERS: a dict of parameter name: check type; the check type is
a five-tuple containing:
- the required flag (boolean)
- a function to check for syntax, that will be used in
L{CheckParameterSyntax}, in the master daemon process
- an error message for the above function
- a function to check for parameter validity on the remote node,
in the L{ValidateParameters} function
- an error message for the above function
"""
PARAMETERS = []
PARAMETERS = {}
def __init__(self):
pass
......@@ -171,14 +234,25 @@ class BaseHypervisor(object):
"""
for key in hvparams:
if key not in cls.PARAMETERS:
raise errors.HypervisorError("Hypervisor parameter '%s'"
" not supported" % key)
for key in cls.PARAMETERS:
if key not in hvparams:
raise errors.HypervisorError("Hypervisor parameter '%s'"
" missing" % key)
def ValidateParameters(self, hvparams):
raise errors.HypervisorError("Parameter '%s' is not supported" % key)
# cheap tests that run on the master, should not access the world
for name, (required, check_fn, errstr, _, _) in cls.PARAMETERS.items():
if name not in hvparams:
raise errors.HypervisorError("Parameter '%s' is missing" % name)
value = hvparams[name]
if not required and not value:
continue
if not value:
raise errors.HypervisorError("Parameter '%s' is required but"
" is currently not defined" % (name, ))
if check_fn is not None and not check_fn(value):
raise errors.HypervisorError("Parameter '%s' fails syntax"
" check: %s (current value: '%s')" %
(name, errstr, value))
@classmethod
def ValidateParameters(cls, hvparams):
"""Check the given parameters for validity.
This should check the passed set of parameters for
......@@ -189,7 +263,14 @@ class BaseHypervisor(object):
@raise errors.HypervisorError: when a parameter is not valid
"""
pass
for name, (required, _, _, check_fn, errstr) in cls.PARAMETERS.items():
value = hvparams[name]
if not required and not value:
continue
if check_fn is not None and not check_fn(value):
raise errors.HypervisorError("Parameter '%s' fails"
" validation: %s (current value: '%s')" %
(name, errstr, value))
def GetLinuxNodeInfo(self):
"""For linux systems, return actual OS information.
......
......@@ -48,23 +48,30 @@ class KVMHypervisor(hv_base.BaseHypervisor):
_CONF_DIR = _ROOT_DIR + "/conf" # contains instances startup data
_DIRS = [_ROOT_DIR, _PIDS_DIR, _CTRL_DIR, _CONF_DIR]
PARAMETERS = [
constants.HV_KERNEL_PATH,
constants.HV_INITRD_PATH,
constants.HV_ROOT_PATH,
constants.HV_KERNEL_ARGS,
constants.HV_ACPI,
constants.HV_SERIAL_CONSOLE,
constants.HV_VNC_BIND_ADDRESS,
constants.HV_VNC_TLS,
constants.HV_VNC_X509,
constants.HV_VNC_X509_VERIFY,
constants.HV_CDROM_IMAGE_PATH,
constants.HV_BOOT_ORDER,
constants.HV_NIC_TYPE,
constants.HV_DISK_TYPE,
constants.HV_USB_MOUSE,
]
PARAMETERS = {
constants.HV_KERNEL_PATH: hv_base.OPT_FILE_CHECK,
constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
constants.HV_ROOT_PATH: hv_base.NO_CHECK,
constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
constants.HV_ACPI: hv_base.NO_CHECK,
constants.HV_SERIAL_CONSOLE: hv_base.NO_CHECK,
constants.HV_VNC_BIND_ADDRESS: \
(False, lambda x: (utils.IsValidIP(x) or os.path.isabs(x)),
"the VNC bind address must be either a valid IP address or an absolute"
" pathname", None, None),
constants.HV_VNC_TLS: hv_base.NO_CHECK,
constants.HV_VNC_X509: hv_base.OPT_DIR_CHECK,
constants.HV_VNC_X509_VERIFY: hv_base.NO_CHECK,
constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
constants.HV_BOOT_ORDER: \
hv_base.ParamInSet(True, constants.HT_KVM_VALID_BO_TYPES),
constants.HV_NIC_TYPE: \
hv_base.ParamInSet(True, constants.HT_KVM_VALID_NIC_TYPES),
constants.HV_DISK_TYPE: \
hv_base.ParamInSet(True, constants.HT_KVM_VALID_DISK_TYPES),
constants.HV_USB_MOUSE: \
hv_base.ParamInSet(False, constants.HT_KVM_VALID_MOUSE_TYPES),
}
_MIGRATION_STATUS_RE = re.compile('Migration\s+status:\s+(\w+)',
re.M | re.I)
......@@ -688,109 +695,23 @@ class KVMHypervisor(hv_base.BaseHypervisor):
kernel_path = hvparams[constants.HV_KERNEL_PATH]
if kernel_path:
if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
raise errors.HypervisorError("The kernel path must be an absolute path"
", if defined")
if not hvparams[constants.HV_ROOT_PATH]:
raise errors.HypervisorError("Need a root partition for the instance"
", if a kernel is defined")
if hvparams[constants.HV_INITRD_PATH]:
if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
raise errors.HypervisorError("The initrd path must an absolute path"
", if defined")
raise errors.HypervisorError("Need a root partition for the instance,"
" if a kernel is defined")
vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
if vnc_bind_address:
if not utils.IsValidIP(vnc_bind_address):
if not os.path.isabs(vnc_bind_address):
raise errors.HypervisorError("The VNC bind address must be either"
" a valid IP address or an absolute"
" pathname. '%s' given" %
vnc_bind_address)
if hvparams[constants.HV_VNC_X509_VERIFY] and \
not hvparams[constants.HV_VNC_X509]:
raise errors.HypervisorError("%s must be defined, if %s is" %
(constants.HV_VNC_X509,
constants.HV_VNC_X509_VERIFY))
if hvparams[constants.HV_VNC_X509]:
if not os.path.isabs(hvparams[constants.HV_VNC_X509]):
raise errors.HypervisorError("The vnc x509 path must an absolute path"
", if defined")
iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
if iso_path and not os.path.isabs(iso_path):
raise errors.HypervisorError("The path to the CDROM image must be"
" an absolute path, if defined")
if (hvparams[constants.HV_VNC_X509_VERIFY] and
not hvparams[constants.HV_VNC_X509]):
raise errors.HypervisorError("%s must be defined, if %s is" %
(constants.HV_VNC_X509,
constants.HV_VNC_X509_VERIFY))
boot_order = hvparams[constants.HV_BOOT_ORDER]
if boot_order not in constants.HT_KVM_VALID_BO_TYPES:
raise errors.HypervisorError(\
"The boot order must be one of %s" %
utils.CommaJoin(constants.HT_KVM_VALID_BO_TYPES))
if boot_order == constants.HT_BO_CDROM and not iso_path:
if (boot_order == constants.HT_BO_CDROM and
not hvparams[constants.HV_CDROM_IMAGE_PATH]):
raise errors.HypervisorError("Cannot boot from cdrom without an"
" ISO path")
nic_type = hvparams[constants.HV_NIC_TYPE]
if nic_type not in constants.HT_KVM_VALID_NIC_TYPES:
raise errors.HypervisorError(\
"Invalid NIC type %s specified for the KVM"
" hypervisor. Please choose one of: %s" %
(nic_type, utils.CommaJoin(constants.HT_KVM_VALID_NIC_TYPES)))
elif (boot_order == constants.HT_BO_NETWORK and
nic_type == constants.HT_NIC_PARAVIRTUAL):
if (boot_order == constants.HT_BO_NETWORK and
hvparams[constants.HV_NIC_TYPE] == constants.HT_NIC_PARAVIRTUAL):
raise errors.HypervisorError("Cannot boot from a paravirtual NIC. Please"
" change the NIC type.")
disk_type = hvparams[constants.HV_DISK_TYPE]
if disk_type not in constants.HT_KVM_VALID_DISK_TYPES:
raise errors.HypervisorError(\
"Invalid disk type %s specified for the KVM"
" hypervisor. Please choose one of: %s" %
(disk_type, utils.CommaJoin(constants.HT_KVM_VALID_DISK_TYPES)))
mouse_type = hvparams[constants.HV_USB_MOUSE]
if mouse_type and mouse_type not in constants.HT_KVM_VALID_MOUSE_TYPES:
raise errors.HypervisorError(\
"Invalid usb mouse type %s specified for the KVM hypervisor. Please"
" choose one of %s" %
utils.CommaJoin(constants.HT_KVM_VALID_MOUSE_TYPES))
def ValidateParameters(self, hvparams):
"""Check the given parameters for validity.
For the KVM hypervisor, this checks the existence of the
kernel.
"""
super(KVMHypervisor, self).ValidateParameters(hvparams)
kernel_path = hvparams[constants.HV_KERNEL_PATH]
if kernel_path and not os.path.isfile(kernel_path):
raise errors.HypervisorError("Instance kernel '%s' not found or"
" not a file" % kernel_path)
initrd_path = hvparams[constants.HV_INITRD_PATH]
if initrd_path and not os.path.isfile(initrd_path):
raise errors.HypervisorError("Instance initrd '%s' not found or"
" not a file" % initrd_path)
vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
if vnc_bind_address and not utils.IsValidIP(vnc_bind_address) and \
not os.path.isdir(vnc_bind_address):
raise errors.HypervisorError("Instance vnc bind address must be either"
" an ip address or an existing directory")
vnc_x509 = hvparams[constants.HV_VNC_X509]
if vnc_x509 and not os.path.isdir(vnc_x509):
raise errors.HypervisorError("Instance vnc x509 path '%s' not found"
" or not a directory" % vnc_x509)
iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
if iso_path and not os.path.isfile(iso_path):
raise errors.HypervisorError("Instance cdrom image '%s' not found or"
" not a file" % iso_path)
......@@ -401,58 +401,12 @@ class XenHypervisor(hv_base.BaseHypervisor):
class XenPvmHypervisor(XenHypervisor):
"""Xen PVM hypervisor interface"""
PARAMETERS = [
constants.HV_KERNEL_PATH,
constants.HV_INITRD_PATH,
constants.HV_ROOT_PATH,
constants.HV_KERNEL_ARGS,
]
@classmethod
def CheckParameterSyntax(cls, hvparams):
"""Check the given parameters for validity.
For the PVM hypervisor, this only check the existence of the
kernel.
@type hvparams: dict
@param hvparams: dictionary with parameter names/value
@raise errors.HypervisorError: when a parameter is not valid
"""
super(XenPvmHypervisor, cls).CheckParameterSyntax(hvparams)
if not hvparams[constants.HV_KERNEL_PATH]:
raise errors.HypervisorError("Need a kernel for the instance")
if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
raise errors.HypervisorError("The kernel path must be an absolute path")
if not hvparams[constants.HV_ROOT_PATH]:
raise errors.HypervisorError("Need a root partition for the instance")
if hvparams[constants.HV_INITRD_PATH]:
if not os.path.isabs(hvparams[constants.HV_INITRD_PATH]):
raise errors.HypervisorError("The initrd path must be an absolute path"
", if defined")
def ValidateParameters(self, hvparams):
"""Check the given parameters for validity.
For the PVM hypervisor, this only check the existence of the
kernel.
"""
super(XenPvmHypervisor, self).ValidateParameters(hvparams)
kernel_path = hvparams[constants.HV_KERNEL_PATH]
if not os.path.isfile(kernel_path):
raise errors.HypervisorError("Instance kernel '%s' not found or"
" not a file" % kernel_path)
initrd_path = hvparams[constants.HV_INITRD_PATH]
if initrd_path and not os.path.isfile(initrd_path):
raise errors.HypervisorError("Instance initrd '%s' not found or"
" not a file" % initrd_path)
PARAMETERS = {
constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
constants.HV_INITRD_PATH: hv_base.OPT_FILE_CHECK,
constants.HV_ROOT_PATH: hv_base.REQUIRED_CHECK,
constants.HV_KERNEL_ARGS: hv_base.NO_CHECK,
}
@classmethod
def _WriteConfigFile(cls, instance, block_devices):
......@@ -510,100 +464,24 @@ class XenPvmHypervisor(XenHypervisor):
class XenHvmHypervisor(XenHypervisor):
"""Xen HVM hypervisor interface"""
PARAMETERS = [
constants.HV_ACPI,
constants.HV_BOOT_ORDER,
constants.HV_CDROM_IMAGE_PATH,
constants.HV_DISK_TYPE,
constants.HV_NIC_TYPE,
constants.HV_PAE,
constants.HV_VNC_BIND_ADDRESS,
constants.HV_KERNEL_PATH,
constants.HV_DEVICE_MODEL,
]
@classmethod
def CheckParameterSyntax(cls, hvparams):
"""Check the given parameter syntax.
"""
super(XenHvmHypervisor, cls).CheckParameterSyntax(hvparams)
# boot order verification
boot_order = hvparams[constants.HV_BOOT_ORDER]
if not boot_order or len(boot_order.strip("acdn")) != 0:
raise errors.HypervisorError("Invalid boot order '%s' specified,"
" must be one or more of [acdn]" %
boot_order)
# device type checks
nic_type = hvparams[constants.HV_NIC_TYPE]
if nic_type not in constants.HT_HVM_VALID_NIC_TYPES:
raise errors.HypervisorError(\
"Invalid NIC type %s specified for the Xen"
" HVM hypervisor. Please choose one of: %s"
% (nic_type, utils.CommaJoin(constants.HT_HVM_VALID_NIC_TYPES)))
disk_type = hvparams[constants.HV_DISK_TYPE]
if disk_type not in constants.HT_HVM_VALID_DISK_TYPES:
raise errors.HypervisorError(\
"Invalid disk type %s specified for the Xen"
" HVM hypervisor. Please choose one of: %s"
% (disk_type, utils.CommaJoin(constants.HT_HVM_VALID_DISK_TYPES)))
# vnc_bind_address verification
vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS]
if vnc_bind_address:
if not utils.IsValidIP(vnc_bind_address):
raise errors.OpPrereqError("given VNC bind address '%s' doesn't look"
" like a valid IP address" %
vnc_bind_address)
iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
if iso_path and not os.path.isabs(iso_path):
raise errors.HypervisorError("The path to the HVM CDROM image must"
" be an absolute path or None, not %s" %
iso_path)
if not hvparams[constants.HV_KERNEL_PATH]:
raise errors.HypervisorError("Need a kernel for the instance")
if not os.path.isabs(hvparams[constants.HV_KERNEL_PATH]):
raise errors.HypervisorError("The kernel path must be an absolute path")
if not hvparams[constants.HV_DEVICE_MODEL]:
raise errors.HypervisorError("Need a device model for the instance")
if not os.path.isabs(hvparams[constants.HV_DEVICE_MODEL]):
raise errors.HypervisorError("The device model must be an absolute path")
def ValidateParameters(self, hvparams):
"""Check the given parameters for validity.
For the PVM hypervisor, this only check the existence of the
kernel.
@type hvparams: dict
@param hvparams: dictionary with parameter names/value
@raise errors.HypervisorError: when a parameter is not valid
"""
super(XenHvmHypervisor, self).ValidateParameters(hvparams)
# hvm_cdrom_image_path verification
iso_path = hvparams[constants.HV_CDROM_IMAGE_PATH]
if iso_path and not os.path.isfile(iso_path):
raise errors.HypervisorError("The HVM CDROM image must either be a"
" regular file or a symlink pointing to"
" an existing regular file, not %s" %
iso_path)
kernel_path = hvparams[constants.HV_KERNEL_PATH]
if not os.path.isfile(kernel_path):
raise errors.HypervisorError("Instance kernel '%s' not found or"
" not a file" % kernel_path)
device_model = hvparams[constants.HV_DEVICE_MODEL]
if not os.path.isfile(device_model):
raise errors.HypervisorError("Device model '%s' not found or"
" not a file" % device_model)
PARAMETERS = {
constants.HV_ACPI: hv_base.NO_CHECK,
constants.HV_BOOT_ORDER: (True, ) + \
(lambda x: x and len(x.strip("acdn")) == 0,
"Invalid boot order specified, must be one or more of [acdn]",
None, None),
constants.HV_CDROM_IMAGE_PATH: hv_base.OPT_FILE_CHECK,
constants.HV_DISK_TYPE: \
hv_base.ParamInSet(True, constants.HT_HVM_VALID_DISK_TYPES),
constants.HV_NIC_TYPE: \
hv_base.ParamInSet(True, constants.HT_HVM_VALID_NIC_TYPES),
constants.HV_PAE: hv_base.NO_CHECK,
constants.HV_VNC_BIND_ADDRESS: \
(False, utils.IsValidIP,
"VNC bind address is not a valid IP address", None, None),
constants.HV_KERNEL_PATH: hv_base.REQ_FILE_CHECK,
constants.HV_DEVICE_MODEL: hv_base.REQ_FILE_CHECK,
}
@classmethod
def _WriteConfigFile(cls, instance, block_devices):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment