1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """Module implementing the iallocator code."""
23
24 from ganeti import compat
25 from ganeti import constants
26 from ganeti import errors
27 from ganeti import ht
28 from ganeti import outils
29 from ganeti import opcodes
30 from ganeti import rpc
31 from ganeti import serializer
32 from ganeti import utils
33
34 import ganeti.masterd.instance as gmi
35
36
37 _STRING_LIST = ht.TListOf(ht.TString)
38 _JOB_LIST = ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, {
39
40
41 "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID,
42 opcodes.OpInstanceMigrate.OP_ID,
43 opcodes.OpInstanceReplaceDisks.OP_ID]),
44 })))
45
46 _NEVAC_MOVED = \
47 ht.TListOf(ht.TAnd(ht.TIsLength(3),
48 ht.TItems([ht.TNonEmptyString,
49 ht.TNonEmptyString,
50 ht.TListOf(ht.TNonEmptyString),
51 ])))
52 _NEVAC_FAILED = \
53 ht.TListOf(ht.TAnd(ht.TIsLength(2),
54 ht.TItems([ht.TNonEmptyString,
55 ht.TMaybeString,
56 ])))
57 _NEVAC_RESULT = ht.TAnd(ht.TIsLength(3),
58 ht.TItems([_NEVAC_MOVED, _NEVAC_FAILED, _JOB_LIST]))
59
60 _INST_NAME = ("name", ht.TNonEmptyString)
61 _INST_UUID = ("inst_uuid", ht.TNonEmptyString)
65 """Meta class for request definitions.
66
67 """
68 @classmethod
70 """Extract the slots out of REQ_PARAMS.
71
72 """
73 params = attrs.setdefault("REQ_PARAMS", [])
74 return [slot for (slot, _) in params]
75
78 """A generic IAllocator request object.
79
80 """
81 __metaclass__ = _AutoReqParam
82
83 MODE = NotImplemented
84 REQ_PARAMS = []
85 REQ_RESULT = NotImplemented
86
88 """Constructor for IARequestBase.
89
90 The constructor takes only keyword arguments and will set
91 attributes on this object based on the passed arguments. As such,
92 it means that you should not pass arguments which are not in the
93 REQ_PARAMS attribute for this class.
94
95 """
96 outils.ValidatedSlots.__init__(self, **kwargs)
97
98 self.Validate()
99
117
119 """Gets the request data dict.
120
121 @param cfg: The configuration instance
122
123 """
124 raise NotImplementedError
125
127 """Validates the result of an request.
128
129 @param ia: The IAllocator instance
130 @param result: The IAllocator run result
131 @raises ResultValidationError: If validation fails
132
133 """
134 if ia.success and not self.REQ_RESULT(result):
135 raise errors.ResultValidationError("iallocator returned invalid result,"
136 " expected %s, got %s" %
137 (self.REQ_RESULT, result))
138
141 """An instance allocation request.
142
143 """
144
145 MODE = constants.IALLOCATOR_MODE_ALLOC
146 REQ_PARAMS = [
147 _INST_NAME,
148 ("memory", ht.TNonNegativeInt),
149 ("spindle_use", ht.TNonNegativeInt),
150 ("disks", ht.TListOf(ht.TDict)),
151 ("disk_template", ht.TString),
152 ("os", ht.TString),
153 ("tags", _STRING_LIST),
154 ("nics", ht.TListOf(ht.TDict)),
155 ("vcpus", ht.TInt),
156 ("hypervisor", ht.TString),
157 ("node_whitelist", ht.TMaybeListOf(ht.TNonEmptyString)),
158 ]
159 REQ_RESULT = ht.TList
160
162 """Calculates the required nodes based on the disk_template.
163
164 """
165 if self.disk_template in constants.DTS_INT_MIRROR:
166 return 2
167 else:
168 return 1
169
171 """Requests a new instance.
172
173 The checks for the completeness of the opcode must have already been
174 done.
175
176 """
177 disk_space = gmi.ComputeDiskSize(self.disk_template, self.disks)
178
179 return {
180 "name": self.name,
181 "disk_template": self.disk_template,
182 "tags": self.tags,
183 "os": self.os,
184 "vcpus": self.vcpus,
185 "memory": self.memory,
186 "spindle_use": self.spindle_use,
187 "disks": self.disks,
188 "disk_space_total": disk_space,
189 "nics": self.nics,
190 "required_nodes": self.RequiredNodes(),
191 "hypervisor": self.hypervisor,
192 }
193
204
228
231 """A relocation request.
232
233 """
234
235 MODE = constants.IALLOCATOR_MODE_RELOC
236 REQ_PARAMS = [
237 _INST_UUID,
238 ("relocate_from_node_uuids", _STRING_LIST),
239 ]
240 REQ_RESULT = ht.TList
241
272
296
297 @staticmethod
299 """Returns a list of unique group names for a list of nodes.
300
301 @type node2group: dict
302 @param node2group: Map from node name to group UUID
303 @type groups: dict
304 @param groups: Group information
305 @type nodes: list
306 @param nodes: Node names
307
308 """
309 result = set()
310
311 for node in nodes:
312 try:
313 group_uuid = node2group[node]
314 except KeyError:
315
316 pass
317 else:
318 try:
319 group = groups[group_uuid]
320 except KeyError:
321
322 group_name = group_uuid
323 else:
324 group_name = group["name"]
325
326 result.add(group_name)
327
328 return sorted(result)
329
351
373
376 """IAllocator framework.
377
378 An IAllocator instance has three sets of attributes:
379 - cfg that is needed to query the cluster
380 - input data (all members of the _KEYS class attribute are required)
381 - four buffer attributes (in|out_data|text), that represent the
382 input (to the external script) in text and data structure format,
383 and the output from it, again in two formats
384 - the result variables from the script (success, info, nodes) for
385 easy usage
386
387 """
388
389
390
391 - def __init__(self, cfg, rpc_runner, req):
392 self.cfg = cfg
393 self.rpc = rpc_runner
394 self.req = req
395
396 self.in_text = self.out_text = self.in_data = self.out_data = None
397
398 self.success = self.info = self.result = None
399
400 self._BuildInputData(req)
401
404 """Prepare and execute node info call.
405
406 @type node_list: list of strings
407 @param node_list: list of nodes' UUIDs
408 @type cluster_info: L{objects.Cluster}
409 @param cluster_info: the cluster's information from the config
410 @type hypervisor_name: string
411 @param hypervisor_name: the hypervisor name
412 @rtype: same as the result of the node info RPC call
413 @return: the result of the node info RPC call
414
415 """
416 storage_units_raw = utils.storage.GetStorageUnitsOfCluster(
417 self.cfg, include_spindles=True)
418 storage_units = rpc.PrepareStorageUnitsForNodes(self.cfg, storage_units_raw,
419 node_list)
420 hvspecs = [(hypervisor_name, cluster_info.hvparams[hypervisor_name])]
421 return self.rpc.call_node_info(node_list, storage_units, hvspecs)
422
424 """Compute the generic allocator input data.
425
426 This is the data that is independent of the actual operation.
427
428 """
429 cluster_info = self.cfg.GetClusterInfo()
430
431 data = {
432 "version": constants.IALLOCATOR_VERSION,
433 "cluster_name": self.cfg.GetClusterName(),
434 "cluster_tags": list(cluster_info.GetTags()),
435 "enabled_hypervisors": list(cluster_info.enabled_hypervisors),
436 "ipolicy": cluster_info.ipolicy,
437 }
438 ninfo = self.cfg.GetAllNodesInfo()
439 iinfo = self.cfg.GetAllInstancesInfo().values()
440 i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo]
441
442
443 node_list = [n.uuid for n in ninfo.values() if n.vm_capable]
444
445 if isinstance(self.req, IAReqInstanceAlloc):
446 hypervisor_name = self.req.hypervisor
447 node_whitelist = self.req.node_whitelist
448 elif isinstance(self.req, IAReqRelocate):
449 hypervisor_name = self.cfg.GetInstanceInfo(self.req.inst_uuid).hypervisor
450 node_whitelist = None
451 else:
452 hypervisor_name = cluster_info.primary_hypervisor
453 node_whitelist = None
454
455 has_lvm = utils.storage.IsLvmEnabled(cluster_info.enabled_disk_templates)
456 node_data = self._ComputeClusterDataNodeInfo(node_list, cluster_info,
457 hypervisor_name)
458
459 node_iinfo = \
460 self.rpc.call_all_instances_info(node_list,
461 cluster_info.enabled_hypervisors,
462 cluster_info.hvparams)
463
464 data["nodegroups"] = self._ComputeNodeGroupData(self.cfg)
465
466 config_ndata = self._ComputeBasicNodeData(self.cfg, ninfo, node_whitelist)
467 data["nodes"] = self._ComputeDynamicNodeData(ninfo, node_data, node_iinfo,
468 i_list, config_ndata, has_lvm)
469 assert len(data["nodes"]) == len(ninfo), \
470 "Incomplete node data computed"
471
472 data["instances"] = self._ComputeInstanceData(self.cfg, cluster_info,
473 i_list)
474
475 self.in_data = data
476
477 @staticmethod
479 """Compute node groups data.
480
481 """
482 cluster = cfg.GetClusterInfo()
483 ng = dict((guuid, {
484 "name": gdata.name,
485 "alloc_policy": gdata.alloc_policy,
486 "networks": [net_uuid for net_uuid, _ in gdata.networks.items()],
487 "ipolicy": gmi.CalculateGroupIPolicy(cluster, gdata),
488 "tags": list(gdata.GetTags()),
489 })
490 for guuid, gdata in cfg.GetAllNodeGroupsInfo().items())
491
492 return ng
493
494 @staticmethod
496 """Compute global node data.
497
498 @rtype: dict
499 @returns: a dict of name: (node dict, node config)
500
501 """
502
503 node_results = dict((ninfo.name, {
504 "tags": list(ninfo.GetTags()),
505 "primary_ip": ninfo.primary_ip,
506 "secondary_ip": ninfo.secondary_ip,
507 "offline": (ninfo.offline or
508 not (node_whitelist is None or
509 ninfo.name in node_whitelist)),
510 "drained": ninfo.drained,
511 "master_candidate": ninfo.master_candidate,
512 "group": ninfo.group,
513 "master_capable": ninfo.master_capable,
514 "vm_capable": ninfo.vm_capable,
515 "ndparams": cfg.GetNdParams(ninfo),
516 })
517 for ninfo in node_cfg.values())
518
519 return node_results
520
521 @staticmethod
523 """Extract an attribute from the hypervisor's node information.
524
525 This is a helper function to extract data from the hypervisor's information
526 about the node, as part of the result of a node_info query.
527
528 @type hv_info: dict of strings
529 @param hv_info: dictionary of node information from the hypervisor
530 @type node_name: string
531 @param node_name: name of the node
532 @type attr: string
533 @param attr: key of the attribute in the hv_info dictionary
534 @rtype: integer
535 @return: the value of the attribute
536 @raises errors.OpExecError: if key not in dictionary or value not
537 integer
538
539 """
540 if attr not in hv_info:
541 raise errors.OpExecError("Node '%s' didn't return attribute"
542 " '%s'" % (node_name, attr))
543 value = hv_info[attr]
544 if not isinstance(value, int):
545 raise errors.OpExecError("Node '%s' returned invalid value"
546 " for '%s': %s" %
547 (node_name, attr, value))
548 return value
549
550 @staticmethod
552 """Extract storage data from node info.
553
554 @type space_info: see result of the RPC call node info
555 @param space_info: the storage reporting part of the result of the RPC call
556 node info
557 @type node_name: string
558 @param node_name: the node's name
559 @type has_lvm: boolean
560 @param has_lvm: whether or not LVM storage information is requested
561 @rtype: 4-tuple of integers
562 @return: tuple of storage info (total_disk, free_disk, total_spindles,
563 free_spindles)
564
565 """
566
567 if has_lvm:
568 lvm_vg_info = utils.storage.LookupSpaceInfoByStorageType(
569 space_info, constants.ST_LVM_VG)
570 if not lvm_vg_info:
571 raise errors.OpExecError("Node '%s' didn't return LVM vg space info."
572 % (node_name))
573 total_disk = lvm_vg_info["storage_size"]
574 free_disk = lvm_vg_info["storage_free"]
575 lvm_pv_info = utils.storage.LookupSpaceInfoByStorageType(
576 space_info, constants.ST_LVM_PV)
577 if not lvm_vg_info:
578 raise errors.OpExecError("Node '%s' didn't return LVM pv space info."
579 % (node_name))
580 total_spindles = lvm_pv_info["storage_size"]
581 free_spindles = lvm_pv_info["storage_free"]
582 else:
583
584 total_disk = free_disk = 0
585 total_spindles = free_spindles = 0
586 return (total_disk, free_disk, total_spindles, free_spindles)
587
588 @staticmethod
591 """Compute memory used by primary instances.
592
593 @rtype: tuple (int, int, int)
594 @returns: A tuple of three integers: 1. the sum of memory used by primary
595 instances on the node (including the ones that are currently down), 2.
596 the sum of memory used by primary instances of the node that are up, 3.
597 the amount of memory that is free on the node considering the current
598 usage of the instances.
599
600 """
601 i_p_mem = i_p_up_mem = 0
602 mem_free = input_mem_free
603 for iinfo, beinfo in instance_list:
604 if iinfo.primary_node == node_uuid:
605 i_p_mem += beinfo[constants.BE_MAXMEM]
606 if iinfo.name not in node_instances_info[node_uuid].payload:
607 i_used_mem = 0
608 else:
609 i_used_mem = int(node_instances_info[node_uuid]
610 .payload[iinfo.name]["memory"])
611 i_mem_diff = beinfo[constants.BE_MAXMEM] - i_used_mem
612 mem_free -= max(0, i_mem_diff)
613
614 if iinfo.admin_state == constants.ADMINST_UP:
615 i_p_up_mem += beinfo[constants.BE_MAXMEM]
616 return (i_p_mem, i_p_up_mem, mem_free)
617
620 """Compute global node data.
621
622 @param node_results: the basic node structures as filled from the config
623
624 """
625
626
627 node_results = dict(node_results)
628 for nuuid, nresult in node_data.items():
629 ninfo = node_cfg[nuuid]
630 assert ninfo.name in node_results, "Missing basic data for node %s" % \
631 ninfo.name
632
633 if not ninfo.offline:
634 nresult.Raise("Can't get data for node %s" % ninfo.name)
635 node_iinfo[nuuid].Raise("Can't get node instance info from node %s" %
636 ninfo.name)
637 (_, space_info, (hv_info, )) = nresult.payload
638
639 mem_free = self._GetAttributeFromHypervisorNodeData(hv_info, ninfo.name,
640 "memory_free")
641
642 (i_p_mem, i_p_up_mem, mem_free) = self._ComputeInstanceMemory(
643 i_list, node_iinfo, nuuid, mem_free)
644 (total_disk, free_disk, total_spindles, free_spindles) = \
645 self._ComputeStorageDataFromSpaceInfo(space_info, ninfo.name,
646 has_lvm)
647
648
649 pnr_dyn = {
650 "total_memory": self._GetAttributeFromHypervisorNodeData(
651 hv_info, ninfo.name, "memory_total"),
652 "reserved_memory": self._GetAttributeFromHypervisorNodeData(
653 hv_info, ninfo.name, "memory_dom0"),
654 "free_memory": mem_free,
655 "total_disk": total_disk,
656 "free_disk": free_disk,
657 "total_spindles": total_spindles,
658 "free_spindles": free_spindles,
659 "total_cpus": self._GetAttributeFromHypervisorNodeData(
660 hv_info, ninfo.name, "cpu_total"),
661 "reserved_cpus": self._GetAttributeFromHypervisorNodeData(
662 hv_info, ninfo.name, "cpu_dom0"),
663 "i_pri_memory": i_p_mem,
664 "i_pri_up_memory": i_p_up_mem,
665 }
666 pnr_dyn.update(node_results[ninfo.name])
667 node_results[ninfo.name] = pnr_dyn
668
669 return node_results
670
671 @staticmethod
673 """Compute global instance data.
674
675 """
676 instance_data = {}
677 for iinfo, beinfo in i_list:
678 nic_data = []
679 for nic in iinfo.nics:
680 filled_params = cluster_info.SimpleFillNIC(nic.nicparams)
681 nic_dict = {
682 "mac": nic.mac,
683 "ip": nic.ip,
684 "mode": filled_params[constants.NIC_MODE],
685 "link": filled_params[constants.NIC_LINK],
686 }
687 if filled_params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
688 nic_dict["bridge"] = filled_params[constants.NIC_LINK]
689 nic_data.append(nic_dict)
690 pir = {
691 "tags": list(iinfo.GetTags()),
692 "admin_state": iinfo.admin_state,
693 "vcpus": beinfo[constants.BE_VCPUS],
694 "memory": beinfo[constants.BE_MAXMEM],
695 "spindle_use": beinfo[constants.BE_SPINDLE_USE],
696 "os": iinfo.os,
697 "nodes": [cfg.GetNodeName(iinfo.primary_node)] +
698 cfg.GetNodeNames(iinfo.secondary_nodes),
699 "nics": nic_data,
700 "disks": [{constants.IDISK_SIZE: dsk.size,
701 constants.IDISK_MODE: dsk.mode,
702 constants.IDISK_SPINDLES: dsk.spindles}
703 for dsk in iinfo.disks],
704 "disk_template": iinfo.disk_template,
705 "disks_active": iinfo.disks_active,
706 "hypervisor": iinfo.hypervisor,
707 }
708 pir["disk_space_total"] = gmi.ComputeDiskSize(iinfo.disk_template,
709 pir["disks"])
710 instance_data[iinfo.name] = pir
711
712 return instance_data
713
725
726 - def Run(self, name, validate=True, call_fn=None):
727 """Run an instance allocator and return the results.
728
729 """
730 if call_fn is None:
731 call_fn = self.rpc.call_iallocator_runner
732
733 result = call_fn(self.cfg.GetMasterNode(), name, self.in_text)
734 result.Raise("Failure while running the iallocator script")
735
736 self.out_text = result.payload
737 if validate:
738 self._ValidateResult()
739
741 """Process the allocator results.
742
743 This will process and if successful save the result in
744 self.out_data and the other parameters.
745
746 """
747 try:
748 rdict = serializer.Load(self.out_text)
749 except Exception, err:
750 raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
751
752 if not isinstance(rdict, dict):
753 raise errors.OpExecError("Can't parse iallocator results: not a dict")
754
755
756 if "nodes" in rdict and "result" not in rdict:
757 rdict["result"] = rdict["nodes"]
758 del rdict["nodes"]
759
760 for key in "success", "info", "result":
761 if key not in rdict:
762 raise errors.OpExecError("Can't parse iallocator results:"
763 " missing key '%s'" % key)
764 setattr(self, key, rdict[key])
765
766 self.req.ValidateResult(self, self.result)
767 self.out_data = rdict
768