X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/1fdeb284f5c9fde710169d20f45ae707a7e48081..6c00b2c7299133153f8370d2db1ca3c12bea4402:/lib/backend.py diff --git a/lib/backend.py b/lib/backend.py index 4bd4700..a16b22a 100644 --- a/lib/backend.py +++ b/lib/backend.py @@ -1,7 +1,7 @@ # # -# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc. +# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 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 @@ -28,12 +28,14 @@ """ -# pylint: disable=E1103 +# pylint: disable=E1103,C0302 # E1103: %s %r has no %r member (but some types could not be # inferred), because the _TryOSFromDisk returns either (True, os_obj) # or (False, "string") which confuses pylint +# C0302: This module has become too big and should be split up + import os import os.path @@ -54,20 +56,25 @@ from ganeti import utils from ganeti import ssh from ganeti import hypervisor from ganeti import constants -from ganeti import bdev +from ganeti.storage import bdev +from ganeti.storage import drbd +from ganeti.storage import filestorage from ganeti import objects from ganeti import ssconf from ganeti import serializer from ganeti import netutils from ganeti import runtime -from ganeti import mcpu from ganeti import compat from ganeti import pathutils from ganeti import vcluster +from ganeti import ht +from ganeti.storage.base import BlockDev +from ganeti.storage.drbd import DRBD8 +from ganeti import hooksmaster _BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id" -_ALLOWED_CLEAN_DIRS = frozenset([ +_ALLOWED_CLEAN_DIRS = compat.UniqueFrozenset([ pathutils.DATA_DIR, pathutils.JOB_QUEUE_ARCHIVE_DIR, pathutils.QUEUE_DIR, @@ -81,21 +88,21 @@ _IES_PID_FILE = "pid" _IES_CA_FILE = "ca" #: Valid LVS output line regex -_LVSLINE_REGEX = re.compile("^ *([^|]+)\|([^|]+)\|([0-9.]+)\|([^|]{6,})\|?$") +_LVSLINE_REGEX = re.compile(r"^ *([^|]+)\|([^|]+)\|([0-9.]+)\|([^|]{6,})\|?$") # Actions for the master setup script _MASTER_START = "start" _MASTER_STOP = "stop" -#: Maximum file permissions for remote command directory and executables +#: Maximum file permissions for restricted command directory and executables _RCMD_MAX_MODE = (stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH) -#: Delay before returning an error for remote commands +#: Delay before returning an error for restricted commands _RCMD_INVALID_DELAY = 10 -#: How long to wait to acquire lock for remote commands (shorter than +#: How long to wait to acquire lock for restricted commands (shorter than #: L{_RCMD_INVALID_DELAY}) to reduce blockage of noded forks when many #: command requests arrive _RCMD_LOCK_TIMEOUT = _RCMD_INVALID_DELAY * 0.8 @@ -109,6 +116,34 @@ class RPCFail(Exception): """ +def _GetInstReasonFilename(instance_name): + """Path of the file containing the reason of the instance status change. + + @type instance_name: string + @param instance_name: The name of the instance + @rtype: string + @return: The path of the file + + """ + return utils.PathJoin(pathutils.INSTANCE_REASON_DIR, instance_name) + + +def _StoreInstReasonTrail(instance_name, trail): + """Serialize a reason trail related to an instance change of state to file. + + The exact location of the file depends on the name of the instance and on + the configuration of the Ganeti cluster defined at deploy time. + + @type instance_name: string + @param instance_name: The name of the instance + @rtype: None + + """ + json = serializer.DumpJson(trail) + filename = _GetInstReasonFilename(instance_name) + utils.WriteFile(filename, data=json) + + def _Fail(msg, *args, **kwargs): """Log an error and the raise an RPCFail exception. @@ -296,10 +331,10 @@ def RunLocalHooks(hook_opcode, hooks_path, env_builder_fn): cfg = _GetConfig() hr = HooksRunner() - hm = mcpu.HooksMaster(hook_opcode, hooks_path, nodes, hr.RunLocalHooks, - None, env_fn, logging.warning, cfg.GetClusterName(), - cfg.GetMasterNode()) - + hm = hooksmaster.HooksMaster(hook_opcode, hooks_path, nodes, + hr.RunLocalHooks, None, env_fn, + logging.warning, cfg.GetClusterName(), + cfg.GetMasterNode()) hm.RunPhase(constants.HOOKS_PHASE_PRE) result = fn(*args, **kwargs) hm.RunPhase(constants.HOOKS_PHASE_POST) @@ -357,8 +392,8 @@ def _RunMasterSetupScript(master_params, action, use_external_mip_script): result = utils.RunCmd([setup_script, action], env=env, reset_env=True) if result.failed: - _Fail("Failed to %s the master IP. Script return value: %s" % - (action, result.exit_code), log=True) + _Fail("Failed to %s the master IP. Script return value: %s, output: '%s'" % + (action, result.exit_code, result.output), log=True) @RunLocalHooks(constants.FAKE_OP_MASTER_TURNUP, "master-ip-turnup", @@ -540,12 +575,62 @@ def LeaveCluster(modify_ssh_setup): raise errors.QuitGanetiException(True, "Shutdown scheduled") -def _GetVgInfo(name): +def _CheckStorageParams(params, num_params): + """Performs sanity checks for storage parameters. + + @type params: list + @param params: list of storage parameters + @type num_params: int + @param num_params: expected number of parameters + + """ + if params is None: + raise errors.ProgrammerError("No storage parameters for storage" + " reporting is provided.") + if not isinstance(params, list): + raise errors.ProgrammerError("The storage parameters are not of type" + " list: '%s'" % params) + if not len(params) == num_params: + raise errors.ProgrammerError("Did not receive the expected number of" + "storage parameters: expected %s," + " received '%s'" % (num_params, len(params))) + + +def _CheckLvmStorageParams(params): + """Performs sanity check for the 'exclusive storage' flag. + + @see: C{_CheckStorageParams} + + """ + _CheckStorageParams(params, 1) + excl_stor = params[0] + if not isinstance(params[0], bool): + raise errors.ProgrammerError("Exclusive storage parameter is not" + " boolean: '%s'." % excl_stor) + return excl_stor + + +def _GetLvmVgSpaceInfo(name, params): + """Wrapper around C{_GetVgInfo} which checks the storage parameters. + + @type name: string + @param name: name of the volume group + @type params: list + @param params: list of storage parameters, which in this case should be + containing only one for exclusive storage + + """ + excl_stor = _CheckLvmStorageParams(params) + return _GetVgInfo(name, excl_stor) + + +def _GetVgInfo( + name, excl_stor, info_fn=bdev.LogicalVolume.GetVGInfo): """Retrieves information about a LVM volume group. """ # TODO: GetVGInfo supports returning information for multiple VGs at once - vginfo = bdev.LogicalVolume.GetVGInfo([name]) + vginfo = info_fn([name], excl_stor) if vginfo: vg_free = int(round(vginfo[0][0], 0)) vg_size = int(round(vginfo[0][1], 0)) @@ -554,13 +639,50 @@ def _GetVgInfo(name): vg_size = None return { + "type": constants.ST_LVM_VG, + "name": name, + "storage_free": vg_free, + "storage_size": vg_size, + } + + +def _GetLvmPvSpaceInfo(name, params): + """Wrapper around C{_GetVgSpindlesInfo} with sanity checks. + + @see: C{_GetLvmVgSpaceInfo} + + """ + excl_stor = _CheckLvmStorageParams(params) + return _GetVgSpindlesInfo(name, excl_stor) + + +def _GetVgSpindlesInfo( + name, excl_stor, info_fn=bdev.LogicalVolume.GetVgSpindlesInfo): + """Retrieves information about spindles in an LVM volume group. + + @type name: string + @param name: VG name + @type excl_stor: bool + @param excl_stor: exclusive storage + @rtype: dict + @return: dictionary whose keys are "name", "vg_free", "vg_size" for VG name, + free spindles, total spindles respectively + + """ + if excl_stor: + (vg_free, vg_size) = info_fn(name) + else: + vg_free = 0 + vg_size = 0 + return { + "type": constants.ST_LVM_PV, "name": name, - "vg_free": vg_free, - "vg_size": vg_size, + "storage_free": vg_free, + "storage_size": vg_size, } -def _GetHvInfo(name): +def _GetHvInfo(name, hvparams, get_hv_fn=hypervisor.GetHypervisor): """Retrieves node information from a hypervisor. The information returned depends on the hypervisor. Common items: @@ -572,8 +694,29 @@ def _GetHvInfo(name): - memory_total is the total number of ram in MiB - hv_version: the hypervisor version, if available + @type hvparams: dict of string + @param hvparams: the hypervisor's hvparams + """ - return hypervisor.GetHypervisor(name).GetNodeInfo() + return get_hv_fn(name).GetNodeInfo(hvparams=hvparams) + + +def _GetHvInfoAll(hv_specs, get_hv_fn=hypervisor.GetHypervisor): + """Retrieves node information for all hypervisors. + + See C{_GetHvInfo} for information on the output. + + @type hv_specs: list of pairs (string, dict of strings) + @param hv_specs: list of pairs of a hypervisor's name and its hvparams + + """ + if hv_specs is None: + return None + + result = [] + for hvname, hvparams in hv_specs: + result.append(_GetHvInfo(hvname, hvparams, get_hv_fn)) + return result def _GetNamedNodeInfo(names, fn): @@ -588,26 +731,205 @@ def _GetNamedNodeInfo(names, fn): return map(fn, names) -def GetNodeInfo(vg_names, hv_names): +def GetNodeInfo(storage_units, hv_specs): """Gives back a hash with different information about the node. - @type vg_names: list of string - @param vg_names: Names of the volume groups to ask for disk space information - @type hv_names: list of string - @param hv_names: Names of the hypervisors to ask for node information + @type storage_units: list of tuples (string, string, list) + @param storage_units: List of tuples (storage unit, identifier, parameters) to + ask for disk space information. In case of lvm-vg, the identifier is + the VG name. The parameters can contain additional, storage-type-specific + parameters, for example exclusive storage for lvm storage. + @type hv_specs: list of pairs (string, dict of strings) + @param hv_specs: list of pairs of a hypervisor's name and its hvparams @rtype: tuple; (string, None/dict, None/dict) @return: Tuple containing boot ID, volume group information and hypervisor information """ bootid = utils.ReadFile(_BOOT_ID_PATH, size=128).rstrip("\n") - vg_info = _GetNamedNodeInfo(vg_names, _GetVgInfo) - hv_info = _GetNamedNodeInfo(hv_names, _GetHvInfo) + storage_info = _GetNamedNodeInfo( + storage_units, + (lambda (storage_type, storage_key, storage_params): + _ApplyStorageInfoFunction(storage_type, storage_key, storage_params))) + hv_info = _GetHvInfoAll(hv_specs) + return (bootid, storage_info, hv_info) + + +def _GetFileStorageSpaceInfo(path, params): + """Wrapper around filestorage.GetSpaceInfo. + + The purpose of this wrapper is to call filestorage.GetFileStorageSpaceInfo + and ignore the *args parameter to not leak it into the filestorage + module's code. + + @see: C{filestorage.GetFileStorageSpaceInfo} for description of the + parameters. + + """ + _CheckStorageParams(params, 0) + return filestorage.GetFileStorageSpaceInfo(path) + + +# FIXME: implement storage reporting for all missing storage types. +_STORAGE_TYPE_INFO_FN = { + constants.ST_BLOCK: None, + constants.ST_DISKLESS: None, + constants.ST_EXT: None, + constants.ST_FILE: _GetFileStorageSpaceInfo, + constants.ST_LVM_PV: _GetLvmPvSpaceInfo, + constants.ST_LVM_VG: _GetLvmVgSpaceInfo, + constants.ST_RADOS: None, +} + + +def _ApplyStorageInfoFunction(storage_type, storage_key, *args): + """Looks up and applies the correct function to calculate free and total + storage for the given storage type. + + @type storage_type: string + @param storage_type: the storage type for which the storage shall be reported. + @type storage_key: string + @param storage_key: identifier of a storage unit, e.g. the volume group name + of an LVM storage unit + @type args: any + @param args: various parameters that can be used for storage reporting. These + parameters and their semantics vary from storage type to storage type and + are just propagated in this function. + @return: the results of the application of the storage space function (see + _STORAGE_TYPE_INFO_FN) if storage space reporting is implemented for that + storage type + @raises NotImplementedError: for storage types who don't support space + reporting yet + """ + fn = _STORAGE_TYPE_INFO_FN[storage_type] + if fn is not None: + return fn(storage_key, *args) + else: + raise NotImplementedError + + +def _CheckExclusivePvs(pvi_list): + """Check that PVs are not shared among LVs + + @type pvi_list: list of L{objects.LvmPvInfo} objects + @param pvi_list: information about the PVs + + @rtype: list of tuples (string, list of strings) + @return: offending volumes, as tuples: (pv_name, [lv1_name, lv2_name...]) + + """ + res = [] + for pvi in pvi_list: + if len(pvi.lv_list) > 1: + res.append((pvi.name, pvi.lv_list)) + return res + + +def _VerifyHypervisors(what, vm_capable, result, all_hvparams, + get_hv_fn=hypervisor.GetHypervisor): + """Verifies the hypervisor. Appends the results to the 'results' list. + + @type what: C{dict} + @param what: a dictionary of things to check + @type vm_capable: boolean + @param vm_capable: whether or not this node is vm capable + @type result: dict + @param result: dictionary of verification results; results of the + verifications in this function will be added here + @type all_hvparams: dict of dict of string + @param all_hvparams: dictionary mapping hypervisor names to hvparams + @type get_hv_fn: function + @param get_hv_fn: function to retrieve the hypervisor, to improve testability + + """ + if not vm_capable: + return + + if constants.NV_HYPERVISOR in what: + result[constants.NV_HYPERVISOR] = {} + for hv_name in what[constants.NV_HYPERVISOR]: + hvparams = all_hvparams[hv_name] + try: + val = get_hv_fn(hv_name).Verify(hvparams=hvparams) + except errors.HypervisorError, err: + val = "Error while checking hypervisor: %s" % str(err) + result[constants.NV_HYPERVISOR][hv_name] = val - return (bootid, vg_info, hv_info) +def _VerifyHvparams(what, vm_capable, result, + get_hv_fn=hypervisor.GetHypervisor): + """Verifies the hvparams. Appends the results to the 'results' list. -def VerifyNode(what, cluster_name): + @type what: C{dict} + @param what: a dictionary of things to check + @type vm_capable: boolean + @param vm_capable: whether or not this node is vm capable + @type result: dict + @param result: dictionary of verification results; results of the + verifications in this function will be added here + @type get_hv_fn: function + @param get_hv_fn: function to retrieve the hypervisor, to improve testability + + """ + if not vm_capable: + return + + if constants.NV_HVPARAMS in what: + result[constants.NV_HVPARAMS] = [] + for source, hv_name, hvparms in what[constants.NV_HVPARAMS]: + try: + logging.info("Validating hv %s, %s", hv_name, hvparms) + get_hv_fn(hv_name).ValidateParameters(hvparms) + except errors.HypervisorError, err: + result[constants.NV_HVPARAMS].append((source, hv_name, str(err))) + + +def _VerifyInstanceList(what, vm_capable, result, all_hvparams): + """Verifies the instance list. + + @type what: C{dict} + @param what: a dictionary of things to check + @type vm_capable: boolean + @param vm_capable: whether or not this node is vm capable + @type result: dict + @param result: dictionary of verification results; results of the + verifications in this function will be added here + @type all_hvparams: dict of dict of string + @param all_hvparams: dictionary mapping hypervisor names to hvparams + + """ + if constants.NV_INSTANCELIST in what and vm_capable: + # GetInstanceList can fail + try: + val = GetInstanceList(what[constants.NV_INSTANCELIST], + all_hvparams=all_hvparams) + except RPCFail, err: + val = str(err) + result[constants.NV_INSTANCELIST] = val + + +def _VerifyNodeInfo(what, vm_capable, result, all_hvparams): + """Verifies the node info. + + @type what: C{dict} + @param what: a dictionary of things to check + @type vm_capable: boolean + @param vm_capable: whether or not this node is vm capable + @type result: dict + @param result: dictionary of verification results; results of the + verifications in this function will be added here + @type all_hvparams: dict of dict of string + @param all_hvparams: dictionary mapping hypervisor names to hvparams + + """ + if constants.NV_HVINFO in what and vm_capable: + hvname = what[constants.NV_HVINFO] + hyper = hypervisor.GetHypervisor(hvname) + hvparams = all_hvparams[hvname] + result[constants.NV_HVINFO] = hyper.GetNodeInfo(hvparams=hvparams) + + +def VerifyNode(what, cluster_name, all_hvparams): """Verify the status of the local node. Based on the input L{what} parameter, various checks are done on the @@ -631,6 +953,10 @@ def VerifyNode(what, cluster_name): - node-net-test: list of nodes we should check node daemon port connectivity with - hypervisor: list with hypervisors to run the verify for + @type cluster_name: string + @param cluster_name: the cluster's name + @type all_hvparams: dict of dict of strings + @param all_hvparams: a dictionary mapping hypervisor names to hvparams @rtype: dict @return: a dictionary with the same keys as the input dict, and values representing the result of the checks @@ -641,23 +967,8 @@ def VerifyNode(what, cluster_name): port = netutils.GetDaemonPort(constants.NODED) vm_capable = my_name not in what.get(constants.NV_VMNODES, []) - if constants.NV_HYPERVISOR in what and vm_capable: - result[constants.NV_HYPERVISOR] = tmp = {} - for hv_name in what[constants.NV_HYPERVISOR]: - try: - val = hypervisor.GetHypervisor(hv_name).Verify() - except errors.HypervisorError, err: - val = "Error while checking hypervisor: %s" % str(err) - tmp[hv_name] = val - - if constants.NV_HVPARAMS in what and vm_capable: - result[constants.NV_HVPARAMS] = tmp = [] - for source, hv_name, hvparms in what[constants.NV_HVPARAMS]: - try: - logging.info("Validating hv %s, %s", hv_name, hvparms) - hypervisor.GetHypervisor(hv_name).ValidateParameters(hvparms) - except errors.HypervisorError, err: - tmp.append((source, hv_name, str(err))) + _VerifyHypervisors(what, vm_capable, result, all_hvparams) + _VerifyHvparams(what, vm_capable, result) if constants.NV_FILELIST in what: fingerprints = utils.FingerprintFiles(map(vcluster.LocalizeVirtualPath, @@ -749,33 +1060,40 @@ def VerifyNode(what, cluster_name): val = str(err) result[constants.NV_LVLIST] = val - if constants.NV_INSTANCELIST in what and vm_capable: - # GetInstanceList can fail - try: - val = GetInstanceList(what[constants.NV_INSTANCELIST]) - except RPCFail, err: - val = str(err) - result[constants.NV_INSTANCELIST] = val + _VerifyInstanceList(what, vm_capable, result, all_hvparams) if constants.NV_VGLIST in what and vm_capable: result[constants.NV_VGLIST] = utils.ListVolumeGroups() if constants.NV_PVLIST in what and vm_capable: - result[constants.NV_PVLIST] = \ - bdev.LogicalVolume.GetPVInfo(what[constants.NV_PVLIST], - filter_allocatable=False) + check_exclusive_pvs = constants.NV_EXCLUSIVEPVS in what + val = bdev.LogicalVolume.GetPVInfo(what[constants.NV_PVLIST], + filter_allocatable=False, + include_lvs=check_exclusive_pvs) + if check_exclusive_pvs: + result[constants.NV_EXCLUSIVEPVS] = _CheckExclusivePvs(val) + for pvi in val: + # Avoid sending useless data on the wire + pvi.lv_list = [] + result[constants.NV_PVLIST] = map(objects.LvmPvInfo.ToDict, val) if constants.NV_VERSION in what: result[constants.NV_VERSION] = (constants.PROTOCOL_VERSION, constants.RELEASE_VERSION) - if constants.NV_HVINFO in what and vm_capable: - hyper = hypervisor.GetHypervisor(what[constants.NV_HVINFO]) - result[constants.NV_HVINFO] = hyper.GetNodeInfo() + _VerifyNodeInfo(what, vm_capable, result, all_hvparams) + + if constants.NV_DRBDVERSION in what and vm_capable: + try: + drbd_version = DRBD8.GetProcInfo().GetVersionString() + except errors.BlockDeviceError, err: + logging.warning("Can't get DRBD version", exc_info=True) + drbd_version = str(err) + result[constants.NV_DRBDVERSION] = drbd_version if constants.NV_DRBDLIST in what and vm_capable: try: - used_minors = bdev.DRBD8.GetUsedDevs().keys() + used_minors = drbd.DRBD8.GetUsedDevs() except errors.BlockDeviceError, err: logging.warning("Can't get used minors list", exc_info=True) used_minors = str(err) @@ -784,7 +1102,7 @@ def VerifyNode(what, cluster_name): if constants.NV_DRBDHELPER in what and vm_capable: status = True try: - payload = bdev.BaseDRBD.GetUsermodeHelper() + payload = drbd.DRBD8.GetUsermodeHelper() except errors.BlockDeviceError, err: logging.error("Can't get DRBD usermode helper: %s", str(err)) status = False @@ -814,9 +1132,21 @@ def VerifyNode(what, cluster_name): for bridge in what[constants.NV_BRIDGES] if not utils.BridgeExists(bridge)] - if what.get(constants.NV_FILE_STORAGE_PATHS) == my_name: - result[constants.NV_FILE_STORAGE_PATHS] = \ - bdev.ComputeWrongFileStoragePaths() + if what.get(constants.NV_ACCEPTED_STORAGE_PATHS) == my_name: + result[constants.NV_ACCEPTED_STORAGE_PATHS] = \ + filestorage.ComputeWrongFileStoragePaths() + + if what.get(constants.NV_FILE_STORAGE_PATH): + pathresult = filestorage.CheckFileStoragePath( + what[constants.NV_FILE_STORAGE_PATH]) + if pathresult: + result[constants.NV_FILE_STORAGE_PATH] = pathresult + + if what.get(constants.NV_SHARED_FILE_STORAGE_PATH): + pathresult = filestorage.CheckFileStoragePath( + what[constants.NV_SHARED_FILE_STORAGE_PATH]) + if pathresult: + result[constants.NV_SHARED_FILE_STORAGE_PATH] = pathresult return result @@ -978,11 +1308,47 @@ def BridgesExist(bridges_list): _Fail("Missing bridges %s", utils.CommaJoin(missing)) -def GetInstanceList(hypervisor_list): +def GetInstanceListForHypervisor(hname, hvparams=None, + get_hv_fn=hypervisor.GetHypervisor): + """Provides a list of instances of the given hypervisor. + + @type hname: string + @param hname: name of the hypervisor + @type hvparams: dict of strings + @param hvparams: hypervisor parameters for the given hypervisor + @type get_hv_fn: function + @param get_hv_fn: function that returns a hypervisor for the given hypervisor + name; optional parameter to increase testability + + @rtype: list + @return: a list of all running instances on the current node + - instance1.example.com + - instance2.example.com + + """ + results = [] + try: + hv = get_hv_fn(hname) + names = hv.ListInstances(hvparams=hvparams) + results.extend(names) + except errors.HypervisorError, err: + _Fail("Error enumerating instances (hypervisor %s): %s", + hname, err, exc=True) + return results + + +def GetInstanceList(hypervisor_list, all_hvparams=None, + get_hv_fn=hypervisor.GetHypervisor): """Provides a list of instances. @type hypervisor_list: list @param hypervisor_list: the list of hypervisors to query information + @type all_hvparams: dict of dict of strings + @param all_hvparams: a dictionary mapping hypervisor types to respective + cluster-wide hypervisor parameters + @type get_hv_fn: function + @param get_hv_fn: function that returns a hypervisor for the given hypervisor + name; optional parameter to increase testability @rtype: list @return: a list of all running instances on the current node @@ -992,23 +1358,21 @@ def GetInstanceList(hypervisor_list): """ results = [] for hname in hypervisor_list: - try: - names = hypervisor.GetHypervisor(hname).ListInstances() - results.extend(names) - except errors.HypervisorError, err: - _Fail("Error enumerating instances (hypervisor %s): %s", - hname, err, exc=True) - + hvparams = all_hvparams[hname] + results.extend(GetInstanceListForHypervisor(hname, hvparams=hvparams, + get_hv_fn=get_hv_fn)) return results -def GetInstanceInfo(instance, hname): +def GetInstanceInfo(instance, hname, hvparams=None): """Gives back the information about an instance as a dictionary. @type instance: string @param instance: the instance name @type hname: string @param hname: the hypervisor type of the instance + @type hvparams: dict of strings + @param hvparams: the instance's hvparams @rtype: dict @return: dictionary with the following keys: @@ -1020,7 +1384,8 @@ def GetInstanceInfo(instance, hname): """ output = {} - iinfo = hypervisor.GetHypervisor(hname).GetInstanceInfo(instance) + iinfo = hypervisor.GetHypervisor(hname).GetInstanceInfo(instance, + hvparams=hvparams) if iinfo is not None: output["memory"] = iinfo[2] output["vcpus"] = iinfo[3] @@ -1031,7 +1396,7 @@ def GetInstanceInfo(instance, hname): def GetInstanceMigratable(instance): - """Gives whether an instance can be migrated. + """Computes whether an instance can be migrated. @type instance: L{objects.Instance} @param instance: object representing the instance to be checked. @@ -1044,7 +1409,7 @@ def GetInstanceMigratable(instance): """ hyper = hypervisor.GetHypervisor(instance.hypervisor) iname = instance.name - if iname not in hyper.ListInstances(): + if iname not in hyper.ListInstances(instance.hvparams): _Fail("Instance %s is not running", iname) for idx in range(len(instance.disks)): @@ -1054,7 +1419,7 @@ def GetInstanceMigratable(instance): iname, link_name, idx) -def GetAllInstancesInfo(hypervisor_list): +def GetAllInstancesInfo(hypervisor_list, all_hvparams): """Gather data about all instances. This is the equivalent of L{GetInstanceInfo}, except that it @@ -1063,6 +1428,8 @@ def GetAllInstancesInfo(hypervisor_list): @type hypervisor_list: list @param hypervisor_list: list of hypervisors to query for instance data + @type all_hvparams: dict of dict of strings + @param all_hvparams: mapping of hypervisor names to hvparams @rtype: dict @return: dictionary of instance: data, with data having the following keys: @@ -1075,7 +1442,8 @@ def GetAllInstancesInfo(hypervisor_list): output = {} for hname in hypervisor_list: - iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo() + hvparams = all_hvparams[hname] + iinfo = hypervisor.GetHypervisor(hname).GetAllInstancesInfo(hvparams) if iinfo: for name, _, memory, vcpus, state, times in iinfo: value = { @@ -1190,9 +1558,16 @@ def RunRenameInstance(instance, old_name, debug): " log file:\n%s", result.fail_reason, "\n".join(lines), log=False) -def _GetBlockDevSymlinkPath(instance_name, idx): - return utils.PathJoin(pathutils.DISK_LINKS_DIR, "%s%s%d" % - (instance_name, constants.DISK_SEPARATOR, idx)) +def _GetBlockDevSymlinkPath(instance_name, idx, _dir=None): + """Returns symlink path for block device. + + """ + if _dir is None: + _dir = pathutils.DISK_LINKS_DIR + + return utils.PathJoin(_dir, + ("%s%s%s" % + (instance_name, constants.DISK_SEPARATOR, idx))) def _SymlinkBlockDev(instance_name, device_path, idx): @@ -1243,7 +1618,7 @@ def _GatherAndLinkBlockDevs(instance): devices must be already assembled. @type instance: L{objects.Instance} - @param instance: the instance whose disks we shoul assemble + @param instance: the instance whose disks we should assemble @rtype: list @return: list of (disk_object, device_path) @@ -1261,22 +1636,27 @@ def _GatherAndLinkBlockDevs(instance): raise errors.BlockDeviceError("Cannot create block device symlink: %s" % e.strerror) - block_devices.append((disk, link_name)) + block_devices.append((disk, link_name, device)) return block_devices -def StartInstance(instance, startup_paused): +def StartInstance(instance, startup_paused, reason, store_reason=True): """Start an instance. @type instance: L{objects.Instance} @param instance: the instance object @type startup_paused: bool @param instance: pause instance at startup? + @type reason: list of reasons + @param reason: the reason trail for this startup + @type store_reason: boolean + @param store_reason: whether to store the shutdown reason trail on file @rtype: None """ - running_instances = GetInstanceList([instance.hypervisor]) + running_instances = GetInstanceListForHypervisor(instance.hypervisor, + instance.hvparams) if instance.name in running_instances: logging.info("Instance %s already running, not starting", instance.name) @@ -1286,6 +1666,8 @@ def StartInstance(instance, startup_paused): block_devices = _GatherAndLinkBlockDevs(instance) hyper = hypervisor.GetHypervisor(instance.hypervisor) hyper.StartInstance(instance, block_devices, startup_paused) + if store_reason: + _StoreInstReasonTrail(instance.name, reason) except errors.BlockDeviceError, err: _Fail("Block device error: %s", err, exc=True) except errors.HypervisorError, err: @@ -1293,7 +1675,7 @@ def StartInstance(instance, startup_paused): _Fail("Hypervisor error: %s", err, exc=True) -def InstanceShutdown(instance, timeout): +def InstanceShutdown(instance, timeout, reason, store_reason=True): """Shut an instance down. @note: this functions uses polling with a hardcoded timeout. @@ -1302,6 +1684,10 @@ def InstanceShutdown(instance, timeout): @param instance: the instance object @type timeout: integer @param timeout: maximum timeout for soft shutdown + @type reason: list of reasons + @param reason: the reason trail for this shutdown + @type store_reason: boolean + @param store_reason: whether to store the shutdown reason trail on file @rtype: None """ @@ -1309,7 +1695,7 @@ def InstanceShutdown(instance, timeout): hyper = hypervisor.GetHypervisor(hv_name) iname = instance.name - if instance.name not in hyper.ListInstances(): + if instance.name not in hyper.ListInstances(instance.hvparams): logging.info("Instance %s not running, doing nothing", iname) return @@ -1318,13 +1704,15 @@ def InstanceShutdown(instance, timeout): self.tried_once = False def __call__(self): - if iname not in hyper.ListInstances(): + if iname not in hyper.ListInstances(instance.hvparams): return try: hyper.StopInstance(instance, retry=self.tried_once) + if store_reason: + _StoreInstReasonTrail(instance.name, reason) except errors.HypervisorError, err: - if iname not in hyper.ListInstances(): + if iname not in hyper.ListInstances(instance.hvparams): # if the instance is no longer existing, consider this a # success and go to cleanup return @@ -1344,14 +1732,14 @@ def InstanceShutdown(instance, timeout): try: hyper.StopInstance(instance, force=True) except errors.HypervisorError, err: - if iname in hyper.ListInstances(): + if iname in hyper.ListInstances(instance.hvparams): # only raise an error if the instance still exists, otherwise # the error could simply be "instance ... unknown"! _Fail("Failed to force stop instance %s: %s", iname, err) time.sleep(1) - if iname in hyper.ListInstances(): + if iname in hyper.ListInstances(instance.hvparams): _Fail("Could not shutdown instance %s even by destroy", iname) try: @@ -1362,7 +1750,7 @@ def InstanceShutdown(instance, timeout): _RemoveBlockDevLinks(iname, instance.disks) -def InstanceReboot(instance, reboot_type, shutdown_timeout): +def InstanceReboot(instance, reboot_type, shutdown_timeout, reason): """Reboot an instance. @type instance: L{objects.Instance} @@ -1380,10 +1768,13 @@ def InstanceReboot(instance, reboot_type, shutdown_timeout): instance (instead of a call_instance_reboot RPC) @type shutdown_timeout: integer @param shutdown_timeout: maximum timeout for soft shutdown + @type reason: list of reasons + @param reason: the reason trail for this reboot @rtype: None """ - running_instances = GetInstanceList([instance.hypervisor]) + running_instances = GetInstanceListForHypervisor(instance.hypervisor, + instance.hvparams) if instance.name not in running_instances: _Fail("Cannot reboot instance %s that is not running", instance.name) @@ -1396,8 +1787,10 @@ def InstanceReboot(instance, reboot_type, shutdown_timeout): _Fail("Failed to soft reboot instance %s: %s", instance.name, err) elif reboot_type == constants.INSTANCE_REBOOT_HARD: try: - InstanceShutdown(instance, shutdown_timeout) - return StartInstance(instance, False) + InstanceShutdown(instance, shutdown_timeout, reason, store_reason=False) + result = StartInstance(instance, False, reason, store_reason=False) + _StoreInstReasonTrail(instance.name, reason) + return result except errors.HypervisorError, err: _Fail("Failed to hard reboot instance %s: %s", instance.name, err) else: @@ -1415,7 +1808,7 @@ def InstanceBalloonMemory(instance, memory): """ hyper = hypervisor.GetHypervisor(instance.hypervisor) - running = hyper.ListInstances() + running = hyper.ListInstances(instance.hvparams) if instance.name not in running: logging.info("Instance %s is not running, cannot balloon", instance.name) return @@ -1487,9 +1880,11 @@ def FinalizeMigrationDst(instance, info, success): _Fail("Failed to finalize migration on the target node: %s", err, exc=True) -def MigrateInstance(instance, target, live): +def MigrateInstance(cluster_name, instance, target, live): """Migrates an instance to another node. + @type cluster_name: string + @param cluster_name: name of the cluster @type instance: L{objects.Instance} @param instance: the instance definition @type target: string @@ -1503,7 +1898,7 @@ def MigrateInstance(instance, target, live): hyper = hypervisor.GetHypervisor(instance.hypervisor) try: - hyper.MigrateInstance(instance, target, live) + hyper.MigrateInstance(cluster_name, instance, target, live) except errors.HypervisorError, err: _Fail("Failed to migrate instance: %s", err, exc=True) @@ -1548,7 +1943,7 @@ def GetMigrationStatus(instance): _Fail("Failed to get migration status: %s", err, exc=True) -def BlockdevCreate(disk, size, owner, on_primary, info): +def BlockdevCreate(disk, size, owner, on_primary, info, excl_stor): """Creates a block device for an instance. @type disk: L{objects.Disk} @@ -1563,6 +1958,8 @@ def BlockdevCreate(disk, size, owner, on_primary, info): @type info: string @param info: string that will be sent to the physical device creation, used for example to set (LVM) tags on LVs + @type excl_stor: boolean + @param excl_stor: Whether exclusive_storage is active @return: the new unique_id of the device (this can sometime be computed only after creation), or None. On secondary nodes, @@ -1589,7 +1986,7 @@ def BlockdevCreate(disk, size, owner, on_primary, info): clist.append(crdev) try: - device = bdev.Create(disk, clist) + device = bdev.Create(disk, clist, excl_stor) except errors.BlockDeviceError, err: _Fail("Can't create block device: %s", err) @@ -1804,7 +2201,7 @@ def BlockdevAssemble(disk, owner, as_primary, idx): """ try: result = _RecursiveAssembleBD(disk, owner, as_primary) - if isinstance(result, bdev.BlockDev): + if isinstance(result, BlockDev): # pylint: disable=E1103 result = result.dev_path if as_primary: @@ -2015,7 +2412,7 @@ def BlockdevFind(disk): return rbd.GetSyncStatus() -def BlockdevGetsize(disks): +def BlockdevGetdimensions(disks): """Computes the size of the given disks. If a disk is not found, returns None instead. @@ -2024,7 +2421,8 @@ def BlockdevGetsize(disks): @param disks: the list of disk to compute the size for @rtype: list @return: list with elements None if the disk cannot be found, - otherwise the size + otherwise the pair (size, spindles), where spindles is None if the + device doesn't support that """ result = [] @@ -2037,17 +2435,17 @@ def BlockdevGetsize(disks): if rbd is None: result.append(None) else: - result.append(rbd.GetActualSize()) + result.append(rbd.GetActualDimensions()) return result -def BlockdevExport(disk, dest_node, dest_path, cluster_name): +def BlockdevExport(disk, dest_node_ip, dest_path, cluster_name): """Export a block device to a remote node. @type disk: L{objects.Disk} @param disk: the description of the disk to export - @type dest_node: str - @param dest_node: the destination node to export to + @type dest_node_ip: str + @param dest_node_ip: the destination node IP to export to @type dest_path: str @param dest_path: the destination path on the target node @type cluster_name: str @@ -2071,7 +2469,7 @@ def BlockdevExport(disk, dest_node, dest_path, cluster_name): destcmd = utils.BuildShellCmd("dd of=%s conv=nocreat,notrunc bs=65536" " oflag=dsync", dest_path) - remotecmd = _GetSshRunner(cluster_name).BuildCmd(dest_node, + remotecmd = _GetSshRunner(cluster_name).BuildCmd(dest_node_ip, constants.SSH_LOGIN_USER, destcmd) @@ -2443,18 +2841,24 @@ def OSEnvironment(instance, inst_os, debug=0): real_disk = _OpenRealBD(disk) result["DISK_%d_PATH" % idx] = real_disk.dev_path result["DISK_%d_ACCESS" % idx] = disk.mode + result["DISK_%d_UUID" % idx] = disk.uuid + if disk.name: + result["DISK_%d_NAME" % idx] = disk.name if constants.HV_DISK_TYPE in instance.hvparams: result["DISK_%d_FRONTEND_TYPE" % idx] = \ instance.hvparams[constants.HV_DISK_TYPE] - if disk.dev_type in constants.LDS_BLOCK: + if disk.dev_type in constants.DTS_BLOCK: result["DISK_%d_BACKEND_TYPE" % idx] = "block" - elif disk.dev_type == constants.LD_FILE: + elif disk.dev_type in [constants.DT_FILE, constants.DT_SHARED_FILE]: result["DISK_%d_BACKEND_TYPE" % idx] = \ - "file:%s" % disk.physical_id[0] + "file:%s" % disk.logical_id[0] # NICs for idx, nic in enumerate(instance.nics): result["NIC_%d_MAC" % idx] = nic.mac + result["NIC_%d_UUID" % idx] = nic.uuid + if nic.name: + result["NIC_%d_NAME" % idx] = nic.name if nic.ip: result["NIC_%d_IP" % idx] = nic.ip result["NIC_%d_MODE" % idx] = nic.nicparams[constants.NIC_MODE] @@ -2462,8 +2866,9 @@ def OSEnvironment(instance, inst_os, debug=0): result["NIC_%d_BRIDGE" % idx] = nic.nicparams[constants.NIC_LINK] if nic.nicparams[constants.NIC_LINK]: result["NIC_%d_LINK" % idx] = nic.nicparams[constants.NIC_LINK] - if nic.network: - result["NIC_%d_NETWORK" % idx] = nic.network + if nic.netinfo: + nobj = objects.Network.FromDict(nic.netinfo) + result.update(nobj.HooksDict("NIC_%d_" % idx)) if constants.HV_NIC_TYPE in instance.hvparams: result["NIC_%d_FRONTEND_TYPE" % idx] = \ instance.hvparams[constants.HV_NIC_TYPE] @@ -2476,7 +2881,52 @@ def OSEnvironment(instance, inst_os, debug=0): return result -def BlockdevGrow(disk, amount, dryrun, backingstore): +def DiagnoseExtStorage(top_dirs=None): + """Compute the validity for all ExtStorage Providers. + + @type top_dirs: list + @param top_dirs: the list of directories in which to + search (if not given defaults to + L{pathutils.ES_SEARCH_PATH}) + @rtype: list of L{objects.ExtStorage} + @return: a list of tuples (name, path, status, diagnose, parameters) + for all (potential) ExtStorage Providers under all + search paths, where: + - name is the (potential) ExtStorage Provider + - path is the full path to the ExtStorage Provider + - status True/False is the validity of the ExtStorage Provider + - diagnose is the error message for an invalid ExtStorage Provider, + otherwise empty + - parameters is a list of (name, help) parameters, if any + + """ + if top_dirs is None: + top_dirs = pathutils.ES_SEARCH_PATH + + result = [] + for dir_name in top_dirs: + if os.path.isdir(dir_name): + try: + f_names = utils.ListVisibleFiles(dir_name) + except EnvironmentError, err: + logging.exception("Can't list the ExtStorage directory %s: %s", + dir_name, err) + break + for name in f_names: + es_path = utils.PathJoin(dir_name, name) + status, es_inst = bdev.ExtStorageFromDisk(name, base_dir=dir_name) + if status: + diagnose = "" + parameters = es_inst.supported_parameters + else: + diagnose = es_inst + parameters = [] + result.append((name, es_path, status, diagnose, parameters)) + + return result + + +def BlockdevGrow(disk, amount, dryrun, backingstore, excl_stor): """Grow a stack of block devices. This function is called recursively, with the childrens being the @@ -2493,6 +2943,8 @@ def BlockdevGrow(disk, amount, dryrun, backingstore): only, or on "logical" storage only; e.g. DRBD is logical storage, whereas LVM, file, RBD are backing storage @rtype: (status, result) + @type excl_stor: boolean + @param excl_stor: Whether exclusive_storage is active @return: a tuple with the status of the operation (True/False), and the errors message if status is False @@ -2502,7 +2954,7 @@ def BlockdevGrow(disk, amount, dryrun, backingstore): _Fail("Cannot find block device %s", disk) try: - r_dev.Grow(amount, dryrun, backingstore) + r_dev.Grow(amount, dryrun, backingstore, excl_stor) except errors.BlockDeviceError, err: _Fail("Failed to grow block device: %s", err, exc=True) @@ -2519,12 +2971,12 @@ def BlockdevSnapshot(disk): @return: snapshot disk ID as (vg, lv) """ - if disk.dev_type == constants.LD_DRBD8: + if disk.dev_type == constants.DT_DRBD8: if not disk.children: _Fail("DRBD device '%s' without backing storage cannot be snapshotted", disk.unique_id) return BlockdevSnapshot(disk.children[0]) - elif disk.dev_type == constants.LD_LV: + elif disk.dev_type == constants.DT_PLAIN: r_dev = _RecursiveFindBD(disk) if r_dev is not None: # FIXME: choose a saner value for the snapshot size @@ -2609,6 +3061,8 @@ def FinalizeExport(instance, snap_disks): config.set(constants.INISECT_INS, "nic%d_mac" % nic_count, "%s" % nic.mac) config.set(constants.INISECT_INS, "nic%d_ip" % nic_count, "%s" % nic.ip) + config.set(constants.INISECT_INS, "nic%d_network" % nic_count, + "%s" % nic.network) for param in constants.NICS_PARAMETER_TYPES: config.set(constants.INISECT_INS, "nic%d_%s" % (nic_count, param), "%s" % nic.nicparams.get(param, None)) @@ -2622,7 +3076,7 @@ def FinalizeExport(instance, snap_disks): config.set(constants.INISECT_INS, "disk%d_ivname" % disk_count, ("%s" % disk.iv_name)) config.set(constants.INISECT_INS, "disk%d_dump" % disk_count, - ("%s" % disk.physical_id[1])) + ("%s" % disk.logical_id[1])) config.set(constants.INISECT_INS, "disk%d_size" % disk_count, ("%d" % disk.size)) @@ -2705,11 +3159,9 @@ def BlockdevRename(devlist): """Rename a list of block devices. @type devlist: list of tuples - @param devlist: list of tuples of the form (disk, - new_logical_id, new_physical_id); disk is an - L{objects.Disk} object describing the current disk, - and new logical_id/physical_id is the name we - rename it to + @param devlist: list of tuples of the form (disk, new_unique_id); disk is + an L{objects.Disk} object describing the current disk, and new + unique_id is the name we rename it to @rtype: boolean @return: True if all renames succeeded, False otherwise @@ -2755,11 +3207,7 @@ def _TransformFileStorageDir(fs_dir): @return: the normalized path if valid, None otherwise """ - if not (constants.ENABLE_FILE_STORAGE or - constants.ENABLE_SHARED_FILE_STORAGE): - _Fail("File storage disabled at configure time") - - bdev.CheckFileStoragePath(fs_dir) + filestorage.CheckFileStoragePath(fs_dir) return os.path.normpath(fs_dir) @@ -2878,7 +3326,7 @@ def JobQueueUpdate(file_name, content): # Write and replace the file atomically utils.WriteFile(file_name, data=_Decompress(content), uid=getents.masterd_uid, - gid=getents.masterd_gid) + gid=getents.daemons_gid, mode=constants.JOB_QUEUE_FILES_PERMS) def JobQueueRename(old, new): @@ -2902,8 +3350,8 @@ def JobQueueRename(old, new): getents = runtime.GetEnts() - utils.RenameFile(old, new, mkdir=True, mkdir_mode=0700, - dir_uid=getents.masterd_uid, dir_gid=getents.masterd_gid) + utils.RenameFile(old, new, mkdir=True, mkdir_mode=0750, + dir_uid=getents.masterd_uid, dir_gid=getents.daemons_gid) def BlockdevClose(instance_name, disks): @@ -3184,8 +3632,6 @@ def _GetImportExportIoCommand(instance, mode, ieio, ieargs): assert isinstance(disk_index, (int, long)) - real_disk = _OpenRealBD(disk) - inst_os = OSFromDisk(instance.os) env = OSEnvironment(instance, inst_os) @@ -3195,6 +3641,7 @@ def _GetImportExportIoCommand(instance, mode, ieio, ieargs): script = inst_os.import_script elif mode == constants.IEM_EXPORT: + real_disk = _OpenRealBD(disk) env["EXPORT_DEVICE"] = real_disk.dev_path env["EXPORT_INDEX"] = str(disk_index) script = inst_os.export_script @@ -3422,30 +3869,31 @@ def CleanupImportExport(name): shutil.rmtree(status_dir, ignore_errors=True) -def _FindDisks(nodes_ip, disks): - """Sets the physical ID on disks and returns the block devices. +def _FindDisks(disks): + """Finds attached L{BlockDev}s for the given disks. - """ - # set the correct physical ID - my_name = netutils.Hostname.GetSysName() - for cf in disks: - cf.SetPhysicalID(my_name, nodes_ip) + @type disks: list of L{objects.Disk} + @param disks: the disk objects we need to find + @return: list of L{BlockDev} objects or C{None} if a given disk + was not found or was no attached. + + """ bdevs = [] - for cf in disks: - rd = _RecursiveFindBD(cf) + for disk in disks: + rd = _RecursiveFindBD(disk) if rd is None: - _Fail("Can't find device %s", cf) + _Fail("Can't find device %s", disk) bdevs.append(rd) return bdevs -def DrbdDisconnectNet(nodes_ip, disks): +def DrbdDisconnectNet(disks): """Disconnects the network on a list of drbd devices. """ - bdevs = _FindDisks(nodes_ip, disks) + bdevs = _FindDisks(disks) # disconnect disks for rd in bdevs: @@ -3456,11 +3904,11 @@ def DrbdDisconnectNet(nodes_ip, disks): err, exc=True) -def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster): +def DrbdAttachNet(disks, instance_name, multimaster): """Attaches the network on a list of drbd devices. """ - bdevs = _FindDisks(nodes_ip, disks) + bdevs = _FindDisks(disks) if multimaster: for idx, rd in enumerate(bdevs): @@ -3518,7 +3966,7 @@ def DrbdAttachNet(nodes_ip, disks, instance_name, multimaster): _Fail("Can't change to primary mode: %s", err) -def DrbdWaitSync(nodes_ip, disks): +def DrbdWaitSync(disks): """Wait until DRBDs have synchronized. """ @@ -3528,7 +3976,7 @@ def DrbdWaitSync(nodes_ip, disks): raise utils.RetryAgain() return stats - bdevs = _FindDisks(nodes_ip, disks) + bdevs = _FindDisks(disks) min_resync = 100 alldone = True @@ -3548,17 +3996,36 @@ def DrbdWaitSync(nodes_ip, disks): return (alldone, min_resync) +def DrbdNeedsActivation(disks): + """Checks which of the passed disks needs activation and returns their UUIDs. + + """ + faulty_disks = [] + + for disk in disks: + rd = _RecursiveFindBD(disk) + if rd is None: + faulty_disks.append(disk) + continue + + stats = rd.GetProcStatus() + if stats.is_standalone or stats.is_diskless: + faulty_disks.append(disk) + + return [disk.uuid for disk in faulty_disks] + + def GetDrbdUsermodeHelper(): """Returns DRBD usermode helper currently configured. """ try: - return bdev.BaseDRBD.GetUsermodeHelper() + return drbd.DRBD8.GetUsermodeHelper() except errors.BlockDeviceError, err: _Fail(str(err)) -def PowercycleNode(hypervisor_type): +def PowercycleNode(hypervisor_type, hvparams=None): """Hard-powercycle the node. Because we need to return first, and schedule the powercycle in the @@ -3579,11 +4046,11 @@ def PowercycleNode(hypervisor_type): except Exception: # pylint: disable=W0703 pass time.sleep(5) - hyper.PowercycleNode() + hyper.PowercycleNode(hvparams=hvparams) -def _VerifyRemoteCommandName(cmd): - """Verifies a remote command name. +def _VerifyRestrictedCmdName(cmd): + """Verifies a restricted command name. @type cmd: string @param cmd: Command name @@ -3604,8 +4071,8 @@ def _VerifyRemoteCommandName(cmd): return (True, None) -def _CommonRemoteCommandCheck(path, owner): - """Common checks for remote command file system directories and files. +def _CommonRestrictedCmdCheck(path, owner): + """Common checks for restricted command file system directories and files. @type path: string @param path: Path to check @@ -3634,8 +4101,8 @@ def _CommonRemoteCommandCheck(path, owner): return (True, st) -def _VerifyRemoteCommandDirectory(path, _owner=None): - """Verifies remote command directory. +def _VerifyRestrictedCmdDirectory(path, _owner=None): + """Verifies restricted command directory. @type path: string @param path: Path to check @@ -3644,7 +4111,7 @@ def _VerifyRemoteCommandDirectory(path, _owner=None): element is an error message string, otherwise it's C{None} """ - (status, value) = _CommonRemoteCommandCheck(path, _owner) + (status, value) = _CommonRestrictedCmdCheck(path, _owner) if not status: return (False, value) @@ -3655,11 +4122,11 @@ def _VerifyRemoteCommandDirectory(path, _owner=None): return (True, None) -def _VerifyRemoteCommand(path, cmd, _owner=None): - """Verifies a whole remote command and returns its executable filename. +def _VerifyRestrictedCmd(path, cmd, _owner=None): + """Verifies a whole restricted command and returns its executable filename. @type path: string - @param path: Directory containing remote commands + @param path: Directory containing restricted commands @type cmd: string @param cmd: Command name @rtype: tuple; (boolean, string) @@ -3670,7 +4137,7 @@ def _VerifyRemoteCommand(path, cmd, _owner=None): """ executable = utils.PathJoin(path, cmd) - (status, msg) = _CommonRemoteCommandCheck(executable, _owner) + (status, msg) = _CommonRestrictedCmdCheck(executable, _owner) if not status: return (False, msg) @@ -3681,17 +4148,17 @@ def _VerifyRemoteCommand(path, cmd, _owner=None): return (True, executable) -def _PrepareRemoteCommand(path, cmd, - _verify_dir=_VerifyRemoteCommandDirectory, - _verify_name=_VerifyRemoteCommandName, - _verify_cmd=_VerifyRemoteCommand): - """Performs a number of tests on a remote command. +def _PrepareRestrictedCmd(path, cmd, + _verify_dir=_VerifyRestrictedCmdDirectory, + _verify_name=_VerifyRestrictedCmdName, + _verify_cmd=_VerifyRestrictedCmd): + """Performs a number of tests on a restricted command. @type path: string - @param path: Directory containing remote commands + @param path: Directory containing restricted commands @type cmd: string @param cmd: Command name - @return: Same as L{_VerifyRemoteCommand} + @return: Same as L{_VerifyRestrictedCmd} """ # Verify the directory first @@ -3707,15 +4174,15 @@ def _PrepareRemoteCommand(path, cmd, return _verify_cmd(path, cmd) -def RunRemoteCommand(cmd, +def RunRestrictedCmd(cmd, _lock_timeout=_RCMD_LOCK_TIMEOUT, - _lock_file=pathutils.REMOTE_COMMANDS_LOCK_FILE, - _path=pathutils.REMOTE_COMMANDS_DIR, + _lock_file=pathutils.RESTRICTED_COMMANDS_LOCK_FILE, + _path=pathutils.RESTRICTED_COMMANDS_DIR, _sleep_fn=time.sleep, - _prepare_fn=_PrepareRemoteCommand, + _prepare_fn=_PrepareRestrictedCmd, _runcmd_fn=utils.RunCmd, _enabled=constants.ENABLE_RESTRICTED_COMMANDS): - """Executes a remote command after performing strict tests. + """Executes a restricted command after performing strict tests. @type cmd: string @param cmd: Command name @@ -3724,10 +4191,10 @@ def RunRemoteCommand(cmd, @raise RPCFail: In case of an error """ - logging.info("Preparing to run remote command '%s'", cmd) + logging.info("Preparing to run restricted command '%s'", cmd) if not _enabled: - _Fail("Remote commands disabled at configure time") + _Fail("Restricted commands disabled at configure time") lock = None try: @@ -3755,7 +4222,7 @@ def RunRemoteCommand(cmd, # Do not include original error message in returned error _Fail("Executing command '%s' failed" % cmd) elif cmdresult.failed or cmdresult.fail_reason: - _Fail("Remote command '%s' failed: %s; output: %s", + _Fail("Restricted command '%s' failed: %s; output: %s", cmd, cmdresult.fail_reason, cmdresult.output) else: return cmdresult.output @@ -3766,6 +4233,52 @@ def RunRemoteCommand(cmd, lock = None +def SetWatcherPause(until, _filename=pathutils.WATCHER_PAUSEFILE): + """Creates or removes the watcher pause file. + + @type until: None or number + @param until: Unix timestamp saying until when the watcher shouldn't run + + """ + if until is None: + logging.info("Received request to no longer pause watcher") + utils.RemoveFile(_filename) + else: + logging.info("Received request to pause watcher until %s", until) + + if not ht.TNumber(until): + _Fail("Duration must be numeric") + + utils.WriteFile(_filename, data="%d\n" % (until, ), mode=0644) + + +def ConfigureOVS(ovs_name, ovs_link): + """Creates a OpenvSwitch on the node. + + This function sets up a OpenvSwitch on the node with given name nad + connects it via a given eth device. + + @type ovs_name: string + @param ovs_name: Name of the OpenvSwitch to create. + @type ovs_link: None or string + @param ovs_link: Ethernet device for outside connection (can be missing) + + """ + # Initialize the OpenvSwitch + result = utils.RunCmd(["ovs-vsctl", "add-br", ovs_name]) + if result.failed: + _Fail("Failed to create openvswitch %s. Script return value: %s, output:" + " '%s'" % result.exit_code, result.output, log=True) + + # And connect it to a physical interface, if given + if ovs_link: + result = utils.RunCmd(["ovs-vsctl", "add-port", ovs_name, ovs_link]) + if result.failed: + _Fail("Failed to connect openvswitch to interface %s. Script return" + " value: %s, output: '%s'" % ovs_link, result.exit_code, + result.output, log=True) + + class HooksRunner(object): """Hook runner.