X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/0cf5e7f559f47e015e16c79a8c4becb705ef0db5..791f317d1395440a966631c9fb9fa40a7c40e950:/lib/cmdlib.py diff --git a/lib/cmdlib.py b/lib/cmdlib.py index 4b6b778..f15cd14 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -33,6 +33,7 @@ import re import platform import logging import copy +import OpenSSL from ganeti import ssh from ganeti import utils @@ -541,6 +542,50 @@ def _CheckNodeNotDrained(lu, node): errors.ECODE_INVAL) +def _CheckNodeHasOS(lu, node, os_name, force_variant): + """Ensure that a node supports a given OS. + + @param lu: the LU on behalf of which we make the check + @param node: the node to check + @param os_name: the OS to query about + @param force_variant: whether to ignore variant errors + @raise errors.OpPrereqError: if the node is not supporting the OS + + """ + result = lu.rpc.call_os_get(node, os_name) + result.Raise("OS '%s' not in supported OS list for node %s" % + (os_name, node), + prereq=True, ecode=errors.ECODE_INVAL) + if not force_variant: + _CheckOSVariant(result.payload, os_name) + + +def _CheckDiskTemplate(template): + """Ensure a given disk template is valid. + + """ + if template not in constants.DISK_TEMPLATES: + msg = ("Invalid disk template name '%s', valid templates are: %s" % + (template, utils.CommaJoin(constants.DISK_TEMPLATES))) + raise errors.OpPrereqError(msg, errors.ECODE_INVAL) + + +def _CheckInstanceDown(lu, instance, reason): + """Ensure that an instance is not running.""" + if instance.admin_up: + raise errors.OpPrereqError("Instance %s is marked to be up, %s" % + (instance.name, reason), errors.ECODE_STATE) + + pnode = instance.primary_node + ins_l = lu.rpc.call_instance_list([pnode], [instance.hypervisor])[pnode] + ins_l.Raise("Can't contact node %s for instance information" % pnode, + prereq=True, ecode=errors.ECODE_ENVIRON) + + if instance.name in ins_l.payload: + raise errors.OpPrereqError("Instance %s is running, %s" % + (instance.name, reason), errors.ECODE_STATE) + + def _ExpandItemName(fn, name, kind): """Expand an item name. @@ -848,6 +893,13 @@ def _FindFaultyInstanceDisks(cfg, rpc, instance, node_name, prereq): return faulty +def _FormatTimestamp(secs): + """Formats a Unix timestamp with the local timezone. + + """ + return time.strftime("%F %T %Z", time.gmtime(secs)) + + class LUPostInitCluster(LogicalUnit): """Logical unit for running hooks after cluster initialization. @@ -939,6 +991,66 @@ class LUDestroyCluster(LogicalUnit): return master +def _VerifyCertificateInner(filename, expired, not_before, not_after, now, + warn_days=constants.SSL_CERT_EXPIRATION_WARN, + error_days=constants.SSL_CERT_EXPIRATION_ERROR): + """Verifies certificate details for LUVerifyCluster. + + """ + if expired: + msg = "Certificate %s is expired" % filename + + if not_before is not None and not_after is not None: + msg += (" (valid from %s to %s)" % + (_FormatTimestamp(not_before), + _FormatTimestamp(not_after))) + elif not_before is not None: + msg += " (valid from %s)" % _FormatTimestamp(not_before) + elif not_after is not None: + msg += " (valid until %s)" % _FormatTimestamp(not_after) + + return (LUVerifyCluster.ETYPE_ERROR, msg) + + elif not_before is not None and not_before > now: + return (LUVerifyCluster.ETYPE_WARNING, + "Certificate %s not yet valid (valid from %s)" % + (filename, _FormatTimestamp(not_before))) + + elif not_after is not None: + remaining_days = int((not_after - now) / (24 * 3600)) + + msg = ("Certificate %s expires in %d days" % (filename, remaining_days)) + + if remaining_days <= error_days: + return (LUVerifyCluster.ETYPE_ERROR, msg) + + if remaining_days <= warn_days: + return (LUVerifyCluster.ETYPE_WARNING, msg) + + return (None, None) + + +def _VerifyCertificate(filename): + """Verifies a certificate for LUVerifyCluster. + + @type filename: string + @param filename: Path to PEM file + + """ + try: + cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, + utils.ReadFile(filename)) + except Exception, err: # pylint: disable-msg=W0703 + return (LUVerifyCluster.ETYPE_ERROR, + "Failed to load X509 certificate %s: %s" % (filename, err)) + + # Depending on the pyOpenSSL version, this can just return (None, None) + (not_before, not_after) = utils.GetX509CertValidity(cert) + + return _VerifyCertificateInner(filename, cert.has_expired(), + not_before, not_after, time.time()) + + class LUVerifyCluster(LogicalUnit): """Verifies the cluster status. @@ -953,6 +1065,7 @@ class LUVerifyCluster(LogicalUnit): TINSTANCE = "instance" ECLUSTERCFG = (TCLUSTER, "ECLUSTERCFG") + ECLUSTERCERT = (TCLUSTER, "ECLUSTERCERT") EINSTANCEBADNODE = (TINSTANCE, "EINSTANCEBADNODE") EINSTANCEDOWN = (TINSTANCE, "EINSTANCEDOWN") EINSTANCELAYOUT = (TINSTANCE, "EINSTANCELAYOUT") @@ -1315,6 +1428,11 @@ class LUVerifyCluster(LogicalUnit): for msg in self.cfg.VerifyConfig(): _ErrorIf(True, self.ECLUSTERCFG, None, msg) + # Check the cluster certificates + for cert_filename in constants.ALL_CERT_FILES: + (errcode, msg) = _VerifyCertificate(cert_filename) + _ErrorIf(errcode, self.ECLUSTERCERT, None, msg, code=errcode) + vg_name = self.cfg.GetVGName() hypervisors = self.cfg.GetClusterInfo().enabled_hypervisors nodelist = utils.NiceSort(self.cfg.GetNodeList()) @@ -1336,8 +1454,7 @@ class LUVerifyCluster(LogicalUnit): master_files = [constants.CLUSTER_CONF_FILE] file_names = ssconf.SimpleStore().GetFileList() - file_names.append(constants.SSL_CERT_FILE) - file_names.append(constants.RAPI_CERT_FILE) + file_names.extend(constants.ALL_CERT_FILES) file_names.extend(master_files) local_checksums = utils.FingerprintFiles(file_names) @@ -2111,6 +2228,20 @@ class LUSetClusterParams(LogicalUnit): hv_class.CheckParameterSyntax(hv_params) _CheckHVParams(self, node_list, hv_name, hv_params) + if self.op.os_hvp: + # no need to check any newly-enabled hypervisors, since the + # defaults have already been checked in the above code-block + for os_name, os_hvp in self.new_os_hvp.items(): + for hv_name, hv_params in os_hvp.items(): + utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES) + # we need to fill in the new os_hvp on top of the actual hv_p + cluster_defaults = self.new_hvparams.get(hv_name, {}) + new_osp = objects.FillDict(cluster_defaults, hv_params) + hv_class = hypervisor.GetHypervisor(hv_name) + hv_class.CheckParameterSyntax(new_osp) + _CheckHVParams(self, node_list, hv_name, new_osp) + + def Exec(self, feedback_fn): """Change the parameters of the cluster. @@ -2167,7 +2298,7 @@ def _RedistributeAncillaryFiles(lu, additional_nodes=None): constants.SSH_KNOWN_HOSTS_FILE, constants.RAPI_CERT_FILE, constants.RAPI_USERS_FILE, - constants.HMAC_CLUSTER_KEY, + constants.CONFD_HMAC_KEY, ]) enabled_hypervisors = lu.cfg.GetClusterInfo().enabled_hypervisors @@ -3226,7 +3357,7 @@ class LUSetNodeParams(LogicalUnit): # candidates (mc_remaining, mc_should, _) = \ self.cfg.GetMasterCandidateStats(exceptions=[node.name]) - if mc_remaining != mc_should: + if mc_remaining < mc_should: raise errors.OpPrereqError("Not enough master candidates, please" " pass auto_promote to allow promotion", errors.ECODE_INVAL) @@ -3633,14 +3764,7 @@ def _SafeShutdownInstanceDisks(lu, instance): _ShutdownInstanceDisks. """ - pnode = instance.primary_node - ins_l = lu.rpc.call_instance_list([pnode], [instance.hypervisor])[pnode] - ins_l.Raise("Can't contact node %s" % pnode) - - if instance.name in ins_l.payload: - raise errors.OpExecError("Instance is running, can't shutdown" - " block devices.") - + _CheckInstanceDown(lu, instance, "cannot shutdown disks") _ShutdownInstanceDisks(lu, instance) @@ -3704,6 +3828,42 @@ def _CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name): errors.ECODE_NORES) +def _CheckNodesFreeDisk(lu, nodenames, requested): + """Checks if nodes have enough free disk space in the default VG. + + This function check if all given nodes have the needed amount of + free disk. In case any node has less disk or we cannot get the + information from the node, this function raise an OpPrereqError + exception. + + @type lu: C{LogicalUnit} + @param lu: a logical unit from which we get configuration data + @type nodenames: C{list} + @param node: the list of node names to check + @type requested: C{int} + @param requested: the amount of disk in MiB to check for + @raise errors.OpPrereqError: if the node doesn't have enough disk, or + we cannot check the node + + """ + nodeinfo = lu.rpc.call_node_info(nodenames, lu.cfg.GetVGName(), + lu.cfg.GetHypervisorType()) + for node in nodenames: + info = nodeinfo[node] + info.Raise("Cannot get current information from node %s" % node, + prereq=True, ecode=errors.ECODE_ENVIRON) + vg_free = info.payload.get("vg_free", None) + if not isinstance(vg_free, int): + raise errors.OpPrereqError("Can't compute free disk space on node %s," + " result was '%s'" % (node, vg_free), + errors.ECODE_ENVIRON) + if requested > vg_free: + raise errors.OpPrereqError("Not enough disk space on target node %s:" + " required %d MiB, available %d MiB" % + (node, requested, vg_free), + errors.ECODE_NORES) + + class LUStartupInstance(LogicalUnit): """Starts an instance. @@ -3990,32 +4150,14 @@ class LUReinstallInstance(LogicalUnit): raise errors.OpPrereqError("Instance '%s' has no disks" % self.op.instance_name, errors.ECODE_INVAL) - if instance.admin_up: - raise errors.OpPrereqError("Instance '%s' is marked to be up" % - self.op.instance_name, - errors.ECODE_STATE) - remote_info = self.rpc.call_instance_info(instance.primary_node, - instance.name, - instance.hypervisor) - remote_info.Raise("Error checking node %s" % instance.primary_node, - prereq=True, ecode=errors.ECODE_ENVIRON) - if remote_info.payload: - raise errors.OpPrereqError("Instance '%s' is running on the node %s" % - (self.op.instance_name, - instance.primary_node), - errors.ECODE_STATE) + _CheckInstanceDown(self, instance, "cannot reinstall") self.op.os_type = getattr(self.op, "os_type", None) self.op.force_variant = getattr(self.op, "force_variant", False) if self.op.os_type is not None: # OS verification pnode = _ExpandNodeName(self.cfg, instance.primary_node) - result = self.rpc.call_os_get(pnode, self.op.os_type) - result.Raise("OS '%s' not in supported OS list for primary node %s" % - (self.op.os_type, pnode), - prereq=True, ecode=errors.ECODE_INVAL) - if not self.op.force_variant: - _CheckOSVariant(result.payload, self.op.os_type) + _CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant) self.instance = instance @@ -4090,18 +4232,7 @@ class LURecreateInstanceDisks(LogicalUnit): if instance.disk_template == constants.DT_DISKLESS: raise errors.OpPrereqError("Instance '%s' has no disks" % self.op.instance_name, errors.ECODE_INVAL) - if instance.admin_up: - raise errors.OpPrereqError("Instance '%s' is marked to be up" % - self.op.instance_name, errors.ECODE_STATE) - remote_info = self.rpc.call_instance_info(instance.primary_node, - instance.name, - instance.hypervisor) - remote_info.Raise("Error checking node %s" % instance.primary_node, - prereq=True, ecode=errors.ECODE_ENVIRON) - if remote_info.payload: - raise errors.OpPrereqError("Instance '%s' is running on the node %s" % - (self.op.instance_name, - instance.primary_node), errors.ECODE_STATE) + _CheckInstanceDown(self, instance, "cannot recreate disks") if not self.op.disks: self.op.disks = range(len(instance.disks)) @@ -4156,19 +4287,7 @@ class LURenameInstance(LogicalUnit): instance = self.cfg.GetInstanceInfo(self.op.instance_name) assert instance is not None _CheckNodeOnline(self, instance.primary_node) - - if instance.admin_up: - raise errors.OpPrereqError("Instance '%s' is marked to be up" % - self.op.instance_name, errors.ECODE_STATE) - remote_info = self.rpc.call_instance_info(instance.primary_node, - instance.name, - instance.hypervisor) - remote_info.Raise("Error checking node %s" % instance.primary_node, - prereq=True, ecode=errors.ECODE_ENVIRON) - if remote_info.payload: - raise errors.OpPrereqError("Instance '%s' is running on the node %s" % - (self.op.instance_name, - instance.primary_node), errors.ECODE_STATE) + _CheckInstanceDown(self, instance, "cannot rename") self.instance = instance # new name verification @@ -4295,18 +4414,29 @@ class LURemoveInstance(LogicalUnit): " node %s: %s" % (instance.name, instance.primary_node, msg)) - logging.info("Removing block devices for instance %s", instance.name) + _RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures) - if not _RemoveDisks(self, instance): - if self.op.ignore_failures: - feedback_fn("Warning: can't remove instance's disks") - else: - raise errors.OpExecError("Can't remove instance's disks") - logging.info("Removing instance %s out of cluster config", instance.name) +def _RemoveInstance(lu, feedback_fn, instance, ignore_failures): + """Utility function to remove an instance. + + """ + logging.info("Removing block devices for instance %s", instance.name) + + if not _RemoveDisks(lu, instance): + if not ignore_failures: + raise errors.OpExecError("Can't remove instance's disks") + feedback_fn("Warning: can't remove instance's disks") + + logging.info("Removing instance %s out of cluster config", instance.name) - self.cfg.RemoveInstance(instance.name) - self.remove_locks[locking.LEVEL_INSTANCE] = instance.name + lu.cfg.RemoveInstance(instance.name) + + assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \ + "Instance lock removal conflict" + + # Remove lock for the instance + lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name class LUQueryInstances(NoHooksLU): @@ -5698,10 +5828,20 @@ class LUCreateInstance(LogicalUnit): """Check arguments. """ + # set optional parameters to none if they don't exist + for attr in ["pnode", "snode", "iallocator", "hypervisor"]: + if not hasattr(self.op, attr): + setattr(self.op, attr, None) + # do not require name_check to ease forward/backward compatibility # for tools if not hasattr(self.op, "name_check"): self.op.name_check = True + if not hasattr(self.op, "no_install"): + self.op.no_install = False + if self.op.no_install and self.op.start: + self.LogInfo("No-installation mode selected, disabling startup") + self.op.start = False # validate/normalize the instance name self.op.instance_name = utils.HostInfo.NormalizeName(self.op.instance_name) if self.op.ip_check and not self.op.name_check: @@ -5712,6 +5852,29 @@ class LUCreateInstance(LogicalUnit): not constants.ENABLE_FILE_STORAGE): raise errors.OpPrereqError("File storage disabled at configure time", errors.ECODE_INVAL) + # check disk information: either all adopt, or no adopt + has_adopt = has_no_adopt = False + for disk in self.op.disks: + if "adopt" in disk: + has_adopt = True + else: + has_no_adopt = True + if has_adopt and has_no_adopt: + raise errors.OpPrereqError("Either all disks have are adoped or none is", + errors.ECODE_INVAL) + if has_adopt: + if self.op.disk_template != constants.DT_PLAIN: + raise errors.OpPrereqError("Disk adoption is only supported for the" + " 'plain' disk template", + errors.ECODE_INVAL) + if self.op.iallocator is not None: + raise errors.OpPrereqError("Disk adoption not allowed with an" + " iallocator script", errors.ECODE_INVAL) + if self.op.mode == constants.INSTANCE_IMPORT: + raise errors.OpPrereqError("Disk adoption not allowed for" + " instance import", errors.ECODE_INVAL) + + self.adopt_disks = has_adopt def ExpandNames(self): """ExpandNames for CreateInstance. @@ -5721,11 +5884,6 @@ class LUCreateInstance(LogicalUnit): """ self.needed_locks = {} - # set optional parameters to none if they don't exist - for attr in ["pnode", "snode", "iallocator", "hypervisor"]: - if not hasattr(self.op, attr): - setattr(self.op, attr, None) - # cheap checks, mostly valid constants given # verify creation mode @@ -5735,9 +5893,7 @@ class LUCreateInstance(LogicalUnit): self.op.mode, errors.ECODE_INVAL) # disk template and mirror node verification - if self.op.disk_template not in constants.DISK_TEMPLATES: - raise errors.OpPrereqError("Invalid disk template name", - errors.ECODE_INVAL) + _CheckDiskTemplate(self.op.disk_template) if self.op.hypervisor is None: self.op.hypervisor = self.cfg.GetHypervisorType() @@ -5871,7 +6027,10 @@ class LUCreateInstance(LogicalUnit): except (TypeError, ValueError): raise errors.OpPrereqError("Invalid disk size '%s'" % size, errors.ECODE_INVAL) - self.disks.append({"size": size, "mode": mode}) + new_disk = {"size": size, "mode": mode} + if "adopt" in disk: + new_disk["adopt"] = disk["adopt"] + self.disks.append(new_disk) # file storage checks if (self.op.file_driver and @@ -5927,6 +6086,9 @@ class LUCreateInstance(LogicalUnit): # works again! self.op.force_variant = True + if self.op.no_install: + self.LogInfo("No-installation mode has no effect during import") + else: # INSTANCE_CREATE if getattr(self.op, "os_type", None) is None: raise errors.OpPrereqError("No guest OS specified", @@ -6004,7 +6166,6 @@ class LUCreateInstance(LogicalUnit): self.secondaries) return env, nl, nl - def CheckPrereq(self): """Check prerequisites. @@ -6140,33 +6301,43 @@ class LUCreateInstance(LogicalUnit): req_size = _ComputeDiskSize(self.op.disk_template, self.disks) - # Check lv size requirements - if req_size is not None: - nodeinfo = self.rpc.call_node_info(nodenames, self.cfg.GetVGName(), - self.op.hypervisor) - for node in nodenames: - info = nodeinfo[node] - info.Raise("Cannot get current information from node %s" % node) - info = info.payload - vg_free = info.get('vg_free', None) - if not isinstance(vg_free, int): - raise errors.OpPrereqError("Can't compute free disk space on" - " node %s" % node, errors.ECODE_ENVIRON) - if req_size > vg_free: - raise errors.OpPrereqError("Not enough disk space on target node %s." - " %d MB available, %d MB required" % - (node, vg_free, req_size), - errors.ECODE_NORES) + # Check lv size requirements, if not adopting + if req_size is not None and not self.adopt_disks: + _CheckNodesFreeDisk(self, nodenames, req_size) + + if self.adopt_disks: # instead, we must check the adoption data + all_lvs = set([i["adopt"] for i in self.disks]) + if len(all_lvs) != len(self.disks): + raise errors.OpPrereqError("Duplicate volume names given for adoption", + errors.ECODE_INVAL) + for lv_name in all_lvs: + try: + self.cfg.ReserveLV(lv_name, self.proc.GetECId()) + except errors.ReservationError: + raise errors.OpPrereqError("LV named %s used by another instance" % + lv_name, errors.ECODE_NOTUNIQUE) + + node_lvs = self.rpc.call_lv_list([pnode.name], + self.cfg.GetVGName())[pnode.name] + node_lvs.Raise("Cannot get LV information from node %s" % pnode.name) + node_lvs = node_lvs.payload + delta = all_lvs.difference(node_lvs.keys()) + if delta: + raise errors.OpPrereqError("Missing logical volume(s): %s" % + utils.CommaJoin(delta), + errors.ECODE_INVAL) + online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]] + if online_lvs: + raise errors.OpPrereqError("Online logical volumes found, cannot" + " adopt: %s" % utils.CommaJoin(online_lvs), + errors.ECODE_STATE) + # update the size of disk based on what is found + for dsk in self.disks: + dsk["size"] = int(float(node_lvs[dsk["adopt"]][0])) _CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams) - # os verification - result = self.rpc.call_os_get(pnode.name, self.op.os_type) - result.Raise("OS '%s' not in supported os list for primary node %s" % - (self.op.os_type, pnode.name), - prereq=True, ecode=errors.ECODE_INVAL) - if not self.op.force_variant: - _CheckOSVariant(result.payload, self.op.os_type) + _CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant) _CheckNicsBridgesExist(self, self.nics, self.pnode.name) @@ -6192,9 +6363,6 @@ class LUCreateInstance(LogicalUnit): else: network_port = None - ##if self.op.vnc_bind_address is None: - ## self.op.vnc_bind_address = constants.VNC_DEFAULT_BIND_ADDRESS - # this is needed because os.path.join does not accept None arguments if self.op.file_storage_dir is None: string_file_storage_dir = "" @@ -6205,7 +6373,6 @@ class LUCreateInstance(LogicalUnit): file_storage_dir = utils.PathJoin(self.cfg.GetFileStorageDir(), string_file_storage_dir, instance) - disks = _GenerateDiskTemplate(self, self.op.disk_template, instance, pnode_name, @@ -6226,16 +6393,29 @@ class LUCreateInstance(LogicalUnit): hypervisor=self.op.hypervisor, ) - feedback_fn("* creating instance disks...") - try: - _CreateDisks(self, iobj) - except errors.OpExecError: - self.LogWarning("Device creation failed, reverting...") + if self.adopt_disks: + # rename LVs to the newly-generated names; we need to construct + # 'fake' LV disks with the old data, plus the new unique_id + tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks] + rename_to = [] + for t_dsk, a_dsk in zip (tmp_disks, self.disks): + rename_to.append(t_dsk.logical_id) + t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk["adopt"]) + self.cfg.SetDiskID(t_dsk, pnode_name) + result = self.rpc.call_blockdev_rename(pnode_name, + zip(tmp_disks, rename_to)) + result.Raise("Failed to rename adoped LVs") + else: + feedback_fn("* creating instance disks...") try: - _RemoveDisks(self, iobj) - finally: - self.cfg.ReleaseDRBDMinors(instance) - raise + _CreateDisks(self, iobj) + except errors.OpExecError: + self.LogWarning("Device creation failed, reverting...") + try: + _RemoveDisks(self, iobj) + finally: + self.cfg.ReleaseDRBDMinors(instance) + raise feedback_fn("adding instance %s to cluster config" % instance) @@ -6273,17 +6453,15 @@ class LUCreateInstance(LogicalUnit): raise errors.OpExecError("There are some degraded disks for" " this instance") - feedback_fn("creating os for instance %s on node %s" % - (instance, pnode_name)) - - if iobj.disk_template != constants.DT_DISKLESS: + if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks: if self.op.mode == constants.INSTANCE_CREATE: - feedback_fn("* running the instance OS create scripts...") - # FIXME: pass debug option from opcode to backend - result = self.rpc.call_instance_os_add(pnode_name, iobj, False, - self.op.debug_level) - result.Raise("Could not add os for instance %s" - " on node %s" % (instance, pnode_name)) + if not self.op.no_install: + feedback_fn("* running the instance OS create scripts...") + # FIXME: pass debug option from opcode to backend + result = self.rpc.call_instance_os_add(pnode_name, iobj, False, + self.op.debug_level) + result.Raise("Could not add os for instance %s" + " on node %s" % (instance, pnode_name)) elif self.op.mode == constants.INSTANCE_IMPORT: feedback_fn("* running the instance OS import scripts...") @@ -7373,20 +7551,7 @@ class LUGrowDisk(LogicalUnit): self.disk = instance.FindDisk(self.op.disk) - nodeinfo = self.rpc.call_node_info(nodenames, self.cfg.GetVGName(), - instance.hypervisor) - for node in nodenames: - info = nodeinfo[node] - info.Raise("Cannot get current information from node %s" % node) - vg_free = info.payload.get('vg_free', None) - if not isinstance(vg_free, int): - raise errors.OpPrereqError("Can't compute free disk space on" - " node %s" % node, errors.ECODE_ENVIRON) - if self.op.amount > vg_free: - raise errors.OpPrereqError("Not enough disk space on target node %s:" - " %d MiB available, %d MiB required" % - (node, vg_free, self.op.amount), - errors.ECODE_NORES) + _CheckNodesFreeDisk(self, nodenames, self.op.amount) def Exec(self, feedback_fn): """Execute disk grow. @@ -7590,9 +7755,17 @@ class LUSetInstanceParams(LogicalUnit): self.op.beparams = {} if not hasattr(self.op, 'hvparams'): self.op.hvparams = {} + if not hasattr(self.op, "disk_template"): + self.op.disk_template = None + if not hasattr(self.op, "remote_node"): + self.op.remote_node = None + if not hasattr(self.op, "os_name"): + self.op.os_name = None + if not hasattr(self.op, "force_variant"): + self.op.force_variant = False self.op.force = getattr(self.op, "force", False) - if not (self.op.nics or self.op.disks or - self.op.hvparams or self.op.beparams): + if not (self.op.nics or self.op.disks or self.op.disk_template or + self.op.hvparams or self.op.beparams or self.op.os_name): raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL) if self.op.hvparams: @@ -7638,6 +7811,19 @@ class LUSetInstanceParams(LogicalUnit): raise errors.OpPrereqError("Only one disk add or remove operation" " supported at a time", errors.ECODE_INVAL) + if self.op.disks and self.op.disk_template is not None: + raise errors.OpPrereqError("Disk template conversion and other disk" + " changes not supported at the same time", + errors.ECODE_INVAL) + + if self.op.disk_template: + _CheckDiskTemplate(self.op.disk_template) + if (self.op.disk_template in constants.DTS_NET_MIRROR and + self.op.remote_node is None): + raise errors.OpPrereqError("Changing the disk template to a mirrored" + " one requires specifying a secondary node", + errors.ECODE_INVAL) + # NIC validation nic_addremove = 0 for nic_op, nic_dict in self.op.nics: @@ -7700,6 +7886,9 @@ class LUSetInstanceParams(LogicalUnit): def DeclareLocks(self, level): if level == locking.LEVEL_NODE: self._LockInstancesNodes() + if self.op.disk_template and self.op.remote_node: + self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node) + self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node) def BuildHooksEnv(self): """Build hooks env. @@ -7749,6 +7938,8 @@ class LUSetInstanceParams(LogicalUnit): del args['nics'][-1] env = _BuildInstanceHookEnvByObject(self, self.instance, override=args) + if self.op.disk_template: + env["NEW_DISK_TEMPLATE"] = self.op.disk_template nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes) return env, nl, nl @@ -7802,6 +7993,25 @@ class LUSetInstanceParams(LogicalUnit): pnode = instance.primary_node nodelist = list(instance.all_nodes) + if self.op.disk_template: + if instance.disk_template == self.op.disk_template: + raise errors.OpPrereqError("Instance already has disk template %s" % + instance.disk_template, errors.ECODE_INVAL) + + if (instance.disk_template, + self.op.disk_template) not in self._DISK_CONVERSIONS: + raise errors.OpPrereqError("Unsupported disk template conversion from" + " %s to %s" % (instance.disk_template, + self.op.disk_template), + errors.ECODE_INVAL) + if self.op.disk_template in constants.DTS_NET_MIRROR: + _CheckNodeOnline(self, self.op.remote_node) + _CheckNodeNotDrained(self, self.op.remote_node) + disks = [{"size": d.size} for d in instance.disks] + required = _ComputeDiskSize(self.op.disk_template, disks) + _CheckNodesFreeDisk(self, [self.op.remote_node], required) + _CheckInstanceDown(self, instance, "cannot change disk template") + # hvparams processing if self.op.hvparams: i_hvdict, hv_new = self._GetUpdatedParams( @@ -7967,17 +8177,8 @@ class LUSetInstanceParams(LogicalUnit): if disk_op == constants.DDM_REMOVE: if len(instance.disks) == 1: raise errors.OpPrereqError("Cannot remove the last disk of" - " an instance", - errors.ECODE_INVAL) - ins_l = self.rpc.call_instance_list([pnode], [instance.hypervisor]) - ins_l = ins_l[pnode] - msg = ins_l.fail_msg - if msg: - raise errors.OpPrereqError("Can't contact node %s: %s" % - (pnode, msg), errors.ECODE_ENVIRON) - if instance.name in ins_l.payload: - raise errors.OpPrereqError("Instance is running, can't remove" - " disks.", errors.ECODE_STATE) + " an instance", errors.ECODE_INVAL) + _CheckInstanceDown(self, instance, "cannot remove disks") if (disk_op == constants.DDM_ADD and len(instance.nics) >= constants.MAX_DISKS): @@ -7992,8 +8193,103 @@ class LUSetInstanceParams(LogicalUnit): (disk_op, len(instance.disks)), errors.ECODE_INVAL) + # OS change + if self.op.os_name and not self.op.force: + _CheckNodeHasOS(self, instance.primary_node, self.op.os_name, + self.op.force_variant) + return + def _ConvertPlainToDrbd(self, feedback_fn): + """Converts an instance from plain to drbd. + + """ + feedback_fn("Converting template to drbd") + instance = self.instance + pnode = instance.primary_node + snode = self.op.remote_node + + # create a fake disk info for _GenerateDiskTemplate + disk_info = [{"size": d.size, "mode": d.mode} for d in instance.disks] + new_disks = _GenerateDiskTemplate(self, self.op.disk_template, + instance.name, pnode, [snode], + disk_info, None, None, 0) + info = _GetInstanceInfoText(instance) + feedback_fn("Creating aditional volumes...") + # first, create the missing data and meta devices + for disk in new_disks: + # unfortunately this is... not too nice + _CreateSingleBlockDev(self, pnode, instance, disk.children[1], + info, True) + for child in disk.children: + _CreateSingleBlockDev(self, snode, instance, child, info, True) + # at this stage, all new LVs have been created, we can rename the + # old ones + feedback_fn("Renaming original volumes...") + rename_list = [(o, n.children[0].logical_id) + for (o, n) in zip(instance.disks, new_disks)] + result = self.rpc.call_blockdev_rename(pnode, rename_list) + result.Raise("Failed to rename original LVs") + + feedback_fn("Initializing DRBD devices...") + # all child devices are in place, we can now create the DRBD devices + for disk in new_disks: + for node in [pnode, snode]: + f_create = node == pnode + _CreateSingleBlockDev(self, node, instance, disk, info, f_create) + + # at this point, the instance has been modified + instance.disk_template = constants.DT_DRBD8 + instance.disks = new_disks + self.cfg.Update(instance, feedback_fn) + + # disks are created, waiting for sync + disk_abort = not _WaitForSync(self, instance) + if disk_abort: + raise errors.OpExecError("There are some degraded disks for" + " this instance, please cleanup manually") + + def _ConvertDrbdToPlain(self, feedback_fn): + """Converts an instance from drbd to plain. + + """ + instance = self.instance + assert len(instance.secondary_nodes) == 1 + pnode = instance.primary_node + snode = instance.secondary_nodes[0] + feedback_fn("Converting template to plain") + + old_disks = instance.disks + new_disks = [d.children[0] for d in old_disks] + + # copy over size and mode + for parent, child in zip(old_disks, new_disks): + child.size = parent.size + child.mode = parent.mode + + # update instance structure + instance.disks = new_disks + instance.disk_template = constants.DT_PLAIN + self.cfg.Update(instance, feedback_fn) + + feedback_fn("Removing volumes on the secondary node...") + for disk in old_disks: + self.cfg.SetDiskID(disk, snode) + msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg + if msg: + self.LogWarning("Could not remove block device %s on node %s," + " continuing anyway: %s", disk.iv_name, snode, msg) + + feedback_fn("Removing unneeded volumes on the primary node...") + for idx, disk in enumerate(old_disks): + meta = disk.children[1] + self.cfg.SetDiskID(meta, pnode) + msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg + if msg: + self.LogWarning("Could not remove metadata for disk %d on node %s," + " continuing anyway: %s", idx, pnode, msg) + + def Exec(self, feedback_fn): """Modifies an instance. @@ -8058,6 +8354,20 @@ class LUSetInstanceParams(LogicalUnit): # change a given disk instance.disks[disk_op].mode = disk_dict['mode'] result.append(("disk.mode/%d" % disk_op, disk_dict['mode'])) + + if self.op.disk_template: + r_shut = _ShutdownInstanceDisks(self, instance) + if not r_shut: + raise errors.OpExecError("Cannot shutdow instance disks, unable to" + " proceed with disk template conversion") + mode = (instance.disk_template, self.op.disk_template) + try: + self._DISK_CONVERSIONS[mode](self, feedback_fn) + except: + self.cfg.ReleaseDRBDMinors(instance.name) + raise + result.append(("disk_template", self.op.disk_template)) + # NIC changes for nic_op, nic_dict in self.op.nics: if nic_op == constants.DDM_REMOVE: @@ -8098,10 +8408,18 @@ class LUSetInstanceParams(LogicalUnit): for key, val in self.op.beparams.iteritems(): result.append(("be/%s" % key, val)) + # OS change + if self.op.os_name: + instance.os = self.op.os_name + self.cfg.Update(instance, feedback_fn) return result + _DISK_CONVERSIONS = { + (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd, + (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain, + } class LUQueryExports(NoHooksLU): """Query the exports list @@ -8158,11 +8476,22 @@ class LUExportInstance(LogicalUnit): """Check the arguments. """ + _CheckBooleanOpField(self.op, "remove_instance") + _CheckBooleanOpField(self.op, "ignore_remove_failures") + self.shutdown_timeout = getattr(self.op, "shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT) + self.remove_instance = getattr(self.op, "remove_instance", False) + self.ignore_remove_failures = getattr(self.op, "ignore_remove_failures", + False) + + if self.remove_instance and not self.op.shutdown: + raise errors.OpPrereqError("Can not remove instance without shutting it" + " down before") def ExpandNames(self): self._ExpandAndLockInstance() + # FIXME: lock only instance primary and destination node # # Sad but true, for now we have do lock all nodes, as we don't know where @@ -8187,6 +8516,8 @@ class LUExportInstance(LogicalUnit): "EXPORT_NODE": self.op.target_node, "EXPORT_DO_SHUTDOWN": self.op.shutdown, "SHUTDOWN_TIMEOUT": self.shutdown_timeout, + # TODO: Generic function for boolean env variables + "REMOVE_INSTANCE": str(bool(self.remove_instance)), } env.update(_BuildInstanceHookEnvByObject(self, self.instance)) nl = [self.cfg.GetMasterNode(), self.instance.primary_node, @@ -8213,6 +8544,7 @@ class LUExportInstance(LogicalUnit): _CheckNodeNotDrained(self, self.dst_node.name) # instance disk type verification + # TODO: Implement export support for file-based disks for disk in self.instance.disks: if disk.dev_type == constants.LD_FILE: raise errors.OpPrereqError("Export not supported for instances with" @@ -8231,6 +8563,7 @@ class LUExportInstance(LogicalUnit): feedback_fn("Shutting down instance %s" % instance.name) result = self.rpc.call_instance_shutdown(src_node, instance, self.shutdown_timeout) + # TODO: Maybe ignore failures if ignore_remove_failures is set result.Raise("Could not shutdown instance %s on" " node %s" % (instance.name, src_node)) @@ -8274,7 +8607,7 @@ class LUExportInstance(LogicalUnit): snap_disks.append(new_dev) finally: - if self.op.shutdown and instance.admin_up: + if self.op.shutdown and instance.admin_up and not self.remove_instance: feedback_fn("Starting instance %s" % instance.name) result = self.rpc.call_instance_start(src_node, instance, None, None) msg = result.fail_msg @@ -8322,6 +8655,11 @@ class LUExportInstance(LogicalUnit): feedback_fn("Deactivating disks for %s" % instance.name) _ShutdownInstanceDisks(self, instance) + # Remove instance if requested + if self.remove_instance: + feedback_fn("Removing instance %s" % instance.name) + _RemoveInstance(self, feedback_fn, instance, self.ignore_remove_failures) + nodelist = self.cfg.GetNodeList() nodelist.remove(dst_node.name) @@ -8340,6 +8678,7 @@ class LUExportInstance(LogicalUnit): if msg: self.LogWarning("Could not remove older export for instance %s" " on node %s: %s", iname, node, msg) + return fin_resu, dresults