Do not add a new Inotify watchers on timer
[ganeti-github.git] / lib / masterd / iallocator.py
1 #
2 #
3
4 # Copyright (C) 2012, 2013 Google Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
9 # met:
10 #
11 # 1. Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
13 #
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
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 import logging
46
47 _STRING_LIST = ht.TListOf(ht.TString)
48 _JOB_LIST = ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, {
49 # pylint: disable=E1101
50 # Class '...' has no 'OP_ID' member
51 "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID,
52 opcodes.OpInstanceMigrate.OP_ID,
53 opcodes.OpInstanceReplaceDisks.OP_ID]),
54 })))
55
56 _NEVAC_MOVED = \
57 ht.TListOf(ht.TAnd(ht.TIsLength(3),
58 ht.TItems([ht.TNonEmptyString,
59 ht.TNonEmptyString,
60 ht.TListOf(ht.TNonEmptyString),
61 ])))
62 _NEVAC_FAILED = \
63 ht.TListOf(ht.TAnd(ht.TIsLength(2),
64 ht.TItems([ht.TNonEmptyString,
65 ht.TMaybeString,
66 ])))
67 _NEVAC_RESULT = ht.TAnd(ht.TIsLength(3),
68 ht.TItems([_NEVAC_MOVED, _NEVAC_FAILED, _JOB_LIST]))
69
70 _INST_NAME = ("name", ht.TNonEmptyString)
71 _INST_UUID = ("inst_uuid", ht.TNonEmptyString)
72
73
74 class _AutoReqParam(outils.AutoSlots):
75 """Meta class for request definitions.
76
77 """
78 @classmethod
79 def _GetSlots(mcs, attrs):
80 """Extract the slots out of REQ_PARAMS.
81
82 """
83 params = attrs.setdefault("REQ_PARAMS", [])
84 return [slot for (slot, _) in params]
85
86
87 class IARequestBase(outils.ValidatedSlots):
88 """A generic IAllocator request object.
89
90 """
91 __metaclass__ = _AutoReqParam
92
93 MODE = NotImplemented
94 REQ_PARAMS = []
95 REQ_RESULT = NotImplemented
96
97 def __init__(self, **kwargs):
98 """Constructor for IARequestBase.
99
100 The constructor takes only keyword arguments and will set
101 attributes on this object based on the passed arguments. As such,
102 it means that you should not pass arguments which are not in the
103 REQ_PARAMS attribute for this class.
104
105 """
106 outils.ValidatedSlots.__init__(self, **kwargs)
107
108 self.Validate()
109
110 def Validate(self):
111 """Validates all parameters of the request.
112
113
114 This method returns L{None} if the validation succeeds, or raises
115 an exception otherwise.
116
117 @rtype: NoneType
118 @return: L{None}, if the validation succeeds
119
120 @raise Exception: validation fails
121
122 """
123 assert self.MODE in constants.VALID_IALLOCATOR_MODES
124
125 for (param, validator) in self.REQ_PARAMS:
126 if not hasattr(self, param):
127 raise errors.OpPrereqError("Request is missing '%s' parameter" % param,
128 errors.ECODE_INVAL)
129
130 value = getattr(self, param)
131 if not validator(value):
132 raise errors.OpPrereqError(("Request parameter '%s' has invalid"
133 " type %s/value %s") %
134 (param, type(value), value),
135 errors.ECODE_INVAL)
136
137 def GetRequest(self, cfg):
138 """Gets the request data dict.
139
140 @param cfg: The configuration instance
141
142 """
143 raise NotImplementedError
144
145 def GetExtraParams(self): # pylint: disable=R0201
146 """Gets extra parameters to the IAllocator call.
147
148 """
149 return {}
150
151 def ValidateResult(self, ia, result):
152 """Validates the result of an request.
153
154 @param ia: The IAllocator instance
155 @param result: The IAllocator run result
156 @raises ResultValidationError: If validation fails
157
158 """
159 if ia.success and not self.REQ_RESULT(result):
160 raise errors.ResultValidationError("iallocator returned invalid result,"
161 " expected %s, got %s" %
162 (self.REQ_RESULT, result))
163
164
165 class IAReqInstanceAlloc(IARequestBase):
166 """An instance allocation request.
167
168 """
169 # pylint: disable=E1101
170 MODE = constants.IALLOCATOR_MODE_ALLOC
171 REQ_PARAMS = [
172 _INST_NAME,
173 ("memory", ht.TNonNegativeInt),
174 ("spindle_use", ht.TNonNegativeInt),
175 ("disks", ht.TListOf(ht.TDict)),
176 ("disk_template", ht.TString),
177 ("group_name", ht.TMaybe(ht.TNonEmptyString)),
178 ("os", ht.TString),
179 ("tags", _STRING_LIST),
180 ("nics", ht.TListOf(ht.TDict)),
181 ("vcpus", ht.TInt),
182 ("hypervisor", ht.TString),
183 ("node_whitelist", ht.TMaybeListOf(ht.TNonEmptyString)),
184 ]
185 REQ_RESULT = ht.TList
186
187 def RequiredNodes(self):
188 """Calculates the required nodes based on the disk_template.
189
190 """
191 if self.disk_template in constants.DTS_INT_MIRROR:
192 return 2
193 else:
194 return 1
195
196 def GetRequest(self, cfg):
197 """Requests a new instance.
198
199 The checks for the completeness of the opcode must have already been
200 done.
201
202 """
203 for d in self.disks:
204 d[constants.IDISK_TYPE] = self.disk_template
205 disk_space = gmi.ComputeDiskSize(self.disks)
206
207 return {
208 "name": self.name,
209 "disk_template": self.disk_template,
210 "group_name": self.group_name,
211 "tags": self.tags,
212 "os": self.os,
213 "vcpus": self.vcpus,
214 "memory": self.memory,
215 "spindle_use": self.spindle_use,
216 "disks": self.disks,
217 "disk_space_total": disk_space,
218 "nics": self.nics,
219 "required_nodes": self.RequiredNodes(),
220 "hypervisor": self.hypervisor,
221 }
222
223 def ValidateResult(self, ia, result):
224 """Validates an single instance allocation request.
225
226 """
227 IARequestBase.ValidateResult(self, ia, result)
228
229 if ia.success and len(result) != self.RequiredNodes():
230 raise errors.ResultValidationError("iallocator returned invalid number"
231 " of nodes (%s), required %s" %
232 (len(result), self.RequiredNodes()))
233
234
235 class IAReqMultiInstanceAlloc(IARequestBase):
236 """An multi instance allocation request.
237
238 """
239 # pylint: disable=E1101
240 MODE = constants.IALLOCATOR_MODE_MULTI_ALLOC
241 REQ_PARAMS = [
242 ("instances", ht.TListOf(ht.TInstanceOf(IAReqInstanceAlloc))),
243 ]
244 _MASUCCESS = \
245 ht.TListOf(ht.TAnd(ht.TIsLength(2),
246 ht.TItems([ht.TNonEmptyString,
247 ht.TListOf(ht.TNonEmptyString),
248 ])))
249 _MAFAILED = ht.TListOf(ht.TNonEmptyString)
250 REQ_RESULT = ht.TAnd(ht.TList, ht.TIsLength(2),
251 ht.TItems([_MASUCCESS, _MAFAILED]))
252
253 def GetRequest(self, cfg):
254 return {
255 "instances": [iareq.GetRequest(cfg) for iareq in self.instances],
256 }
257
258
259 class IAReqRelocate(IARequestBase):
260 """A relocation request.
261
262 """
263 # pylint: disable=E1101
264 MODE = constants.IALLOCATOR_MODE_RELOC
265 REQ_PARAMS = [
266 _INST_UUID,
267 ("relocate_from_node_uuids", _STRING_LIST),
268 ]
269 REQ_RESULT = ht.TList
270
271 def GetRequest(self, cfg):
272 """Request an relocation of an instance
273
274 The checks for the completeness of the opcode must have already been
275 done.
276
277 """
278 instance = cfg.GetInstanceInfo(self.inst_uuid)
279 disks = cfg.GetInstanceDisks(self.inst_uuid)
280 if instance is None:
281 raise errors.ProgrammerError("Unknown instance '%s' passed to"
282 " IAllocator" % self.inst_uuid)
283
284 if not utils.AllDiskOfType(disks, constants.DTS_MIRRORED):
285 raise errors.OpPrereqError("Can't relocate non-mirrored instances",
286 errors.ECODE_INVAL)
287
288 secondary_nodes = cfg.GetInstanceSecondaryNodes(instance.uuid)
289 if (utils.AnyDiskOfType(disks, constants.DTS_INT_MIRROR) and
290 len(secondary_nodes) != 1):
291 raise errors.OpPrereqError("Instance has not exactly one secondary node",
292 errors.ECODE_STATE)
293
294 disk_sizes = [{constants.IDISK_SIZE: disk.size,
295 constants.IDISK_TYPE: disk.dev_type} for disk in disks]
296 disk_space = gmi.ComputeDiskSize(disk_sizes)
297
298 return {
299 "name": instance.name,
300 "disk_space_total": disk_space,
301 "required_nodes": 1,
302 "relocate_from": cfg.GetNodeNames(self.relocate_from_node_uuids),
303 }
304
305 def ValidateResult(self, ia, result):
306 """Validates the result of an relocation request.
307
308 """
309 IARequestBase.ValidateResult(self, ia, result)
310
311 node2group = dict((name, ndata["group"])
312 for (name, ndata) in ia.in_data["nodes"].items())
313
314 fn = compat.partial(self._NodesToGroups, node2group,
315 ia.in_data["nodegroups"])
316
317 instance = ia.cfg.GetInstanceInfo(self.inst_uuid)
318 request_groups = fn(ia.cfg.GetNodeNames(self.relocate_from_node_uuids) +
319 ia.cfg.GetNodeNames([instance.primary_node]))
320 result_groups = fn(result + ia.cfg.GetNodeNames([instance.primary_node]))
321
322 if ia.success and not set(result_groups).issubset(request_groups):
323 raise errors.ResultValidationError("Groups of nodes returned by"
324 " iallocator (%s) differ from original"
325 " groups (%s)" %
326 (utils.CommaJoin(result_groups),
327 utils.CommaJoin(request_groups)))
328
329 @staticmethod
330 def _NodesToGroups(node2group, groups, nodes):
331 """Returns a list of unique group names for a list of nodes.
332
333 @type node2group: dict
334 @param node2group: Map from node name to group UUID
335 @type groups: dict
336 @param groups: Group information
337 @type nodes: list
338 @param nodes: Node names
339
340 """
341 result = set()
342
343 for node in nodes:
344 try:
345 group_uuid = node2group[node]
346 except KeyError:
347 # Ignore unknown node
348 pass
349 else:
350 try:
351 group = groups[group_uuid]
352 except KeyError:
353 # Can't find group, let's use UUID
354 group_name = group_uuid
355 else:
356 group_name = group["name"]
357
358 result.add(group_name)
359
360 return sorted(result)
361
362
363 class IAReqNodeEvac(IARequestBase):
364 """A node evacuation request.
365
366 """
367 # pylint: disable=E1101
368 MODE = constants.IALLOCATOR_MODE_NODE_EVAC
369 REQ_PARAMS = [
370 ("instances", _STRING_LIST),
371 ("evac_mode", ht.TEvacMode),
372 ("ignore_soft_errors", ht.TMaybe(ht.TBool)),
373 ]
374 REQ_RESULT = _NEVAC_RESULT
375
376 def GetRequest(self, cfg):
377 """Get data for node-evacuate requests.
378
379 """
380 return {
381 "instances": self.instances,
382 "evac_mode": self.evac_mode,
383 }
384
385 def GetExtraParams(self):
386 """Get extra iallocator command line options for
387 node-evacuate requests.
388
389 """
390 if self.ignore_soft_errors:
391 return {"ignore-soft-errors": None}
392 else:
393 return {}
394
395
396 class IAReqGroupChange(IARequestBase):
397 """A group change request.
398
399 """
400 # pylint: disable=E1101
401 MODE = constants.IALLOCATOR_MODE_CHG_GROUP
402 REQ_PARAMS = [
403 ("instances", _STRING_LIST),
404 ("target_groups", _STRING_LIST),
405 ]
406 REQ_RESULT = _NEVAC_RESULT
407
408 def GetRequest(self, cfg):
409 """Get data for node-evacuate requests.
410
411 """
412 return {
413 "instances": self.instances,
414 "target_groups": self.target_groups,
415 }
416
417
418 class IAllocator(object):
419 """IAllocator framework.
420
421 An IAllocator instance has three sets of attributes:
422 - cfg that is needed to query the cluster
423 - input data (all members of the _KEYS class attribute are required)
424 - four buffer attributes (in|out_data|text), that represent the
425 input (to the external script) in text and data structure format,
426 and the output from it, again in two formats
427 - the result variables from the script (success, info, nodes) for
428 easy usage
429
430 """
431 # pylint: disable=R0902
432 # lots of instance attributes
433
434 def __init__(self, cfg, rpc_runner, req):
435 self.cfg = cfg
436 self.rpc = rpc_runner
437 self.req = req
438 # init buffer variables
439 self.in_text = self.out_text = self.in_data = self.out_data = None
440 # init result fields
441 self.success = self.info = self.result = None
442
443 self._BuildInputData(req)
444
445 def _ComputeClusterDataNodeInfo(self, disk_templates, node_list,
446 cluster_info, hypervisor_name):
447 """Prepare and execute node info call.
448
449 @type disk_templates: list of string
450 @param disk_templates: the disk templates of the instances to be allocated
451 @type node_list: list of strings
452 @param node_list: list of nodes' UUIDs
453 @type cluster_info: L{objects.Cluster}
454 @param cluster_info: the cluster's information from the config
455 @type hypervisor_name: string
456 @param hypervisor_name: the hypervisor name
457 @rtype: same as the result of the node info RPC call
458 @return: the result of the node info RPC call
459
460 """
461 storage_units_raw = utils.storage.GetStorageUnits(self.cfg, disk_templates)
462 storage_units = rpc.PrepareStorageUnitsForNodes(self.cfg, storage_units_raw,
463 node_list)
464 hvspecs = [(hypervisor_name, cluster_info.hvparams[hypervisor_name])]
465 return self.rpc.call_node_info(node_list, storage_units, hvspecs)
466
467 def _ComputeClusterData(self, disk_template=None):
468 """Compute the generic allocator input data.
469
470 @type disk_template: list of string
471 @param disk_template: the disk templates of the instances to be allocated
472
473 """
474 cfg = self.cfg.GetDetachedConfig()
475 cluster_info = cfg.GetClusterInfo()
476 # cluster data
477 data = {
478 "version": constants.IALLOCATOR_VERSION,
479 "cluster_name": cluster_info.cluster_name,
480 "cluster_tags": list(cluster_info.GetTags()),
481 "enabled_hypervisors": list(cluster_info.enabled_hypervisors),
482 "ipolicy": cluster_info.ipolicy,
483 }
484 ginfo = cfg.GetAllNodeGroupsInfo()
485 ninfo = cfg.GetAllNodesInfo()
486 iinfo = cfg.GetAllInstancesInfo()
487 i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo.values()]
488
489 # node data
490 node_list = [n.uuid for n in ninfo.values() if n.vm_capable]
491
492 if isinstance(self.req, IAReqInstanceAlloc):
493 hypervisor_name = self.req.hypervisor
494 node_whitelist = self.req.node_whitelist
495 elif isinstance(self.req, IAReqRelocate):
496 hypervisor_name = iinfo[self.req.inst_uuid].hypervisor
497 node_whitelist = None
498 else:
499 hypervisor_name = cluster_info.primary_hypervisor
500 node_whitelist = None
501
502 if not disk_template:
503 disk_template = cluster_info.enabled_disk_templates[0]
504
505 node_data = self._ComputeClusterDataNodeInfo([disk_template], node_list,
506 cluster_info, hypervisor_name)
507
508 node_iinfo = \
509 self.rpc.call_all_instances_info(node_list,
510 cluster_info.enabled_hypervisors,
511 cluster_info.hvparams)
512
513 data["nodegroups"] = self._ComputeNodeGroupData(cluster_info, ginfo)
514
515 config_ndata = self._ComputeBasicNodeData(cfg, ninfo, node_whitelist)
516 data["nodes"] = self._ComputeDynamicNodeData(
517 ninfo, node_data, node_iinfo, i_list, config_ndata, disk_template)
518 assert len(data["nodes"]) == len(ninfo), \
519 "Incomplete node data computed"
520
521 data["instances"] = self._ComputeInstanceData(cfg, cluster_info, i_list)
522
523 self.in_data = data
524
525 @staticmethod
526 def _ComputeNodeGroupData(cluster, ginfo):
527 """Compute node groups data.
528
529 """
530 ng = dict((guuid, {
531 "name": gdata.name,
532 "alloc_policy": gdata.alloc_policy,
533 "networks": [net_uuid for net_uuid, _ in gdata.networks.items()],
534 "ipolicy": gmi.CalculateGroupIPolicy(cluster, gdata),
535 "tags": list(gdata.GetTags()),
536 })
537 for guuid, gdata in ginfo.items())
538
539 return ng
540
541 @staticmethod
542 def _ComputeBasicNodeData(cfg, node_cfg, node_whitelist):
543 """Compute global node data.
544
545 @rtype: dict
546 @returns: a dict of name: (node dict, node config)
547
548 """
549 # fill in static (config-based) values
550 node_results = dict((ninfo.name, {
551 "tags": list(ninfo.GetTags()),
552 "primary_ip": ninfo.primary_ip,
553 "secondary_ip": ninfo.secondary_ip,
554 "offline": (ninfo.offline or
555 not (node_whitelist is None or
556 ninfo.name in node_whitelist)),
557 "drained": ninfo.drained,
558 "master_candidate": ninfo.master_candidate,
559 "group": ninfo.group,
560 "master_capable": ninfo.master_capable,
561 "vm_capable": ninfo.vm_capable,
562 "ndparams": cfg.GetNdParams(ninfo),
563 })
564 for ninfo in node_cfg.values())
565
566 return node_results
567
568 @staticmethod
569 def _GetAttributeFromHypervisorNodeData(hv_info, node_name, attr):
570 """Extract an attribute from the hypervisor's node information.
571
572 This is a helper function to extract data from the hypervisor's information
573 about the node, as part of the result of a node_info query.
574
575 @type hv_info: dict of strings
576 @param hv_info: dictionary of node information from the hypervisor
577 @type node_name: string
578 @param node_name: name of the node
579 @type attr: string
580 @param attr: key of the attribute in the hv_info dictionary
581 @rtype: integer
582 @return: the value of the attribute
583 @raises errors.OpExecError: if key not in dictionary or value not
584 integer
585
586 """
587 if attr not in hv_info:
588 raise errors.OpExecError("Node '%s' didn't return attribute"
589 " '%s'" % (node_name, attr))
590 value = hv_info[attr]
591 if not isinstance(value, int):
592 raise errors.OpExecError("Node '%s' returned invalid value"
593 " for '%s': %s" %
594 (node_name, attr, value))
595 return value
596
597 @staticmethod
598 def _ComputeStorageDataFromSpaceInfoByTemplate(
599 space_info, node_name, disk_template):
600 """Extract storage data from node info.
601
602 @type space_info: see result of the RPC call node info
603 @param space_info: the storage reporting part of the result of the RPC call
604 node info
605 @type node_name: string
606 @param node_name: the node's name
607 @type disk_template: string
608 @param disk_template: the disk template to report space for
609 @rtype: 4-tuple of integers
610 @return: tuple of storage info (total_disk, free_disk, total_spindles,
611 free_spindles)
612
613 """
614 storage_type = constants.MAP_DISK_TEMPLATE_STORAGE_TYPE[disk_template]
615 if storage_type not in constants.STS_REPORT:
616 total_disk = total_spindles = 0
617 free_disk = free_spindles = 0
618 else:
619 template_space_info = utils.storage.LookupSpaceInfoByDiskTemplate(
620 space_info, disk_template)
621 if not template_space_info:
622 raise errors.OpExecError("Node '%s' didn't return space info for disk"
623 "template '%s'" % (node_name, disk_template))
624 total_disk = template_space_info["storage_size"]
625 free_disk = template_space_info["storage_free"]
626
627 total_spindles = 0
628 free_spindles = 0
629 if disk_template in constants.DTS_LVM:
630 lvm_pv_info = utils.storage.LookupSpaceInfoByStorageType(
631 space_info, constants.ST_LVM_PV)
632 if lvm_pv_info:
633 total_spindles = lvm_pv_info["storage_size"]
634 free_spindles = lvm_pv_info["storage_free"]
635 return (total_disk, free_disk, total_spindles, free_spindles)
636
637 @staticmethod
638 def _ComputeStorageDataFromSpaceInfo(space_info, node_name, has_lvm):
639 """Extract storage data from node info.
640
641 @type space_info: see result of the RPC call node info
642 @param space_info: the storage reporting part of the result of the RPC call
643 node info
644 @type node_name: string
645 @param node_name: the node's name
646 @type has_lvm: boolean
647 @param has_lvm: whether or not LVM storage information is requested
648 @rtype: 4-tuple of integers
649 @return: tuple of storage info (total_disk, free_disk, total_spindles,
650 free_spindles)
651
652 """
653 # TODO: replace this with proper storage reporting
654 if has_lvm:
655 lvm_vg_info = utils.storage.LookupSpaceInfoByStorageType(
656 space_info, constants.ST_LVM_VG)
657 if not lvm_vg_info:
658 raise errors.OpExecError("Node '%s' didn't return LVM vg space info."
659 % (node_name))
660 total_disk = lvm_vg_info["storage_size"]
661 free_disk = lvm_vg_info["storage_free"]
662 lvm_pv_info = utils.storage.LookupSpaceInfoByStorageType(
663 space_info, constants.ST_LVM_PV)
664 if not lvm_pv_info:
665 raise errors.OpExecError("Node '%s' didn't return LVM pv space info."
666 % (node_name))
667 total_spindles = lvm_pv_info["storage_size"]
668 free_spindles = lvm_pv_info["storage_free"]
669 else:
670 # we didn't even ask the node for VG status, so use zeros
671 total_disk = free_disk = 0
672 total_spindles = free_spindles = 0
673 return (total_disk, free_disk, total_spindles, free_spindles)
674
675 @staticmethod
676 def _ComputeInstanceMemory(instance_list, node_instances_info, node_uuid,
677 input_mem_free):
678 """Compute memory used by primary instances.
679
680 @rtype: tuple (int, int, int)
681 @returns: A tuple of three integers: 1. the sum of memory used by primary
682 instances on the node (including the ones that are currently down), 2.
683 the sum of memory used by primary instances of the node that are up, 3.
684 the amount of memory that is free on the node considering the current
685 usage of the instances.
686
687 """
688 i_p_mem = i_p_up_mem = 0
689 mem_free = input_mem_free
690 for iinfo, beinfo in instance_list:
691 if iinfo.primary_node == node_uuid:
692 i_p_mem += beinfo[constants.BE_MAXMEM]
693 if iinfo.name not in node_instances_info[node_uuid].payload:
694 i_used_mem = 0
695 else:
696 i_used_mem = int(node_instances_info[node_uuid]
697 .payload[iinfo.name]["memory"])
698 i_mem_diff = beinfo[constants.BE_MAXMEM] - i_used_mem
699 mem_free -= max(0, i_mem_diff)
700
701 if iinfo.admin_state == constants.ADMINST_UP:
702 i_p_up_mem += beinfo[constants.BE_MAXMEM]
703 return (i_p_mem, i_p_up_mem, mem_free)
704
705 def _ComputeDynamicNodeData(self, node_cfg, node_data, node_iinfo, i_list,
706 node_results, disk_template):
707 """Compute global node data.
708
709 @param node_results: the basic node structures as filled from the config
710
711 """
712 #TODO(dynmem): compute the right data on MAX and MIN memory
713 # make a copy of the current dict
714 node_results = dict(node_results)
715 for nuuid, nresult in node_data.items():
716 ninfo = node_cfg[nuuid]
717 assert ninfo.name in node_results, "Missing basic data for node %s" % \
718 ninfo.name
719
720 if not ninfo.offline:
721 nresult.Raise("Can't get data for node %s" % ninfo.name)
722 node_iinfo[nuuid].Raise("Can't get node instance info from node %s" %
723 ninfo.name)
724 (_, space_info, (hv_info, )) = nresult.payload
725
726 mem_free = self._GetAttributeFromHypervisorNodeData(hv_info, ninfo.name,
727 "memory_free")
728
729 (i_p_mem, i_p_up_mem, mem_free) = self._ComputeInstanceMemory(
730 i_list, node_iinfo, nuuid, mem_free)
731 (total_disk, free_disk, total_spindles, free_spindles) = \
732 self._ComputeStorageDataFromSpaceInfoByTemplate(
733 space_info, ninfo.name, disk_template)
734
735 # compute memory used by instances
736 pnr_dyn = {
737 "total_memory": self._GetAttributeFromHypervisorNodeData(
738 hv_info, ninfo.name, "memory_total"),
739 "reserved_memory": self._GetAttributeFromHypervisorNodeData(
740 hv_info, ninfo.name, "memory_dom0"),
741 "free_memory": mem_free,
742 "total_disk": total_disk,
743 "free_disk": free_disk,
744 "total_spindles": total_spindles,
745 "free_spindles": free_spindles,
746 "total_cpus": self._GetAttributeFromHypervisorNodeData(
747 hv_info, ninfo.name, "cpu_total"),
748 "reserved_cpus": self._GetAttributeFromHypervisorNodeData(
749 hv_info, ninfo.name, "cpu_dom0"),
750 "i_pri_memory": i_p_mem,
751 "i_pri_up_memory": i_p_up_mem,
752 }
753 pnr_dyn.update(node_results[ninfo.name])
754 node_results[ninfo.name] = pnr_dyn
755
756 return node_results
757
758 @staticmethod
759 def _ComputeInstanceData(cfg, cluster_info, i_list):
760 """Compute global instance data.
761
762 """
763 instance_data = {}
764 for iinfo, beinfo in i_list:
765 nic_data = []
766 for nic in iinfo.nics:
767 filled_params = cluster_info.SimpleFillNIC(nic.nicparams)
768 nic_dict = {
769 "mac": nic.mac,
770 "ip": nic.ip,
771 "mode": filled_params[constants.NIC_MODE],
772 "link": filled_params[constants.NIC_LINK],
773 }
774 if filled_params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
775 nic_dict["bridge"] = filled_params[constants.NIC_LINK]
776 nic_data.append(nic_dict)
777 inst_disks = cfg.GetInstanceDisks(iinfo.uuid)
778 inst_disktemplate = cfg.GetInstanceDiskTemplate(iinfo.uuid)
779 pir = {
780 "tags": list(iinfo.GetTags()),
781 "admin_state": iinfo.admin_state,
782 "vcpus": beinfo[constants.BE_VCPUS],
783 "memory": beinfo[constants.BE_MAXMEM],
784 "spindle_use": beinfo[constants.BE_SPINDLE_USE],
785 "os": iinfo.os,
786 "nodes": [cfg.GetNodeName(iinfo.primary_node)] +
787 cfg.GetNodeNames(
788 cfg.GetInstanceSecondaryNodes(iinfo.uuid)),
789 "nics": nic_data,
790 "disks": [{constants.IDISK_TYPE: dsk.dev_type,
791 constants.IDISK_SIZE: dsk.size,
792 constants.IDISK_MODE: dsk.mode,
793 constants.IDISK_SPINDLES: dsk.spindles}
794 for dsk in inst_disks],
795 "disk_template": inst_disktemplate,
796 "disks_active": iinfo.disks_active,
797 "hypervisor": iinfo.hypervisor,
798 }
799 pir["disk_space_total"] = gmi.ComputeDiskSize(pir["disks"])
800 instance_data[iinfo.name] = pir
801
802 return instance_data
803
804 def _BuildInputData(self, req):
805 """Build input data structures.
806
807 """
808 request = req.GetRequest(self.cfg)
809 disk_template = None
810 if request.get("disk_template") is not None:
811 disk_template = request["disk_template"]
812 elif isinstance(req, IAReqRelocate):
813 disk_template = self.cfg.GetInstanceDiskTemplate(self.req.inst_uuid)
814 self._ComputeClusterData(disk_template=disk_template)
815
816 request["type"] = req.MODE
817 self.in_data["request"] = request
818
819 self.in_text = serializer.Dump(self.in_data)
820 logging.debug("IAllocator request: %s", self.in_text)
821
822 def Run(self, name, validate=True, call_fn=None):
823 """Run an instance allocator and return the results.
824
825 """
826 if call_fn is None:
827 call_fn = self.rpc.call_iallocator_runner
828
829 ial_params = self.cfg.GetDefaultIAllocatorParameters()
830
831 for ial_param in self.req.GetExtraParams().items():
832 ial_params[ial_param[0]] = ial_param[1]
833
834 result = call_fn(self.cfg.GetMasterNode(), name, self.in_text, ial_params)
835 result.Raise("Failure while running the iallocator script")
836
837 self.out_text = result.payload
838 if validate:
839 self._ValidateResult()
840
841 def _ValidateResult(self):
842 """Process the allocator results.
843
844 This will process and if successful save the result in
845 self.out_data and the other parameters.
846
847 """
848 try:
849 rdict = serializer.Load(self.out_text)
850 except Exception, err:
851 raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
852
853 if not isinstance(rdict, dict):
854 raise errors.OpExecError("Can't parse iallocator results: not a dict")
855
856 # TODO: remove backwards compatiblity in later versions
857 if "nodes" in rdict and "result" not in rdict:
858 rdict["result"] = rdict["nodes"]
859 del rdict["nodes"]
860
861 for key in "success", "info", "result":
862 if key not in rdict:
863 raise errors.OpExecError("Can't parse iallocator results:"
864 " missing key '%s'" % key)
865 setattr(self, key, rdict[key])
866
867 self.req.ValidateResult(self, self.result)
868 self.out_data = rdict