1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Module implementing the iallocator code."""
32
33 from ganeti import compat
34 from ganeti import constants
35 from ganeti import errors
36 from ganeti import ht
37 from ganeti import outils
38 from ganeti import opcodes
39 import ganeti.rpc.node as rpc
40 from ganeti import serializer
41 from ganeti import utils
42
43 import ganeti.masterd.instance as gmi
44
45
46 _STRING_LIST = ht.TListOf(ht.TString)
47 _JOB_LIST = ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, {
48
49
50 "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID,
51 opcodes.OpInstanceMigrate.OP_ID,
52 opcodes.OpInstanceReplaceDisks.OP_ID]),
53 })))
54
55 _NEVAC_MOVED = \
56 ht.TListOf(ht.TAnd(ht.TIsLength(3),
57 ht.TItems([ht.TNonEmptyString,
58 ht.TNonEmptyString,
59 ht.TListOf(ht.TNonEmptyString),
60 ])))
61 _NEVAC_FAILED = \
62 ht.TListOf(ht.TAnd(ht.TIsLength(2),
63 ht.TItems([ht.TNonEmptyString,
64 ht.TMaybeString,
65 ])))
66 _NEVAC_RESULT = ht.TAnd(ht.TIsLength(3),
67 ht.TItems([_NEVAC_MOVED, _NEVAC_FAILED, _JOB_LIST]))
68
69 _INST_NAME = ("name", ht.TNonEmptyString)
70 _INST_UUID = ("inst_uuid", ht.TNonEmptyString)
74 """Meta class for request definitions.
75
76 """
77 @classmethod
79 """Extract the slots out of REQ_PARAMS.
80
81 """
82 params = attrs.setdefault("REQ_PARAMS", [])
83 return [slot for (slot, _) in params]
84
87 """A generic IAllocator request object.
88
89 """
90 __metaclass__ = _AutoReqParam
91
92 MODE = NotImplemented
93 REQ_PARAMS = []
94 REQ_RESULT = NotImplemented
95
97 """Constructor for IARequestBase.
98
99 The constructor takes only keyword arguments and will set
100 attributes on this object based on the passed arguments. As such,
101 it means that you should not pass arguments which are not in the
102 REQ_PARAMS attribute for this class.
103
104 """
105 outils.ValidatedSlots.__init__(self, **kwargs)
106
107 self.Validate()
108
110 """Validates all parameters of the request.
111
112
113 This method returns L{None} if the validation succeeds, or raises
114 an exception otherwise.
115
116 @rtype: NoneType
117 @return: L{None}, if the validation succeeds
118
119 @raise Exception: validation fails
120
121 """
122 assert self.MODE in constants.VALID_IALLOCATOR_MODES
123
124 for (param, validator) in self.REQ_PARAMS:
125 if not hasattr(self, param):
126 raise errors.OpPrereqError("Request is missing '%s' parameter" % param,
127 errors.ECODE_INVAL)
128
129 value = getattr(self, param)
130 if not validator(value):
131 raise errors.OpPrereqError(("Request parameter '%s' has invalid"
132 " type %s/value %s") %
133 (param, type(value), value),
134 errors.ECODE_INVAL)
135
137 """Gets the request data dict.
138
139 @param cfg: The configuration instance
140
141 """
142 raise NotImplementedError
143
145 """Validates the result of an request.
146
147 @param ia: The IAllocator instance
148 @param result: The IAllocator run result
149 @raises ResultValidationError: If validation fails
150
151 """
152 if ia.success and not self.REQ_RESULT(result):
153 raise errors.ResultValidationError("iallocator returned invalid result,"
154 " expected %s, got %s" %
155 (self.REQ_RESULT, result))
156
159 """An instance allocation request.
160
161 """
162
163 MODE = constants.IALLOCATOR_MODE_ALLOC
164 REQ_PARAMS = [
165 _INST_NAME,
166 ("memory", ht.TNonNegativeInt),
167 ("spindle_use", ht.TNonNegativeInt),
168 ("disks", ht.TListOf(ht.TDict)),
169 ("disk_template", ht.TString),
170 ("os", ht.TString),
171 ("tags", _STRING_LIST),
172 ("nics", ht.TListOf(ht.TDict)),
173 ("vcpus", ht.TInt),
174 ("hypervisor", ht.TString),
175 ("node_whitelist", ht.TMaybeListOf(ht.TNonEmptyString)),
176 ]
177 REQ_RESULT = ht.TList
178
180 """Calculates the required nodes based on the disk_template.
181
182 """
183 if self.disk_template in constants.DTS_INT_MIRROR:
184 return 2
185 else:
186 return 1
187
189 """Requests a new instance.
190
191 The checks for the completeness of the opcode must have already been
192 done.
193
194 """
195 disk_space = gmi.ComputeDiskSize(self.disk_template, self.disks)
196
197 return {
198 "name": self.name,
199 "disk_template": self.disk_template,
200 "tags": self.tags,
201 "os": self.os,
202 "vcpus": self.vcpus,
203 "memory": self.memory,
204 "spindle_use": self.spindle_use,
205 "disks": self.disks,
206 "disk_space_total": disk_space,
207 "nics": self.nics,
208 "required_nodes": self.RequiredNodes(),
209 "hypervisor": self.hypervisor,
210 }
211
222
246
249 """A relocation request.
250
251 """
252
253 MODE = constants.IALLOCATOR_MODE_RELOC
254 REQ_PARAMS = [
255 _INST_UUID,
256 ("relocate_from_node_uuids", _STRING_LIST),
257 ]
258 REQ_RESULT = ht.TList
259
290
314
315 @staticmethod
317 """Returns a list of unique group names for a list of nodes.
318
319 @type node2group: dict
320 @param node2group: Map from node name to group UUID
321 @type groups: dict
322 @param groups: Group information
323 @type nodes: list
324 @param nodes: Node names
325
326 """
327 result = set()
328
329 for node in nodes:
330 try:
331 group_uuid = node2group[node]
332 except KeyError:
333
334 pass
335 else:
336 try:
337 group = groups[group_uuid]
338 except KeyError:
339
340 group_name = group_uuid
341 else:
342 group_name = group["name"]
343
344 result.add(group_name)
345
346 return sorted(result)
347
369
391
394 """IAllocator framework.
395
396 An IAllocator instance has three sets of attributes:
397 - cfg that is needed to query the cluster
398 - input data (all members of the _KEYS class attribute are required)
399 - four buffer attributes (in|out_data|text), that represent the
400 input (to the external script) in text and data structure format,
401 and the output from it, again in two formats
402 - the result variables from the script (success, info, nodes) for
403 easy usage
404
405 """
406
407
408
409 - def __init__(self, cfg, rpc_runner, req):
410 self.cfg = cfg
411 self.rpc = rpc_runner
412 self.req = req
413
414 self.in_text = self.out_text = self.in_data = self.out_data = None
415
416 self.success = self.info = self.result = None
417
418 self._BuildInputData(req)
419
422 """Prepare and execute node info call.
423
424 @type disk_templates: list of string
425 @param disk_templates: the disk templates of the instances to be allocated
426 @type node_list: list of strings
427 @param node_list: list of nodes' UUIDs
428 @type cluster_info: L{objects.Cluster}
429 @param cluster_info: the cluster's information from the config
430 @type hypervisor_name: string
431 @param hypervisor_name: the hypervisor name
432 @rtype: same as the result of the node info RPC call
433 @return: the result of the node info RPC call
434
435 """
436 storage_units_raw = utils.storage.GetStorageUnits(self.cfg, disk_templates)
437 storage_units = rpc.PrepareStorageUnitsForNodes(self.cfg, storage_units_raw,
438 node_list)
439 hvspecs = [(hypervisor_name, cluster_info.hvparams[hypervisor_name])]
440 return self.rpc.call_node_info(node_list, storage_units, hvspecs)
441
443 """Compute the generic allocator input data.
444
445 @type disk_template: list of string
446 @param disk_template: the disk templates of the instances to be allocated
447
448 """
449 cluster_info = self.cfg.GetClusterInfo()
450
451 data = {
452 "version": constants.IALLOCATOR_VERSION,
453 "cluster_name": self.cfg.GetClusterName(),
454 "cluster_tags": list(cluster_info.GetTags()),
455 "enabled_hypervisors": list(cluster_info.enabled_hypervisors),
456 "ipolicy": cluster_info.ipolicy,
457 }
458 ninfo = self.cfg.GetAllNodesInfo()
459 iinfo = self.cfg.GetAllInstancesInfo().values()
460 i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo]
461
462
463 node_list = [n.uuid for n in ninfo.values() if n.vm_capable]
464
465 if isinstance(self.req, IAReqInstanceAlloc):
466 hypervisor_name = self.req.hypervisor
467 node_whitelist = self.req.node_whitelist
468 elif isinstance(self.req, IAReqRelocate):
469 hypervisor_name = self.cfg.GetInstanceInfo(self.req.inst_uuid).hypervisor
470 node_whitelist = None
471 else:
472 hypervisor_name = cluster_info.primary_hypervisor
473 node_whitelist = None
474
475 if not disk_template:
476 disk_template = cluster_info.enabled_disk_templates[0]
477
478 node_data = self._ComputeClusterDataNodeInfo([disk_template], node_list,
479 cluster_info, hypervisor_name)
480
481 node_iinfo = \
482 self.rpc.call_all_instances_info(node_list,
483 cluster_info.enabled_hypervisors,
484 cluster_info.hvparams)
485
486 data["nodegroups"] = self._ComputeNodeGroupData(self.cfg)
487
488 config_ndata = self._ComputeBasicNodeData(self.cfg, ninfo, node_whitelist)
489 data["nodes"] = self._ComputeDynamicNodeData(
490 ninfo, node_data, node_iinfo, i_list, config_ndata, disk_template)
491 assert len(data["nodes"]) == len(ninfo), \
492 "Incomplete node data computed"
493
494 data["instances"] = self._ComputeInstanceData(self.cfg, cluster_info,
495 i_list)
496
497 self.in_data = data
498
499 @staticmethod
501 """Compute node groups data.
502
503 """
504 cluster = cfg.GetClusterInfo()
505 ng = dict((guuid, {
506 "name": gdata.name,
507 "alloc_policy": gdata.alloc_policy,
508 "networks": [net_uuid for net_uuid, _ in gdata.networks.items()],
509 "ipolicy": gmi.CalculateGroupIPolicy(cluster, gdata),
510 "tags": list(gdata.GetTags()),
511 })
512 for guuid, gdata in cfg.GetAllNodeGroupsInfo().items())
513
514 return ng
515
516 @staticmethod
518 """Compute global node data.
519
520 @rtype: dict
521 @returns: a dict of name: (node dict, node config)
522
523 """
524
525 node_results = dict((ninfo.name, {
526 "tags": list(ninfo.GetTags()),
527 "primary_ip": ninfo.primary_ip,
528 "secondary_ip": ninfo.secondary_ip,
529 "offline": (ninfo.offline or
530 not (node_whitelist is None or
531 ninfo.name in node_whitelist)),
532 "drained": ninfo.drained,
533 "master_candidate": ninfo.master_candidate,
534 "group": ninfo.group,
535 "master_capable": ninfo.master_capable,
536 "vm_capable": ninfo.vm_capable,
537 "ndparams": cfg.GetNdParams(ninfo),
538 })
539 for ninfo in node_cfg.values())
540
541 return node_results
542
543 @staticmethod
545 """Extract an attribute from the hypervisor's node information.
546
547 This is a helper function to extract data from the hypervisor's information
548 about the node, as part of the result of a node_info query.
549
550 @type hv_info: dict of strings
551 @param hv_info: dictionary of node information from the hypervisor
552 @type node_name: string
553 @param node_name: name of the node
554 @type attr: string
555 @param attr: key of the attribute in the hv_info dictionary
556 @rtype: integer
557 @return: the value of the attribute
558 @raises errors.OpExecError: if key not in dictionary or value not
559 integer
560
561 """
562 if attr not in hv_info:
563 raise errors.OpExecError("Node '%s' didn't return attribute"
564 " '%s'" % (node_name, attr))
565 value = hv_info[attr]
566 if not isinstance(value, int):
567 raise errors.OpExecError("Node '%s' returned invalid value"
568 " for '%s': %s" %
569 (node_name, attr, value))
570 return value
571
572 @staticmethod
575 """Extract storage data from node info.
576
577 @type space_info: see result of the RPC call node info
578 @param space_info: the storage reporting part of the result of the RPC call
579 node info
580 @type node_name: string
581 @param node_name: the node's name
582 @type disk_template: string
583 @param disk_template: the disk template to report space for
584 @rtype: 4-tuple of integers
585 @return: tuple of storage info (total_disk, free_disk, total_spindles,
586 free_spindles)
587
588 """
589 storage_type = constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template]
590 if storage_type not in constants.STS_REPORT:
591 total_disk = total_spindles = 0
592 free_disk = free_spindles = 0
593 else:
594 template_space_info = utils.storage.LookupSpaceInfoByDiskTemplate(
595 space_info, disk_template)
596 if not template_space_info:
597 raise errors.OpExecError("Node '%s' didn't return space info for disk"
598 "template '%s'" % (node_name, disk_template))
599 total_disk = template_space_info["storage_size"]
600 free_disk = template_space_info["storage_free"]
601
602 total_spindles = 0
603 free_spindles = 0
604 if disk_template in constants.DTS_LVM:
605 lvm_pv_info = utils.storage.LookupSpaceInfoByStorageType(
606 space_info, constants.ST_LVM_PV)
607 if lvm_pv_info:
608 total_spindles = lvm_pv_info["storage_size"]
609 free_spindles = lvm_pv_info["storage_free"]
610 return (total_disk, free_disk, total_spindles, free_spindles)
611
612 @staticmethod
614 """Extract storage data from node info.
615
616 @type space_info: see result of the RPC call node info
617 @param space_info: the storage reporting part of the result of the RPC call
618 node info
619 @type node_name: string
620 @param node_name: the node's name
621 @type has_lvm: boolean
622 @param has_lvm: whether or not LVM storage information is requested
623 @rtype: 4-tuple of integers
624 @return: tuple of storage info (total_disk, free_disk, total_spindles,
625 free_spindles)
626
627 """
628
629 if has_lvm:
630 lvm_vg_info = utils.storage.LookupSpaceInfoByStorageType(
631 space_info, constants.ST_LVM_VG)
632 if not lvm_vg_info:
633 raise errors.OpExecError("Node '%s' didn't return LVM vg space info."
634 % (node_name))
635 total_disk = lvm_vg_info["storage_size"]
636 free_disk = lvm_vg_info["storage_free"]
637 lvm_pv_info = utils.storage.LookupSpaceInfoByStorageType(
638 space_info, constants.ST_LVM_PV)
639 if not lvm_pv_info:
640 raise errors.OpExecError("Node '%s' didn't return LVM pv space info."
641 % (node_name))
642 total_spindles = lvm_pv_info["storage_size"]
643 free_spindles = lvm_pv_info["storage_free"]
644 else:
645
646 total_disk = free_disk = 0
647 total_spindles = free_spindles = 0
648 return (total_disk, free_disk, total_spindles, free_spindles)
649
650 @staticmethod
653 """Compute memory used by primary instances.
654
655 @rtype: tuple (int, int, int)
656 @returns: A tuple of three integers: 1. the sum of memory used by primary
657 instances on the node (including the ones that are currently down), 2.
658 the sum of memory used by primary instances of the node that are up, 3.
659 the amount of memory that is free on the node considering the current
660 usage of the instances.
661
662 """
663 i_p_mem = i_p_up_mem = 0
664 mem_free = input_mem_free
665 for iinfo, beinfo in instance_list:
666 if iinfo.primary_node == node_uuid:
667 i_p_mem += beinfo[constants.BE_MAXMEM]
668 if iinfo.name not in node_instances_info[node_uuid].payload:
669 i_used_mem = 0
670 else:
671 i_used_mem = int(node_instances_info[node_uuid]
672 .payload[iinfo.name]["memory"])
673 i_mem_diff = beinfo[constants.BE_MAXMEM] - i_used_mem
674 mem_free -= max(0, i_mem_diff)
675
676 if iinfo.admin_state == constants.ADMINST_UP:
677 i_p_up_mem += beinfo[constants.BE_MAXMEM]
678 return (i_p_mem, i_p_up_mem, mem_free)
679
682 """Compute global node data.
683
684 @param node_results: the basic node structures as filled from the config
685
686 """
687
688
689 node_results = dict(node_results)
690 for nuuid, nresult in node_data.items():
691 ninfo = node_cfg[nuuid]
692 assert ninfo.name in node_results, "Missing basic data for node %s" % \
693 ninfo.name
694
695 if not ninfo.offline:
696 nresult.Raise("Can't get data for node %s" % ninfo.name)
697 node_iinfo[nuuid].Raise("Can't get node instance info from node %s" %
698 ninfo.name)
699 (_, space_info, (hv_info, )) = nresult.payload
700
701 mem_free = self._GetAttributeFromHypervisorNodeData(hv_info, ninfo.name,
702 "memory_free")
703
704 (i_p_mem, i_p_up_mem, mem_free) = self._ComputeInstanceMemory(
705 i_list, node_iinfo, nuuid, mem_free)
706 (total_disk, free_disk, total_spindles, free_spindles) = \
707 self._ComputeStorageDataFromSpaceInfoByTemplate(
708 space_info, ninfo.name, disk_template)
709
710
711 pnr_dyn = {
712 "total_memory": self._GetAttributeFromHypervisorNodeData(
713 hv_info, ninfo.name, "memory_total"),
714 "reserved_memory": self._GetAttributeFromHypervisorNodeData(
715 hv_info, ninfo.name, "memory_dom0"),
716 "free_memory": mem_free,
717 "total_disk": total_disk,
718 "free_disk": free_disk,
719 "total_spindles": total_spindles,
720 "free_spindles": free_spindles,
721 "total_cpus": self._GetAttributeFromHypervisorNodeData(
722 hv_info, ninfo.name, "cpu_total"),
723 "reserved_cpus": self._GetAttributeFromHypervisorNodeData(
724 hv_info, ninfo.name, "cpu_dom0"),
725 "i_pri_memory": i_p_mem,
726 "i_pri_up_memory": i_p_up_mem,
727 }
728 pnr_dyn.update(node_results[ninfo.name])
729 node_results[ninfo.name] = pnr_dyn
730
731 return node_results
732
733 @staticmethod
735 """Compute global instance data.
736
737 """
738 instance_data = {}
739 for iinfo, beinfo in i_list:
740 nic_data = []
741 for nic in iinfo.nics:
742 filled_params = cluster_info.SimpleFillNIC(nic.nicparams)
743 nic_dict = {
744 "mac": nic.mac,
745 "ip": nic.ip,
746 "mode": filled_params[constants.NIC_MODE],
747 "link": filled_params[constants.NIC_LINK],
748 }
749 if filled_params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
750 nic_dict["bridge"] = filled_params[constants.NIC_LINK]
751 nic_data.append(nic_dict)
752 pir = {
753 "tags": list(iinfo.GetTags()),
754 "admin_state": iinfo.admin_state,
755 "vcpus": beinfo[constants.BE_VCPUS],
756 "memory": beinfo[constants.BE_MAXMEM],
757 "spindle_use": beinfo[constants.BE_SPINDLE_USE],
758 "os": iinfo.os,
759 "nodes": [cfg.GetNodeName(iinfo.primary_node)] +
760 cfg.GetNodeNames(iinfo.secondary_nodes),
761 "nics": nic_data,
762 "disks": [{constants.IDISK_SIZE: dsk.size,
763 constants.IDISK_MODE: dsk.mode,
764 constants.IDISK_SPINDLES: dsk.spindles}
765 for dsk in iinfo.disks],
766 "disk_template": iinfo.disk_template,
767 "disks_active": iinfo.disks_active,
768 "hypervisor": iinfo.hypervisor,
769 }
770 pir["disk_space_total"] = gmi.ComputeDiskSize(iinfo.disk_template,
771 pir["disks"])
772 instance_data[iinfo.name] = pir
773
774 return instance_data
775
790
791 - def Run(self, name, validate=True, call_fn=None):
806
808 """Process the allocator results.
809
810 This will process and if successful save the result in
811 self.out_data and the other parameters.
812
813 """
814 try:
815 rdict = serializer.Load(self.out_text)
816 except Exception, err:
817 raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
818
819 if not isinstance(rdict, dict):
820 raise errors.OpExecError("Can't parse iallocator results: not a dict")
821
822
823 if "nodes" in rdict and "result" not in rdict:
824 rdict["result"] = rdict["nodes"]
825 del rdict["nodes"]
826
827 for key in "success", "info", "result":
828 if key not in rdict:
829 raise errors.OpExecError("Can't parse iallocator results:"
830 " missing key '%s'" % key)
831 setattr(self, key, rdict[key])
832
833 self.req.ValidateResult(self, self.result)
834 self.out_data = rdict
835