X-Git-Url: https://code.grnet.gr/git/ganeti-local/blobdiff_plain/75f044c53753c0dab22ae97879eaf9d4d646c4e6..96d7b4280cf8e0bc9f6b5547fd30bac46b0049f6:/lib/cmdlib.py diff --git a/lib/cmdlib.py b/lib/cmdlib.py index a625dcd..11c98bc 100644 --- a/lib/cmdlib.py +++ b/lib/cmdlib.py @@ -267,11 +267,12 @@ def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status, if nics: nic_count = len(nics) - for idx, (ip, bridge) in enumerate(nics): + for idx, (ip, bridge, mac) in enumerate(nics): if ip is None: ip = "" env["INSTANCE_NIC%d_IP" % idx] = ip env["INSTANCE_NIC%d_BRIDGE" % idx] = bridge + env["INSTANCE_NIC%d_HWADDR" % idx] = mac else: nic_count = 0 @@ -295,7 +296,7 @@ def _BuildInstanceHookEnvByObject(instance, override=None): 'status': instance.os, 'memory': instance.memory, 'vcpus': instance.vcpus, - 'nics': [(nic.ip, nic.bridge) for nic in instance.nics], + 'nics': [(nic.ip, nic.bridge, nic.mac) for nic in instance.nics], } if override: args.update(override) @@ -946,7 +947,7 @@ class LUVerifyDisks(NoHooksLU): inst = nv_dict.pop((node, lv_name), None) if (not lv_online and inst is not None and inst.name not in res_instances): - res_instances.append(inst.name) + res_instances.append(inst.name) # any leftover items in nv_dict are missing LVs, let's arrange the # data better @@ -1859,23 +1860,41 @@ def _AssembleInstanceDisks(instance, cfg, ignore_secondaries=False): """ device_info = [] disks_ok = True + iname = instance.name + # With the two passes mechanism we try to reduce the window of + # opportunity for the race condition of switching DRBD to primary + # before handshaking occured, but we do not eliminate it + + # The proper fix would be to wait (with some limits) until the + # connection has been made and drbd transitions from WFConnection + # into any other network-connected state (Connected, SyncTarget, + # SyncSource, etc.) + + # 1st pass, assemble on all nodes in secondary mode for inst_disk in instance.disks: - master_result = None for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node): cfg.SetDiskID(node_disk, node) - is_primary = node == instance.primary_node - result = rpc.call_blockdev_assemble(node, node_disk, - instance.name, is_primary) + result = rpc.call_blockdev_assemble(node, node_disk, iname, False) if not result: logger.Error("could not prepare block device %s on node %s" - " (is_primary=%s)" % - (inst_disk.iv_name, node, is_primary)) - if is_primary or not ignore_secondaries: + " (is_primary=False, pass=1)" % (inst_disk.iv_name, node)) + if not ignore_secondaries: disks_ok = False - if is_primary: - master_result = result - device_info.append((instance.primary_node, inst_disk.iv_name, - master_result)) + + # FIXME: race condition on drbd migration to primary + + # 2nd pass, do only the primary node + for inst_disk in instance.disks: + for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node): + if node != instance.primary_node: + continue + cfg.SetDiskID(node_disk, node) + result = rpc.call_blockdev_assemble(node, node_disk, iname, True) + if not result: + logger.Error("could not prepare block device %s on node %s" + " (is_primary=True, pass=2)" % (inst_disk.iv_name, node)) + disks_ok = False + device_info.append((instance.primary_node, inst_disk.iv_name, result)) # leave the disks configured for the primary node # this is a workaround that would be fixed better by @@ -1958,6 +1977,36 @@ def _ShutdownInstanceDisks(instance, cfg, ignore_primary=False): return result +def _CheckNodeFreeMemory(cfg, node, reason, requested): + """Checks if a node has enough free memory. + + This function check if a given node has the needed amount of free + memory. In case the node has less memory or we cannot get the + information from the node, this function raise an OpPrereqError + exception. + + Args: + - cfg: a ConfigWriter instance + - node: the node name + - reason: string to use in the error message + - requested: the amount of memory in MiB + + """ + nodeinfo = rpc.call_node_info([node], cfg.GetVGName()) + if not nodeinfo or not isinstance(nodeinfo, dict): + raise errors.OpPrereqError("Could not contact node %s for resource" + " information" % (node,)) + + free_mem = nodeinfo[node].get('memory_free') + if not isinstance(free_mem, int): + raise errors.OpPrereqError("Can't compute free memory on node %s, result" + " was '%s'" % (node, free_mem)) + if requested > free_mem: + raise errors.OpPrereqError("Not enough memory on node %s for %s:" + " needed %s MiB, available %s MiB" % + (node, reason, requested, free_mem)) + + class LUStartupInstance(LogicalUnit): """Starts an instance. @@ -1995,6 +2044,10 @@ class LUStartupInstance(LogicalUnit): # check bridges existance _CheckInstanceBridgesExist(instance) + _CheckNodeFreeMemory(self.cfg, instance.primary_node, + "starting instance %s" % instance.name, + instance.memory) + self.instance = instance self.op.instance_name = instance.name @@ -2008,20 +2061,6 @@ class LUStartupInstance(LogicalUnit): node_current = instance.primary_node - nodeinfo = rpc.call_node_info([node_current], self.cfg.GetVGName()) - if not nodeinfo: - raise errors.OpExecError("Could not contact node %s for infos" % - (node_current)) - - freememory = nodeinfo[node_current]['memory_free'] - memory = instance.memory - if memory > freememory: - raise errors.OpExecError("Not enough memory to start instance" - " %s on node %s" - " needed %s MiB, available %s MiB" % - (instance.name, node_current, memory, - freememory)) - _StartInstanceDisks(self.cfg, instance, force) if not rpc.call_instance_start(node_current, instance, extra_args): @@ -2379,11 +2418,11 @@ class LUQueryInstances(NoHooksLU): This checks that the fields required are valid output fields. """ - self.dynamic_fields = frozenset(["oper_state", "oper_ram"]) + self.dynamic_fields = frozenset(["oper_state", "oper_ram", "status"]) _CheckOutputFields(static=["name", "os", "pnode", "snodes", "admin_state", "admin_ram", "disk_template", "ip", "mac", "bridge", - "sda_size", "sdb_size"], + "sda_size", "sdb_size", "vcpus"], dynamic=self.dynamic_fields, selected=self.op.output_fields) @@ -2436,6 +2475,21 @@ class LUQueryInstances(NoHooksLU): val = None else: val = bool(live_data.get(instance.name)) + elif field == "status": + if instance.primary_node in bad_nodes: + val = "ERROR_nodedown" + else: + running = bool(live_data.get(instance.name)) + if running: + if instance.status != "down": + val = "running" + else: + val = "ERROR_up" + else: + if instance.status != "down": + val = "ERROR_down" + else: + val = "ADMIN_down" elif field == "admin_ram": val = instance.memory elif field == "oper_ram": @@ -2459,6 +2513,8 @@ class LUQueryInstances(NoHooksLU): val = None else: val = disk.size + elif field == "vcpus": + val = instance.vcpus else: raise errors.ParameterError(field) iout.append(val) @@ -2509,18 +2565,10 @@ class LUFailoverInstance(LogicalUnit): raise errors.ProgrammerError("no secondary node but using " "DT_REMOTE_RAID1 template") - # check memory requirements on the secondary node target_node = secondary_nodes[0] - nodeinfo = rpc.call_node_info([target_node], self.cfg.GetVGName()) - info = nodeinfo.get(target_node, None) - if not info: - raise errors.OpPrereqError("Cannot get current information" - " from node '%s'" % nodeinfo) - if instance.memory > info['memory_free']: - raise errors.OpPrereqError("Not enough memory on target node %s." - " %d MB available, %d MB required" % - (target_node, info['memory_free'], - instance.memory)) + # check memory requirements on the secondary node + _CheckNodeFreeMemory(self.cfg, target_node, "failing over instance %s" % + instance.name, instance.memory) # check bridge existance brlist = [nic.bridge for nic in instance.nics] @@ -2551,21 +2599,6 @@ class LUFailoverInstance(LogicalUnit): raise errors.OpExecError("Disk %s is degraded on target node," " aborting failover." % dev.iv_name) - feedback_fn("* checking target node resource availability") - nodeinfo = rpc.call_node_info([target_node], self.cfg.GetVGName()) - - if not nodeinfo: - raise errors.OpExecError("Could not contact target node %s." % - target_node) - - free_memory = int(nodeinfo[target_node]['memory_free']) - memory = instance.memory - if memory > free_memory: - raise errors.OpExecError("Not enough memory to create instance %s on" - " node %s. needed %s MiB, available %s MiB" % - (instance.name, target_node, memory, - free_memory)) - feedback_fn("* shutting down instance on source node") logger.Info("Shutting down instance %s on node %s" % (instance.name, source_node)) @@ -2878,7 +2911,7 @@ class LUCreateInstance(LogicalUnit): os_type=self.op.os_type, memory=self.op.mem_size, vcpus=self.op.vcpus, - nics=[(self.inst_ip, self.op.bridge)], + nics=[(self.inst_ip, self.op.bridge, self.op.mac)], )) nl = ([self.sstore.GetMasterNode(), self.op.pnode] + @@ -2890,6 +2923,10 @@ class LUCreateInstance(LogicalUnit): """Check prerequisites. """ + for attr in ["kernel_path", "initrd_path", "hvm_boot_order"]: + if not hasattr(self.op, attr): + setattr(self.op, attr, None) + if self.op.mode not in (constants.INSTANCE_CREATE, constants.INSTANCE_IMPORT): raise errors.OpPrereqError("Invalid instance creation mode '%s'" % @@ -2961,13 +2998,9 @@ class LUCreateInstance(LogicalUnit): " the primary node.") self.secondaries.append(snode_name) - # Check lv size requirements - nodenames = [pnode.name] + self.secondaries - nodeinfo = rpc.call_node_info(nodenames, self.cfg.GetVGName()) - # Required free disk space as a function of disk and swap space req_size_dict = { - constants.DT_DISKLESS: 0, + constants.DT_DISKLESS: None, constants.DT_PLAIN: self.op.disk_size + self.op.swap_size, constants.DT_LOCAL_RAID1: (self.op.disk_size + self.op.swap_size) * 2, # 256 MB are added for drbd metadata, 128MB for each drbd device @@ -2981,15 +3014,23 @@ class LUCreateInstance(LogicalUnit): req_size = req_size_dict[self.op.disk_template] - for node in nodenames: - info = nodeinfo.get(node, None) - if not info: - raise errors.OpPrereqError("Cannot get current information" - " from node '%s'" % nodeinfo) - if req_size > info['vg_free']: - raise errors.OpPrereqError("Not enough disk space on target node %s." - " %d MB available, %d MB required" % - (node, info['vg_free'], req_size)) + # Check lv size requirements + if req_size is not None: + nodenames = [pnode.name] + self.secondaries + nodeinfo = rpc.call_node_info(nodenames, self.cfg.GetVGName()) + for node in nodenames: + info = nodeinfo.get(node, None) + if not info: + raise errors.OpPrereqError("Cannot get current information" + " from node '%s'" % nodeinfo) + 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) + if req_size > info['vg_free']: + raise errors.OpPrereqError("Not enough disk space on target node %s." + " %d MB available, %d MB required" % + (node, info['vg_free'], req_size)) # os verification os_obj = rpc.call_os_get(pnode.name, self.op.os_type) @@ -3049,6 +3090,12 @@ class LUCreateInstance(LogicalUnit): " destination node '%s'" % (self.op.bridge, pnode.name)) + # boot order verification + if self.op.hvm_boot_order is not None: + if len(self.op.hvm_boot_order.strip("acdn")) != 0: + raise errors.OpPrereqError("invalid boot order specified," + " must be one or more of [acdn]") + if self.op.start: self.instance_status = 'up' else: @@ -3062,9 +3109,9 @@ class LUCreateInstance(LogicalUnit): pnode_name = self.pnode.name if self.op.mac == "auto": - mac_address=self.cfg.GenerateMAC() + mac_address = self.cfg.GenerateMAC() else: - mac_address=self.op.mac + mac_address = self.op.mac nic = objects.NIC(bridge=self.op.bridge, mac=mac_address) if self.inst_ip is not None: @@ -3092,6 +3139,7 @@ class LUCreateInstance(LogicalUnit): network_port=network_port, kernel_path=self.op.kernel_path, initrd_path=self.op.initrd_path, + hvm_boot_order=self.op.hvm_boot_order, ) feedback_fn("* creating instance disks...") @@ -3980,7 +4028,7 @@ class LUQueryInstanceData(NoHooksLU): instance = self.cfg.GetInstanceInfo(self.cfg.ExpandInstanceName(name)) if instance is None: raise errors.OpPrereqError("No such instance name '%s'" % name) - self.wanted_instances.append(instance) + self.wanted_instances.append(instance) else: self.wanted_instances = [self.cfg.GetInstanceInfo(name) for name in self.cfg.GetInstanceList()] @@ -4056,6 +4104,7 @@ class LUQueryInstanceData(NoHooksLU): "vcpus": instance.vcpus, "kernel_path": instance.kernel_path, "initrd_path": instance.initrd_path, + "hvm_boot_order": instance.hvm_boot_order, } result[instance.name] = idict @@ -4082,7 +4131,7 @@ class LUSetInstanceParms(LogicalUnit): args['memory'] = self.mem if self.vcpus: args['vcpus'] = self.vcpus - if self.do_ip or self.do_bridge: + if self.do_ip or self.do_bridge or self.mac: if self.do_ip: ip = self.ip else: @@ -4091,7 +4140,11 @@ class LUSetInstanceParms(LogicalUnit): bridge = self.bridge else: bridge = self.instance.nics[0].bridge - args['nics'] = [(ip, bridge)] + if self.mac: + mac = self.mac + else: + mac = self.instance.nics[0].mac + args['nics'] = [(ip, bridge, mac)] env = _BuildInstanceHookEnvByObject(self.instance, override=args) nl = [self.sstore.GetMasterNode(), self.instance.primary_node] + list(self.instance.secondary_nodes) @@ -4110,8 +4163,9 @@ class LUSetInstanceParms(LogicalUnit): self.bridge = getattr(self.op, "bridge", None) self.kernel_path = getattr(self.op, "kernel_path", None) self.initrd_path = getattr(self.op, "initrd_path", None) + self.hvm_boot_order = getattr(self.op, "hvm_boot_order", None) all_parms = [self.mem, self.vcpus, self.ip, self.bridge, self.mac, - self.kernel_path, self.initrd_path] + self.kernel_path, self.initrd_path, self.hvm_boot_order] if all_parms.count(None) == len(all_parms): raise errors.OpPrereqError("No changes submitted") if self.mem is not None: @@ -4148,7 +4202,7 @@ class LUSetInstanceParms(LogicalUnit): if self.kernel_path != constants.VALUE_DEFAULT: if not os.path.isabs(self.kernel_path): - raise errors.OpPrereError("The kernel path must be an absolute" + raise errors.OpPrereqError("The kernel path must be an absolute" " filename") else: self.do_kernel_path = False @@ -4157,12 +4211,20 @@ class LUSetInstanceParms(LogicalUnit): self.do_initrd_path = True if self.initrd_path not in (constants.VALUE_NONE, constants.VALUE_DEFAULT): - if not os.path.isabs(self.kernel_path): - raise errors.OpPrereError("The initrd path must be an absolute" + if not os.path.isabs(self.initrd_path): + raise errors.OpPrereqError("The initrd path must be an absolute" " filename") else: self.do_initrd_path = False + # boot order verification + if self.hvm_boot_order is not None: + if self.hvm_boot_order != constants.VALUE_DEFAULT: + if len(self.hvm_boot_order.strip("acdn")) != 0: + raise errors.OpPrereqError("invalid boot order specified," + " must be one or more of [acdn]" + " or 'default'") + instance = self.cfg.GetInstanceInfo( self.cfg.ExpandInstanceName(self.op.instance_name)) if instance is None: @@ -4200,6 +4262,12 @@ class LUSetInstanceParms(LogicalUnit): if self.do_initrd_path: instance.initrd_path = self.initrd_path result.append(("initrd_path", self.initrd_path)) + if self.hvm_boot_order: + if self.hvm_boot_order == constants.VALUE_DEFAULT: + instance.hvm_boot_order = None + else: + instance.hvm_boot_order = self.hvm_boot_order + result.append(("hvm_boot_order", self.hvm_boot_order)) self.cfg.AddInstance(instance) @@ -4493,3 +4561,39 @@ class LUDelTags(TagsLU): raise errors.OpRetryError("There has been a modification to the" " config file and the operation has been" " aborted. Please retry.") + +class LUTestDelay(NoHooksLU): + """Sleep for a specified amount of time. + + This LU sleeps on the master and/or nodes for a specified amoutn of + time. + + """ + _OP_REQP = ["duration", "on_master", "on_nodes"] + + def CheckPrereq(self): + """Check prerequisites. + + This checks that we have a good list of nodes and/or the duration + is valid. + + """ + + if self.op.on_nodes: + self.op.on_nodes = _GetWantedNodes(self, self.op.on_nodes) + + def Exec(self, feedback_fn): + """Do the actual sleep. + + """ + if self.op.on_master: + if not utils.TestDelay(self.op.duration): + raise errors.OpExecError("Error during master delay test") + if self.op.on_nodes: + result = rpc.call_test_delay(self.op.on_nodes, self.op.duration) + if not result: + raise errors.OpExecError("Complete failure from rpc call") + for node, node_result in result.items(): + if not node_result: + raise errors.OpExecError("Failure during rpc call to node %s," + " result: %s" % (node, node_result))