Merge 'EvacNode' and 'NodeEvacMode'
[ganeti-local] / lib / masterd / iallocator.py
1 #
2 #
3
4 # Copyright (C) 2012, 2013 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
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    # pylint: disable=E1101
40    # Class '...' has no 'OP_ID' member
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)
62
63
64 class _AutoReqParam(outils.AutoSlots):
65   """Meta class for request definitions.
66
67   """
68   @classmethod
69   def _GetSlots(mcs, attrs):
70     """Extract the slots out of REQ_PARAMS.
71
72     """
73     params = attrs.setdefault("REQ_PARAMS", [])
74     return [slot for (slot, _) in params]
75
76
77 class IARequestBase(outils.ValidatedSlots):
78   """A generic IAllocator request object.
79
80   """
81   __metaclass__ = _AutoReqParam
82
83   MODE = NotImplemented
84   REQ_PARAMS = []
85   REQ_RESULT = NotImplemented
86
87   def __init__(self, **kwargs):
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
100   def Validate(self):
101     """Validates all parameters of the request.
102
103     """
104     assert self.MODE in constants.VALID_IALLOCATOR_MODES
105
106     for (param, validator) in self.REQ_PARAMS:
107       if not hasattr(self, param):
108         raise errors.OpPrereqError("Request is missing '%s' parameter" % param,
109                                    errors.ECODE_INVAL)
110
111       value = getattr(self, param)
112       if not validator(value):
113         raise errors.OpPrereqError(("Request parameter '%s' has invalid"
114                                     " type %s/value %s") %
115                                     (param, type(value), value),
116                                     errors.ECODE_INVAL)
117
118   def GetRequest(self, cfg):
119     """Gets the request data dict.
120
121     @param cfg: The configuration instance
122
123     """
124     raise NotImplementedError
125
126   def ValidateResult(self, ia, result):
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
139
140 class IAReqInstanceAlloc(IARequestBase):
141   """An instance allocation request.
142
143   """
144   # pylint: disable=E1101
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
161   def RequiredNodes(self):
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
170   def GetRequest(self, cfg):
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
194   def ValidateResult(self, ia, result):
195     """Validates an single instance allocation request.
196
197     """
198     IARequestBase.ValidateResult(self, ia, result)
199
200     if ia.success and len(result) != self.RequiredNodes():
201       raise errors.ResultValidationError("iallocator returned invalid number"
202                                          " of nodes (%s), required %s" %
203                                          (len(result), self.RequiredNodes()))
204
205
206 class IAReqMultiInstanceAlloc(IARequestBase):
207   """An multi instance allocation request.
208
209   """
210   # pylint: disable=E1101
211   MODE = constants.IALLOCATOR_MODE_MULTI_ALLOC
212   REQ_PARAMS = [
213     ("instances", ht.TListOf(ht.TInstanceOf(IAReqInstanceAlloc))),
214     ]
215   _MASUCCESS = \
216     ht.TListOf(ht.TAnd(ht.TIsLength(2),
217                        ht.TItems([ht.TNonEmptyString,
218                                   ht.TListOf(ht.TNonEmptyString),
219                                   ])))
220   _MAFAILED = ht.TListOf(ht.TNonEmptyString)
221   REQ_RESULT = ht.TAnd(ht.TList, ht.TIsLength(2),
222                        ht.TItems([_MASUCCESS, _MAFAILED]))
223
224   def GetRequest(self, cfg):
225     return {
226       "instances": [iareq.GetRequest(cfg) for iareq in self.instances],
227       }
228
229
230 class IAReqRelocate(IARequestBase):
231   """A relocation request.
232
233   """
234   # pylint: disable=E1101
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
242   def GetRequest(self, cfg):
243     """Request an relocation of an instance
244
245     The checks for the completeness of the opcode must have already been
246     done.
247
248     """
249     instance = cfg.GetInstanceInfo(self.inst_uuid)
250     if instance is None:
251       raise errors.ProgrammerError("Unknown instance '%s' passed to"
252                                    " IAllocator" % self.inst_uuid)
253
254     if instance.disk_template not in constants.DTS_MIRRORED:
255       raise errors.OpPrereqError("Can't relocate non-mirrored instances",
256                                  errors.ECODE_INVAL)
257
258     if (instance.disk_template in constants.DTS_INT_MIRROR and
259         len(instance.secondary_nodes) != 1):
260       raise errors.OpPrereqError("Instance has not exactly one secondary node",
261                                  errors.ECODE_STATE)
262
263     disk_sizes = [{constants.IDISK_SIZE: disk.size} for disk in instance.disks]
264     disk_space = gmi.ComputeDiskSize(instance.disk_template, disk_sizes)
265
266     return {
267       "name": instance.name,
268       "disk_space_total": disk_space,
269       "required_nodes": 1,
270       "relocate_from": cfg.GetNodeNames(self.relocate_from_node_uuids),
271       }
272
273   def ValidateResult(self, ia, result):
274     """Validates the result of an relocation request.
275
276     """
277     IARequestBase.ValidateResult(self, ia, result)
278
279     node2group = dict((name, ndata["group"])
280                       for (name, ndata) in ia.in_data["nodes"].items())
281
282     fn = compat.partial(self._NodesToGroups, node2group,
283                         ia.in_data["nodegroups"])
284
285     instance = ia.cfg.GetInstanceInfo(self.inst_uuid)
286     request_groups = fn(ia.cfg.GetNodeNames(self.relocate_from_node_uuids) +
287                         ia.cfg.GetNodeNames([instance.primary_node]))
288     result_groups = fn(result + ia.cfg.GetNodeNames([instance.primary_node]))
289
290     if ia.success and not set(result_groups).issubset(request_groups):
291       raise errors.ResultValidationError("Groups of nodes returned by"
292                                          " iallocator (%s) differ from original"
293                                          " groups (%s)" %
294                                          (utils.CommaJoin(result_groups),
295                                           utils.CommaJoin(request_groups)))
296
297   @staticmethod
298   def _NodesToGroups(node2group, groups, nodes):
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         # Ignore unknown node
316         pass
317       else:
318         try:
319           group = groups[group_uuid]
320         except KeyError:
321           # Can't find group, let's use UUID
322           group_name = group_uuid
323         else:
324           group_name = group["name"]
325
326         result.add(group_name)
327
328     return sorted(result)
329
330
331 class IAReqNodeEvac(IARequestBase):
332   """A node evacuation request.
333
334   """
335   # pylint: disable=E1101
336   MODE = constants.IALLOCATOR_MODE_NODE_EVAC
337   REQ_PARAMS = [
338     ("instances", _STRING_LIST),
339     ("evac_mode", ht.TEvacMode),
340     ]
341   REQ_RESULT = _NEVAC_RESULT
342
343   def GetRequest(self, cfg):
344     """Get data for node-evacuate requests.
345
346     """
347     return {
348       "instances": self.instances,
349       "evac_mode": self.evac_mode,
350       }
351
352
353 class IAReqGroupChange(IARequestBase):
354   """A group change request.
355
356   """
357   # pylint: disable=E1101
358   MODE = constants.IALLOCATOR_MODE_CHG_GROUP
359   REQ_PARAMS = [
360     ("instances", _STRING_LIST),
361     ("target_groups", _STRING_LIST),
362     ]
363   REQ_RESULT = _NEVAC_RESULT
364
365   def GetRequest(self, cfg):
366     """Get data for node-evacuate requests.
367
368     """
369     return {
370       "instances": self.instances,
371       "target_groups": self.target_groups,
372       }
373
374
375 class IAllocator(object):
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   # pylint: disable=R0902
389   # lots of instance attributes
390
391   def __init__(self, cfg, rpc_runner, req):
392     self.cfg = cfg
393     self.rpc = rpc_runner
394     self.req = req
395     # init buffer variables
396     self.in_text = self.out_text = self.in_data = self.out_data = None
397     # init result fields
398     self.success = self.info = self.result = None
399
400     self._BuildInputData(req)
401
402   def _ComputeClusterDataNodeInfo(self, node_list, cluster_info,
403                                    hypervisor_name):
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
423   def _ComputeClusterData(self):
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     # cluster data
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     # node data
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
478   def _ComputeNodeGroupData(cfg):
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
495   def _ComputeBasicNodeData(cfg, node_cfg, node_whitelist):
496     """Compute global node data.
497
498     @rtype: dict
499     @returns: a dict of name: (node dict, node config)
500
501     """
502     # fill in static (config-based) values
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
522   def _GetAttributeFromHypervisorNodeData(hv_info, node_name, attr):
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
551   def _ComputeStorageDataFromSpaceInfo(space_info, node_name, has_lvm):
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     # TODO: replace this with proper storage reporting
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       # we didn't even ask the node for VG status, so use zeros
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
589   def _ComputeInstanceMemory(instance_list, node_instances_info, node_uuid,
590                              input_mem_free):
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
618   def _ComputeDynamicNodeData(self, node_cfg, node_data, node_iinfo, i_list,
619                               node_results, has_lvm):
620     """Compute global node data.
621
622     @param node_results: the basic node structures as filled from the config
623
624     """
625     #TODO(dynmem): compute the right data on MAX and MIN memory
626     # make a copy of the current dict
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 or ninfo.drained):
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         # compute memory used by instances
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
672   def _ComputeInstanceData(cfg, cluster_info, i_list):
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
714   def _BuildInputData(self, req):
715     """Build input data structures.
716
717     """
718     self._ComputeClusterData()
719
720     request = req.GetRequest(self.cfg)
721     request["type"] = req.MODE
722     self.in_data["request"] = request
723
724     self.in_text = serializer.Dump(self.in_data)
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
740   def _ValidateResult(self):
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     # TODO: remove backwards compatiblity in later versions
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