diff --git a/lib/hypervisor/hv_base.py b/lib/hypervisor/hv_base.py index e5de1b7cc09423f391a61b246dacfb48d0dbb0a4..7f100476f011085d57eb1dd04e606560778c347b 100644 --- a/lib/hypervisor/hv_base.py +++ b/lib/hypervisor/hv_base.py @@ -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. diff --git a/lib/hypervisor/hv_kvm.py b/lib/hypervisor/hv_kvm.py index 3b9a0dfe287bf16149fa0ebfc91f4af8d42c48d2..bc7294792ca9f5c5d67c3db13e8f407a391b2428 100644 --- a/lib/hypervisor/hv_kvm.py +++ b/lib/hypervisor/hv_kvm.py @@ -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) diff --git a/lib/hypervisor/hv_xen.py b/lib/hypervisor/hv_xen.py index 9779e51b2ce486044ce9ca6172557cdff86559cc..c444cf0269fd7b0b5425ed78fe7c91f82e119d2e 100644 --- a/lib/hypervisor/hv_xen.py +++ b/lib/hypervisor/hv_xen.py @@ -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):