Migrate/Failover: allow less-than-max-mem op
[ganeti-local] / lib / cmdlib.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 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 master-side code."""
23
24 # pylint: disable=W0201,C0302
25
26 # W0201 since most LU attributes are defined in CheckPrereq or similar
27 # functions
28
29 # C0302: since we have waaaay too many lines in this module
30
31 import os
32 import os.path
33 import time
34 import re
35 import platform
36 import logging
37 import copy
38 import OpenSSL
39 import socket
40 import tempfile
41 import shutil
42 import itertools
43 import operator
44
45 from ganeti import ssh
46 from ganeti import utils
47 from ganeti import errors
48 from ganeti import hypervisor
49 from ganeti import locking
50 from ganeti import constants
51 from ganeti import objects
52 from ganeti import serializer
53 from ganeti import ssconf
54 from ganeti import uidpool
55 from ganeti import compat
56 from ganeti import masterd
57 from ganeti import netutils
58 from ganeti import query
59 from ganeti import qlang
60 from ganeti import opcodes
61 from ganeti import ht
62 from ganeti import rpc
63
64 import ganeti.masterd.instance # pylint: disable=W0611
65
66
67 #: Size of DRBD meta block device
68 DRBD_META_SIZE = 128
69
70 # States of instance
71 INSTANCE_UP = [constants.ADMINST_UP]
72 INSTANCE_DOWN = [constants.ADMINST_DOWN]
73 INSTANCE_OFFLINE = [constants.ADMINST_OFFLINE]
74 INSTANCE_ONLINE = [constants.ADMINST_DOWN, constants.ADMINST_UP]
75 INSTANCE_NOT_RUNNING = [constants.ADMINST_DOWN, constants.ADMINST_OFFLINE]
76
77
78 class ResultWithJobs:
79   """Data container for LU results with jobs.
80
81   Instances of this class returned from L{LogicalUnit.Exec} will be recognized
82   by L{mcpu.Processor._ProcessResult}. The latter will then submit the jobs
83   contained in the C{jobs} attribute and include the job IDs in the opcode
84   result.
85
86   """
87   def __init__(self, jobs, **kwargs):
88     """Initializes this class.
89
90     Additional return values can be specified as keyword arguments.
91
92     @type jobs: list of lists of L{opcode.OpCode}
93     @param jobs: A list of lists of opcode objects
94
95     """
96     self.jobs = jobs
97     self.other = kwargs
98
99
100 class LogicalUnit(object):
101   """Logical Unit base class.
102
103   Subclasses must follow these rules:
104     - implement ExpandNames
105     - implement CheckPrereq (except when tasklets are used)
106     - implement Exec (except when tasklets are used)
107     - implement BuildHooksEnv
108     - implement BuildHooksNodes
109     - redefine HPATH and HTYPE
110     - optionally redefine their run requirements:
111         REQ_BGL: the LU needs to hold the Big Ganeti Lock exclusively
112
113   Note that all commands require root permissions.
114
115   @ivar dry_run_result: the value (if any) that will be returned to the caller
116       in dry-run mode (signalled by opcode dry_run parameter)
117
118   """
119   HPATH = None
120   HTYPE = None
121   REQ_BGL = True
122
123   def __init__(self, processor, op, context, rpc_runner):
124     """Constructor for LogicalUnit.
125
126     This needs to be overridden in derived classes in order to check op
127     validity.
128
129     """
130     self.proc = processor
131     self.op = op
132     self.cfg = context.cfg
133     self.glm = context.glm
134     # readability alias
135     self.owned_locks = context.glm.list_owned
136     self.context = context
137     self.rpc = rpc_runner
138     # Dicts used to declare locking needs to mcpu
139     self.needed_locks = None
140     self.share_locks = dict.fromkeys(locking.LEVELS, 0)
141     self.add_locks = {}
142     self.remove_locks = {}
143     # Used to force good behavior when calling helper functions
144     self.recalculate_locks = {}
145     # logging
146     self.Log = processor.Log # pylint: disable=C0103
147     self.LogWarning = processor.LogWarning # pylint: disable=C0103
148     self.LogInfo = processor.LogInfo # pylint: disable=C0103
149     self.LogStep = processor.LogStep # pylint: disable=C0103
150     # support for dry-run
151     self.dry_run_result = None
152     # support for generic debug attribute
153     if (not hasattr(self.op, "debug_level") or
154         not isinstance(self.op.debug_level, int)):
155       self.op.debug_level = 0
156
157     # Tasklets
158     self.tasklets = None
159
160     # Validate opcode parameters and set defaults
161     self.op.Validate(True)
162
163     self.CheckArguments()
164
165   def CheckArguments(self):
166     """Check syntactic validity for the opcode arguments.
167
168     This method is for doing a simple syntactic check and ensure
169     validity of opcode parameters, without any cluster-related
170     checks. While the same can be accomplished in ExpandNames and/or
171     CheckPrereq, doing these separate is better because:
172
173       - ExpandNames is left as as purely a lock-related function
174       - CheckPrereq is run after we have acquired locks (and possible
175         waited for them)
176
177     The function is allowed to change the self.op attribute so that
178     later methods can no longer worry about missing parameters.
179
180     """
181     pass
182
183   def ExpandNames(self):
184     """Expand names for this LU.
185
186     This method is called before starting to execute the opcode, and it should
187     update all the parameters of the opcode to their canonical form (e.g. a
188     short node name must be fully expanded after this method has successfully
189     completed). This way locking, hooks, logging, etc. can work correctly.
190
191     LUs which implement this method must also populate the self.needed_locks
192     member, as a dict with lock levels as keys, and a list of needed lock names
193     as values. Rules:
194
195       - use an empty dict if you don't need any lock
196       - if you don't need any lock at a particular level omit that level
197       - don't put anything for the BGL level
198       - if you want all locks at a level use locking.ALL_SET as a value
199
200     If you need to share locks (rather than acquire them exclusively) at one
201     level you can modify self.share_locks, setting a true value (usually 1) for
202     that level. By default locks are not shared.
203
204     This function can also define a list of tasklets, which then will be
205     executed in order instead of the usual LU-level CheckPrereq and Exec
206     functions, if those are not defined by the LU.
207
208     Examples::
209
210       # Acquire all nodes and one instance
211       self.needed_locks = {
212         locking.LEVEL_NODE: locking.ALL_SET,
213         locking.LEVEL_INSTANCE: ['instance1.example.com'],
214       }
215       # Acquire just two nodes
216       self.needed_locks = {
217         locking.LEVEL_NODE: ['node1.example.com', 'node2.example.com'],
218       }
219       # Acquire no locks
220       self.needed_locks = {} # No, you can't leave it to the default value None
221
222     """
223     # The implementation of this method is mandatory only if the new LU is
224     # concurrent, so that old LUs don't need to be changed all at the same
225     # time.
226     if self.REQ_BGL:
227       self.needed_locks = {} # Exclusive LUs don't need locks.
228     else:
229       raise NotImplementedError
230
231   def DeclareLocks(self, level):
232     """Declare LU locking needs for a level
233
234     While most LUs can just declare their locking needs at ExpandNames time,
235     sometimes there's the need to calculate some locks after having acquired
236     the ones before. This function is called just before acquiring locks at a
237     particular level, but after acquiring the ones at lower levels, and permits
238     such calculations. It can be used to modify self.needed_locks, and by
239     default it does nothing.
240
241     This function is only called if you have something already set in
242     self.needed_locks for the level.
243
244     @param level: Locking level which is going to be locked
245     @type level: member of ganeti.locking.LEVELS
246
247     """
248
249   def CheckPrereq(self):
250     """Check prerequisites for this LU.
251
252     This method should check that the prerequisites for the execution
253     of this LU are fulfilled. It can do internode communication, but
254     it should be idempotent - no cluster or system changes are
255     allowed.
256
257     The method should raise errors.OpPrereqError in case something is
258     not fulfilled. Its return value is ignored.
259
260     This method should also update all the parameters of the opcode to
261     their canonical form if it hasn't been done by ExpandNames before.
262
263     """
264     if self.tasklets is not None:
265       for (idx, tl) in enumerate(self.tasklets):
266         logging.debug("Checking prerequisites for tasklet %s/%s",
267                       idx + 1, len(self.tasklets))
268         tl.CheckPrereq()
269     else:
270       pass
271
272   def Exec(self, feedback_fn):
273     """Execute the LU.
274
275     This method should implement the actual work. It should raise
276     errors.OpExecError for failures that are somewhat dealt with in
277     code, or expected.
278
279     """
280     if self.tasklets is not None:
281       for (idx, tl) in enumerate(self.tasklets):
282         logging.debug("Executing tasklet %s/%s", idx + 1, len(self.tasklets))
283         tl.Exec(feedback_fn)
284     else:
285       raise NotImplementedError
286
287   def BuildHooksEnv(self):
288     """Build hooks environment for this LU.
289
290     @rtype: dict
291     @return: Dictionary containing the environment that will be used for
292       running the hooks for this LU. The keys of the dict must not be prefixed
293       with "GANETI_"--that'll be added by the hooks runner. The hooks runner
294       will extend the environment with additional variables. If no environment
295       should be defined, an empty dictionary should be returned (not C{None}).
296     @note: If the C{HPATH} attribute of the LU class is C{None}, this function
297       will not be called.
298
299     """
300     raise NotImplementedError
301
302   def BuildHooksNodes(self):
303     """Build list of nodes to run LU's hooks.
304
305     @rtype: tuple; (list, list)
306     @return: Tuple containing a list of node names on which the hook
307       should run before the execution and a list of node names on which the
308       hook should run after the execution. No nodes should be returned as an
309       empty list (and not None).
310     @note: If the C{HPATH} attribute of the LU class is C{None}, this function
311       will not be called.
312
313     """
314     raise NotImplementedError
315
316   def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result):
317     """Notify the LU about the results of its hooks.
318
319     This method is called every time a hooks phase is executed, and notifies
320     the Logical Unit about the hooks' result. The LU can then use it to alter
321     its result based on the hooks.  By default the method does nothing and the
322     previous result is passed back unchanged but any LU can define it if it
323     wants to use the local cluster hook-scripts somehow.
324
325     @param phase: one of L{constants.HOOKS_PHASE_POST} or
326         L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
327     @param hook_results: the results of the multi-node hooks rpc call
328     @param feedback_fn: function used send feedback back to the caller
329     @param lu_result: the previous Exec result this LU had, or None
330         in the PRE phase
331     @return: the new Exec result, based on the previous result
332         and hook results
333
334     """
335     # API must be kept, thus we ignore the unused argument and could
336     # be a function warnings
337     # pylint: disable=W0613,R0201
338     return lu_result
339
340   def _ExpandAndLockInstance(self):
341     """Helper function to expand and lock an instance.
342
343     Many LUs that work on an instance take its name in self.op.instance_name
344     and need to expand it and then declare the expanded name for locking. This
345     function does it, and then updates self.op.instance_name to the expanded
346     name. It also initializes needed_locks as a dict, if this hasn't been done
347     before.
348
349     """
350     if self.needed_locks is None:
351       self.needed_locks = {}
352     else:
353       assert locking.LEVEL_INSTANCE not in self.needed_locks, \
354         "_ExpandAndLockInstance called with instance-level locks set"
355     self.op.instance_name = _ExpandInstanceName(self.cfg,
356                                                 self.op.instance_name)
357     self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
358
359   def _LockInstancesNodes(self, primary_only=False,
360                           level=locking.LEVEL_NODE):
361     """Helper function to declare instances' nodes for locking.
362
363     This function should be called after locking one or more instances to lock
364     their nodes. Its effect is populating self.needed_locks[locking.LEVEL_NODE]
365     with all primary or secondary nodes for instances already locked and
366     present in self.needed_locks[locking.LEVEL_INSTANCE].
367
368     It should be called from DeclareLocks, and for safety only works if
369     self.recalculate_locks[locking.LEVEL_NODE] is set.
370
371     In the future it may grow parameters to just lock some instance's nodes, or
372     to just lock primaries or secondary nodes, if needed.
373
374     If should be called in DeclareLocks in a way similar to::
375
376       if level == locking.LEVEL_NODE:
377         self._LockInstancesNodes()
378
379     @type primary_only: boolean
380     @param primary_only: only lock primary nodes of locked instances
381     @param level: Which lock level to use for locking nodes
382
383     """
384     assert level in self.recalculate_locks, \
385       "_LockInstancesNodes helper function called with no nodes to recalculate"
386
387     # TODO: check if we're really been called with the instance locks held
388
389     # For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the
390     # future we might want to have different behaviors depending on the value
391     # of self.recalculate_locks[locking.LEVEL_NODE]
392     wanted_nodes = []
393     locked_i = self.owned_locks(locking.LEVEL_INSTANCE)
394     for _, instance in self.cfg.GetMultiInstanceInfo(locked_i):
395       wanted_nodes.append(instance.primary_node)
396       if not primary_only:
397         wanted_nodes.extend(instance.secondary_nodes)
398
399     if self.recalculate_locks[level] == constants.LOCKS_REPLACE:
400       self.needed_locks[level] = wanted_nodes
401     elif self.recalculate_locks[level] == constants.LOCKS_APPEND:
402       self.needed_locks[level].extend(wanted_nodes)
403     else:
404       raise errors.ProgrammerError("Unknown recalculation mode")
405
406     del self.recalculate_locks[level]
407
408
409 class NoHooksLU(LogicalUnit): # pylint: disable=W0223
410   """Simple LU which runs no hooks.
411
412   This LU is intended as a parent for other LogicalUnits which will
413   run no hooks, in order to reduce duplicate code.
414
415   """
416   HPATH = None
417   HTYPE = None
418
419   def BuildHooksEnv(self):
420     """Empty BuildHooksEnv for NoHooksLu.
421
422     This just raises an error.
423
424     """
425     raise AssertionError("BuildHooksEnv called for NoHooksLUs")
426
427   def BuildHooksNodes(self):
428     """Empty BuildHooksNodes for NoHooksLU.
429
430     """
431     raise AssertionError("BuildHooksNodes called for NoHooksLU")
432
433
434 class Tasklet:
435   """Tasklet base class.
436
437   Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
438   they can mix legacy code with tasklets. Locking needs to be done in the LU,
439   tasklets know nothing about locks.
440
441   Subclasses must follow these rules:
442     - Implement CheckPrereq
443     - Implement Exec
444
445   """
446   def __init__(self, lu):
447     self.lu = lu
448
449     # Shortcuts
450     self.cfg = lu.cfg
451     self.rpc = lu.rpc
452
453   def CheckPrereq(self):
454     """Check prerequisites for this tasklets.
455
456     This method should check whether the prerequisites for the execution of
457     this tasklet are fulfilled. It can do internode communication, but it
458     should be idempotent - no cluster or system changes are allowed.
459
460     The method should raise errors.OpPrereqError in case something is not
461     fulfilled. Its return value is ignored.
462
463     This method should also update all parameters to their canonical form if it
464     hasn't been done before.
465
466     """
467     pass
468
469   def Exec(self, feedback_fn):
470     """Execute the tasklet.
471
472     This method should implement the actual work. It should raise
473     errors.OpExecError for failures that are somewhat dealt with in code, or
474     expected.
475
476     """
477     raise NotImplementedError
478
479
480 class _QueryBase:
481   """Base for query utility classes.
482
483   """
484   #: Attribute holding field definitions
485   FIELDS = None
486
487   def __init__(self, qfilter, fields, use_locking):
488     """Initializes this class.
489
490     """
491     self.use_locking = use_locking
492
493     self.query = query.Query(self.FIELDS, fields, qfilter=qfilter,
494                              namefield="name")
495     self.requested_data = self.query.RequestedData()
496     self.names = self.query.RequestedNames()
497
498     # Sort only if no names were requested
499     self.sort_by_name = not self.names
500
501     self.do_locking = None
502     self.wanted = None
503
504   def _GetNames(self, lu, all_names, lock_level):
505     """Helper function to determine names asked for in the query.
506
507     """
508     if self.do_locking:
509       names = lu.owned_locks(lock_level)
510     else:
511       names = all_names
512
513     if self.wanted == locking.ALL_SET:
514       assert not self.names
515       # caller didn't specify names, so ordering is not important
516       return utils.NiceSort(names)
517
518     # caller specified names and we must keep the same order
519     assert self.names
520     assert not self.do_locking or lu.glm.is_owned(lock_level)
521
522     missing = set(self.wanted).difference(names)
523     if missing:
524       raise errors.OpExecError("Some items were removed before retrieving"
525                                " their data: %s" % missing)
526
527     # Return expanded names
528     return self.wanted
529
530   def ExpandNames(self, lu):
531     """Expand names for this query.
532
533     See L{LogicalUnit.ExpandNames}.
534
535     """
536     raise NotImplementedError()
537
538   def DeclareLocks(self, lu, level):
539     """Declare locks for this query.
540
541     See L{LogicalUnit.DeclareLocks}.
542
543     """
544     raise NotImplementedError()
545
546   def _GetQueryData(self, lu):
547     """Collects all data for this query.
548
549     @return: Query data object
550
551     """
552     raise NotImplementedError()
553
554   def NewStyleQuery(self, lu):
555     """Collect data and execute query.
556
557     """
558     return query.GetQueryResponse(self.query, self._GetQueryData(lu),
559                                   sort_by_name=self.sort_by_name)
560
561   def OldStyleQuery(self, lu):
562     """Collect data and execute query.
563
564     """
565     return self.query.OldStyleQuery(self._GetQueryData(lu),
566                                     sort_by_name=self.sort_by_name)
567
568
569 def _ShareAll():
570   """Returns a dict declaring all lock levels shared.
571
572   """
573   return dict.fromkeys(locking.LEVELS, 1)
574
575
576 def _MakeLegacyNodeInfo(data):
577   """Formats the data returned by L{rpc.RpcRunner.call_node_info}.
578
579   Converts the data into a single dictionary. This is fine for most use cases,
580   but some require information from more than one volume group or hypervisor.
581
582   """
583   (bootid, (vg_info, ), (hv_info, )) = data
584
585   return utils.JoinDisjointDicts(utils.JoinDisjointDicts(vg_info, hv_info), {
586     "bootid": bootid,
587     })
588
589
590 def _CheckInstanceNodeGroups(cfg, instance_name, owned_groups):
591   """Checks if the owned node groups are still correct for an instance.
592
593   @type cfg: L{config.ConfigWriter}
594   @param cfg: The cluster configuration
595   @type instance_name: string
596   @param instance_name: Instance name
597   @type owned_groups: set or frozenset
598   @param owned_groups: List of currently owned node groups
599
600   """
601   inst_groups = cfg.GetInstanceNodeGroups(instance_name)
602
603   if not owned_groups.issuperset(inst_groups):
604     raise errors.OpPrereqError("Instance %s's node groups changed since"
605                                " locks were acquired, current groups are"
606                                " are '%s', owning groups '%s'; retry the"
607                                " operation" %
608                                (instance_name,
609                                 utils.CommaJoin(inst_groups),
610                                 utils.CommaJoin(owned_groups)),
611                                errors.ECODE_STATE)
612
613   return inst_groups
614
615
616 def _CheckNodeGroupInstances(cfg, group_uuid, owned_instances):
617   """Checks if the instances in a node group are still correct.
618
619   @type cfg: L{config.ConfigWriter}
620   @param cfg: The cluster configuration
621   @type group_uuid: string
622   @param group_uuid: Node group UUID
623   @type owned_instances: set or frozenset
624   @param owned_instances: List of currently owned instances
625
626   """
627   wanted_instances = cfg.GetNodeGroupInstances(group_uuid)
628   if owned_instances != wanted_instances:
629     raise errors.OpPrereqError("Instances in node group '%s' changed since"
630                                " locks were acquired, wanted '%s', have '%s';"
631                                " retry the operation" %
632                                (group_uuid,
633                                 utils.CommaJoin(wanted_instances),
634                                 utils.CommaJoin(owned_instances)),
635                                errors.ECODE_STATE)
636
637   return wanted_instances
638
639
640 def _SupportsOob(cfg, node):
641   """Tells if node supports OOB.
642
643   @type cfg: L{config.ConfigWriter}
644   @param cfg: The cluster configuration
645   @type node: L{objects.Node}
646   @param node: The node
647   @return: The OOB script if supported or an empty string otherwise
648
649   """
650   return cfg.GetNdParams(node)[constants.ND_OOB_PROGRAM]
651
652
653 def _GetWantedNodes(lu, nodes):
654   """Returns list of checked and expanded node names.
655
656   @type lu: L{LogicalUnit}
657   @param lu: the logical unit on whose behalf we execute
658   @type nodes: list
659   @param nodes: list of node names or None for all nodes
660   @rtype: list
661   @return: the list of nodes, sorted
662   @raise errors.ProgrammerError: if the nodes parameter is wrong type
663
664   """
665   if nodes:
666     return [_ExpandNodeName(lu.cfg, name) for name in nodes]
667
668   return utils.NiceSort(lu.cfg.GetNodeList())
669
670
671 def _GetWantedInstances(lu, instances):
672   """Returns list of checked and expanded instance names.
673
674   @type lu: L{LogicalUnit}
675   @param lu: the logical unit on whose behalf we execute
676   @type instances: list
677   @param instances: list of instance names or None for all instances
678   @rtype: list
679   @return: the list of instances, sorted
680   @raise errors.OpPrereqError: if the instances parameter is wrong type
681   @raise errors.OpPrereqError: if any of the passed instances is not found
682
683   """
684   if instances:
685     wanted = [_ExpandInstanceName(lu.cfg, name) for name in instances]
686   else:
687     wanted = utils.NiceSort(lu.cfg.GetInstanceList())
688   return wanted
689
690
691 def _GetUpdatedParams(old_params, update_dict,
692                       use_default=True, use_none=False):
693   """Return the new version of a parameter dictionary.
694
695   @type old_params: dict
696   @param old_params: old parameters
697   @type update_dict: dict
698   @param update_dict: dict containing new parameter values, or
699       constants.VALUE_DEFAULT to reset the parameter to its default
700       value
701   @param use_default: boolean
702   @type use_default: whether to recognise L{constants.VALUE_DEFAULT}
703       values as 'to be deleted' values
704   @param use_none: boolean
705   @type use_none: whether to recognise C{None} values as 'to be
706       deleted' values
707   @rtype: dict
708   @return: the new parameter dictionary
709
710   """
711   params_copy = copy.deepcopy(old_params)
712   for key, val in update_dict.iteritems():
713     if ((use_default and val == constants.VALUE_DEFAULT) or
714         (use_none and val is None)):
715       try:
716         del params_copy[key]
717       except KeyError:
718         pass
719     else:
720       params_copy[key] = val
721   return params_copy
722
723
724 def _GetUpdatedIPolicy(old_ipolicy, new_ipolicy, group_policy=False):
725   """Return the new version of a instance policy.
726
727   @param group_policy: whether this policy applies to a group and thus
728     we should support removal of policy entries
729
730   """
731   use_none = use_default = group_policy
732   ipolicy = copy.deepcopy(old_ipolicy)
733   for key, value in new_ipolicy.items():
734     if key not in constants.IPOLICY_ALL_KEYS:
735       raise errors.OpPrereqError("Invalid key in new ipolicy: %s" % key,
736                                  errors.ECODE_INVAL)
737     if key in constants.IPOLICY_ISPECS:
738       utils.ForceDictType(value, constants.ISPECS_PARAMETER_TYPES)
739       ipolicy[key] = _GetUpdatedParams(old_ipolicy.get(key, {}), value,
740                                        use_none=use_none,
741                                        use_default=use_default)
742     else:
743       if not value or value == [constants.VALUE_DEFAULT]:
744         if group_policy:
745           del ipolicy[key]
746         else:
747           raise errors.OpPrereqError("Can't unset ipolicy attribute '%s'"
748                                      " on the cluster'" % key,
749                                      errors.ECODE_INVAL)
750       else:
751         if key in constants.IPOLICY_PARAMETERS:
752           # FIXME: we assume all such values are float
753           try:
754             ipolicy[key] = float(value)
755           except (TypeError, ValueError), err:
756             raise errors.OpPrereqError("Invalid value for attribute"
757                                        " '%s': '%s', error: %s" %
758                                        (key, value, err), errors.ECODE_INVAL)
759         else:
760           # FIXME: we assume all others are lists; this should be redone
761           # in a nicer way
762           ipolicy[key] = list(value)
763   try:
764     objects.InstancePolicy.CheckParameterSyntax(ipolicy)
765   except errors.ConfigurationError, err:
766     raise errors.OpPrereqError("Invalid instance policy: %s" % err,
767                                errors.ECODE_INVAL)
768   return ipolicy
769
770
771 def _UpdateAndVerifySubDict(base, updates, type_check):
772   """Updates and verifies a dict with sub dicts of the same type.
773
774   @param base: The dict with the old data
775   @param updates: The dict with the new data
776   @param type_check: Dict suitable to ForceDictType to verify correct types
777   @returns: A new dict with updated and verified values
778
779   """
780   def fn(old, value):
781     new = _GetUpdatedParams(old, value)
782     utils.ForceDictType(new, type_check)
783     return new
784
785   ret = copy.deepcopy(base)
786   ret.update(dict((key, fn(base.get(key, {}), value))
787                   for key, value in updates.items()))
788   return ret
789
790
791 def _MergeAndVerifyHvState(op_input, obj_input):
792   """Combines the hv state from an opcode with the one of the object
793
794   @param op_input: The input dict from the opcode
795   @param obj_input: The input dict from the objects
796   @return: The verified and updated dict
797
798   """
799   if op_input:
800     invalid_hvs = set(op_input) - constants.HYPER_TYPES
801     if invalid_hvs:
802       raise errors.OpPrereqError("Invalid hypervisor(s) in hypervisor state:"
803                                  " %s" % utils.CommaJoin(invalid_hvs),
804                                  errors.ECODE_INVAL)
805     if obj_input is None:
806       obj_input = {}
807     type_check = constants.HVSTS_PARAMETER_TYPES
808     return _UpdateAndVerifySubDict(obj_input, op_input, type_check)
809
810   return None
811
812
813 def _MergeAndVerifyDiskState(op_input, obj_input):
814   """Combines the disk state from an opcode with the one of the object
815
816   @param op_input: The input dict from the opcode
817   @param obj_input: The input dict from the objects
818   @return: The verified and updated dict
819   """
820   if op_input:
821     invalid_dst = set(op_input) - constants.DS_VALID_TYPES
822     if invalid_dst:
823       raise errors.OpPrereqError("Invalid storage type(s) in disk state: %s" %
824                                  utils.CommaJoin(invalid_dst),
825                                  errors.ECODE_INVAL)
826     type_check = constants.DSS_PARAMETER_TYPES
827     if obj_input is None:
828       obj_input = {}
829     return dict((key, _UpdateAndVerifySubDict(obj_input.get(key, {}), value,
830                                               type_check))
831                 for key, value in op_input.items())
832
833   return None
834
835
836 def _ReleaseLocks(lu, level, names=None, keep=None):
837   """Releases locks owned by an LU.
838
839   @type lu: L{LogicalUnit}
840   @param level: Lock level
841   @type names: list or None
842   @param names: Names of locks to release
843   @type keep: list or None
844   @param keep: Names of locks to retain
845
846   """
847   assert not (keep is not None and names is not None), \
848          "Only one of the 'names' and the 'keep' parameters can be given"
849
850   if names is not None:
851     should_release = names.__contains__
852   elif keep:
853     should_release = lambda name: name not in keep
854   else:
855     should_release = None
856
857   owned = lu.owned_locks(level)
858   if not owned:
859     # Not owning any lock at this level, do nothing
860     pass
861
862   elif should_release:
863     retain = []
864     release = []
865
866     # Determine which locks to release
867     for name in owned:
868       if should_release(name):
869         release.append(name)
870       else:
871         retain.append(name)
872
873     assert len(lu.owned_locks(level)) == (len(retain) + len(release))
874
875     # Release just some locks
876     lu.glm.release(level, names=release)
877
878     assert frozenset(lu.owned_locks(level)) == frozenset(retain)
879   else:
880     # Release everything
881     lu.glm.release(level)
882
883     assert not lu.glm.is_owned(level), "No locks should be owned"
884
885
886 def _MapInstanceDisksToNodes(instances):
887   """Creates a map from (node, volume) to instance name.
888
889   @type instances: list of L{objects.Instance}
890   @rtype: dict; tuple of (node name, volume name) as key, instance name as value
891
892   """
893   return dict(((node, vol), inst.name)
894               for inst in instances
895               for (node, vols) in inst.MapLVsByNode().items()
896               for vol in vols)
897
898
899 def _RunPostHook(lu, node_name):
900   """Runs the post-hook for an opcode on a single node.
901
902   """
903   hm = lu.proc.BuildHooksManager(lu)
904   try:
905     hm.RunPhase(constants.HOOKS_PHASE_POST, nodes=[node_name])
906   except:
907     # pylint: disable=W0702
908     lu.LogWarning("Errors occurred running hooks on %s" % node_name)
909
910
911 def _CheckOutputFields(static, dynamic, selected):
912   """Checks whether all selected fields are valid.
913
914   @type static: L{utils.FieldSet}
915   @param static: static fields set
916   @type dynamic: L{utils.FieldSet}
917   @param dynamic: dynamic fields set
918
919   """
920   f = utils.FieldSet()
921   f.Extend(static)
922   f.Extend(dynamic)
923
924   delta = f.NonMatching(selected)
925   if delta:
926     raise errors.OpPrereqError("Unknown output fields selected: %s"
927                                % ",".join(delta), errors.ECODE_INVAL)
928
929
930 def _CheckGlobalHvParams(params):
931   """Validates that given hypervisor params are not global ones.
932
933   This will ensure that instances don't get customised versions of
934   global params.
935
936   """
937   used_globals = constants.HVC_GLOBALS.intersection(params)
938   if used_globals:
939     msg = ("The following hypervisor parameters are global and cannot"
940            " be customized at instance level, please modify them at"
941            " cluster level: %s" % utils.CommaJoin(used_globals))
942     raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
943
944
945 def _CheckNodeOnline(lu, node, msg=None):
946   """Ensure that a given node is online.
947
948   @param lu: the LU on behalf of which we make the check
949   @param node: the node to check
950   @param msg: if passed, should be a message to replace the default one
951   @raise errors.OpPrereqError: if the node is offline
952
953   """
954   if msg is None:
955     msg = "Can't use offline node"
956   if lu.cfg.GetNodeInfo(node).offline:
957     raise errors.OpPrereqError("%s: %s" % (msg, node), errors.ECODE_STATE)
958
959
960 def _CheckNodeNotDrained(lu, node):
961   """Ensure that a given node is not drained.
962
963   @param lu: the LU on behalf of which we make the check
964   @param node: the node to check
965   @raise errors.OpPrereqError: if the node is drained
966
967   """
968   if lu.cfg.GetNodeInfo(node).drained:
969     raise errors.OpPrereqError("Can't use drained node %s" % node,
970                                errors.ECODE_STATE)
971
972
973 def _CheckNodeVmCapable(lu, node):
974   """Ensure that a given node is vm capable.
975
976   @param lu: the LU on behalf of which we make the check
977   @param node: the node to check
978   @raise errors.OpPrereqError: if the node is not vm capable
979
980   """
981   if not lu.cfg.GetNodeInfo(node).vm_capable:
982     raise errors.OpPrereqError("Can't use non-vm_capable node %s" % node,
983                                errors.ECODE_STATE)
984
985
986 def _CheckNodeHasOS(lu, node, os_name, force_variant):
987   """Ensure that a node supports a given OS.
988
989   @param lu: the LU on behalf of which we make the check
990   @param node: the node to check
991   @param os_name: the OS to query about
992   @param force_variant: whether to ignore variant errors
993   @raise errors.OpPrereqError: if the node is not supporting the OS
994
995   """
996   result = lu.rpc.call_os_get(node, os_name)
997   result.Raise("OS '%s' not in supported OS list for node %s" %
998                (os_name, node),
999                prereq=True, ecode=errors.ECODE_INVAL)
1000   if not force_variant:
1001     _CheckOSVariant(result.payload, os_name)
1002
1003
1004 def _CheckNodeHasSecondaryIP(lu, node, secondary_ip, prereq):
1005   """Ensure that a node has the given secondary ip.
1006
1007   @type lu: L{LogicalUnit}
1008   @param lu: the LU on behalf of which we make the check
1009   @type node: string
1010   @param node: the node to check
1011   @type secondary_ip: string
1012   @param secondary_ip: the ip to check
1013   @type prereq: boolean
1014   @param prereq: whether to throw a prerequisite or an execute error
1015   @raise errors.OpPrereqError: if the node doesn't have the ip, and prereq=True
1016   @raise errors.OpExecError: if the node doesn't have the ip, and prereq=False
1017
1018   """
1019   result = lu.rpc.call_node_has_ip_address(node, secondary_ip)
1020   result.Raise("Failure checking secondary ip on node %s" % node,
1021                prereq=prereq, ecode=errors.ECODE_ENVIRON)
1022   if not result.payload:
1023     msg = ("Node claims it doesn't have the secondary ip you gave (%s),"
1024            " please fix and re-run this command" % secondary_ip)
1025     if prereq:
1026       raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
1027     else:
1028       raise errors.OpExecError(msg)
1029
1030
1031 def _GetClusterDomainSecret():
1032   """Reads the cluster domain secret.
1033
1034   """
1035   return utils.ReadOneLineFile(constants.CLUSTER_DOMAIN_SECRET_FILE,
1036                                strict=True)
1037
1038
1039 def _CheckInstanceState(lu, instance, req_states, msg=None):
1040   """Ensure that an instance is in one of the required states.
1041
1042   @param lu: the LU on behalf of which we make the check
1043   @param instance: the instance to check
1044   @param msg: if passed, should be a message to replace the default one
1045   @raise errors.OpPrereqError: if the instance is not in the required state
1046
1047   """
1048   if msg is None:
1049     msg = "can't use instance from outside %s states" % ", ".join(req_states)
1050   if instance.admin_state not in req_states:
1051     raise errors.OpPrereqError("Instance '%s' is marked to be %s, %s" %
1052                                (instance.name, instance.admin_state, msg),
1053                                errors.ECODE_STATE)
1054
1055   if constants.ADMINST_UP not in req_states:
1056     pnode = instance.primary_node
1057     ins_l = lu.rpc.call_instance_list([pnode], [instance.hypervisor])[pnode]
1058     ins_l.Raise("Can't contact node %s for instance information" % pnode,
1059                 prereq=True, ecode=errors.ECODE_ENVIRON)
1060
1061     if instance.name in ins_l.payload:
1062       raise errors.OpPrereqError("Instance %s is running, %s" %
1063                                  (instance.name, msg), errors.ECODE_STATE)
1064
1065
1066 def _ComputeMinMaxSpec(name, ipolicy, value):
1067   """Computes if value is in the desired range.
1068
1069   @param name: name of the parameter for which we perform the check
1070   @param ipolicy: dictionary containing min, max and std values
1071   @param value: actual value that we want to use
1072   @return: None or element not meeting the criteria
1073
1074
1075   """
1076   if value in [None, constants.VALUE_AUTO]:
1077     return None
1078   max_v = ipolicy[constants.ISPECS_MAX].get(name, value)
1079   min_v = ipolicy[constants.ISPECS_MIN].get(name, value)
1080   if value > max_v or min_v > value:
1081     return ("%s value %s is not in range [%s, %s]" %
1082             (name, value, min_v, max_v))
1083   return None
1084
1085
1086 def _ComputeIPolicySpecViolation(ipolicy, mem_size, cpu_count, disk_count,
1087                                  nic_count, disk_sizes,
1088                                  _compute_fn=_ComputeMinMaxSpec):
1089   """Verifies ipolicy against provided specs.
1090
1091   @type ipolicy: dict
1092   @param ipolicy: The ipolicy
1093   @type mem_size: int
1094   @param mem_size: The memory size
1095   @type cpu_count: int
1096   @param cpu_count: Used cpu cores
1097   @type disk_count: int
1098   @param disk_count: Number of disks used
1099   @type nic_count: int
1100   @param nic_count: Number of nics used
1101   @type disk_sizes: list of ints
1102   @param disk_sizes: Disk sizes of used disk (len must match C{disk_count})
1103   @param _compute_fn: The compute function (unittest only)
1104   @return: A list of violations, or an empty list of no violations are found
1105
1106   """
1107   assert disk_count == len(disk_sizes)
1108
1109   test_settings = [
1110     (constants.ISPEC_MEM_SIZE, mem_size),
1111     (constants.ISPEC_CPU_COUNT, cpu_count),
1112     (constants.ISPEC_DISK_COUNT, disk_count),
1113     (constants.ISPEC_NIC_COUNT, nic_count),
1114     ] + map((lambda d: (constants.ISPEC_DISK_SIZE, d)), disk_sizes)
1115
1116   return filter(None,
1117                 (_compute_fn(name, ipolicy, value)
1118                  for (name, value) in test_settings))
1119
1120
1121 def _ComputeIPolicyInstanceViolation(ipolicy, instance,
1122                                      _compute_fn=_ComputeIPolicySpecViolation):
1123   """Compute if instance meets the specs of ipolicy.
1124
1125   @type ipolicy: dict
1126   @param ipolicy: The ipolicy to verify against
1127   @type instance: L{objects.Instance}
1128   @param instance: The instance to verify
1129   @param _compute_fn: The function to verify ipolicy (unittest only)
1130   @see: L{_ComputeIPolicySpecViolation}
1131
1132   """
1133   mem_size = instance.beparams.get(constants.BE_MAXMEM, None)
1134   cpu_count = instance.beparams.get(constants.BE_VCPUS, None)
1135   disk_count = len(instance.disks)
1136   disk_sizes = [disk.size for disk in instance.disks]
1137   nic_count = len(instance.nics)
1138
1139   return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
1140                      disk_sizes)
1141
1142
1143 def _ComputeIPolicyInstanceSpecViolation(ipolicy, instance_spec,
1144     _compute_fn=_ComputeIPolicySpecViolation):
1145   """Compute if instance specs meets the specs of ipolicy.
1146
1147   @type ipolicy: dict
1148   @param ipolicy: The ipolicy to verify against
1149   @param instance_spec: dict
1150   @param instance_spec: The instance spec to verify
1151   @param _compute_fn: The function to verify ipolicy (unittest only)
1152   @see: L{_ComputeIPolicySpecViolation}
1153
1154   """
1155   mem_size = instance_spec.get(constants.ISPEC_MEM_SIZE, None)
1156   cpu_count = instance_spec.get(constants.ISPEC_CPU_COUNT, None)
1157   disk_count = instance_spec.get(constants.ISPEC_DISK_COUNT, 0)
1158   disk_sizes = instance_spec.get(constants.ISPEC_DISK_SIZE, [])
1159   nic_count = instance_spec.get(constants.ISPEC_NIC_COUNT, 0)
1160
1161   return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
1162                      disk_sizes)
1163
1164
1165 def _ComputeIPolicyNodeViolation(ipolicy, instance, current_group,
1166                                  target_group,
1167                                  _compute_fn=_ComputeIPolicyInstanceViolation):
1168   """Compute if instance meets the specs of the new target group.
1169
1170   @param ipolicy: The ipolicy to verify
1171   @param instance: The instance object to verify
1172   @param current_group: The current group of the instance
1173   @param target_group: The new group of the instance
1174   @param _compute_fn: The function to verify ipolicy (unittest only)
1175   @see: L{_ComputeIPolicySpecViolation}
1176
1177   """
1178   if current_group == target_group:
1179     return []
1180   else:
1181     return _compute_fn(ipolicy, instance)
1182
1183
1184 def _CheckTargetNodeIPolicy(lu, ipolicy, instance, node, ignore=False,
1185                             _compute_fn=_ComputeIPolicyNodeViolation):
1186   """Checks that the target node is correct in terms of instance policy.
1187
1188   @param ipolicy: The ipolicy to verify
1189   @param instance: The instance object to verify
1190   @param node: The new node to relocate
1191   @param ignore: Ignore violations of the ipolicy
1192   @param _compute_fn: The function to verify ipolicy (unittest only)
1193   @see: L{_ComputeIPolicySpecViolation}
1194
1195   """
1196   primary_node = lu.cfg.GetNodeInfo(instance.primary_node)
1197   res = _compute_fn(ipolicy, instance, primary_node.group, node.group)
1198
1199   if res:
1200     msg = ("Instance does not meet target node group's (%s) instance"
1201            " policy: %s") % (node.group, utils.CommaJoin(res))
1202     if ignore:
1203       lu.LogWarning(msg)
1204     else:
1205       raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
1206
1207
1208 def _ComputeNewInstanceViolations(old_ipolicy, new_ipolicy, instances):
1209   """Computes a set of any instances that would violate the new ipolicy.
1210
1211   @param old_ipolicy: The current (still in-place) ipolicy
1212   @param new_ipolicy: The new (to become) ipolicy
1213   @param instances: List of instances to verify
1214   @return: A list of instances which violates the new ipolicy but did not before
1215
1216   """
1217   return (_ComputeViolatingInstances(old_ipolicy, instances) -
1218           _ComputeViolatingInstances(new_ipolicy, instances))
1219
1220
1221 def _ExpandItemName(fn, name, kind):
1222   """Expand an item name.
1223
1224   @param fn: the function to use for expansion
1225   @param name: requested item name
1226   @param kind: text description ('Node' or 'Instance')
1227   @return: the resolved (full) name
1228   @raise errors.OpPrereqError: if the item is not found
1229
1230   """
1231   full_name = fn(name)
1232   if full_name is None:
1233     raise errors.OpPrereqError("%s '%s' not known" % (kind, name),
1234                                errors.ECODE_NOENT)
1235   return full_name
1236
1237
1238 def _ExpandNodeName(cfg, name):
1239   """Wrapper over L{_ExpandItemName} for nodes."""
1240   return _ExpandItemName(cfg.ExpandNodeName, name, "Node")
1241
1242
1243 def _ExpandInstanceName(cfg, name):
1244   """Wrapper over L{_ExpandItemName} for instance."""
1245   return _ExpandItemName(cfg.ExpandInstanceName, name, "Instance")
1246
1247
1248 def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
1249                           minmem, maxmem, vcpus, nics, disk_template, disks,
1250                           bep, hvp, hypervisor_name, tags):
1251   """Builds instance related env variables for hooks
1252
1253   This builds the hook environment from individual variables.
1254
1255   @type name: string
1256   @param name: the name of the instance
1257   @type primary_node: string
1258   @param primary_node: the name of the instance's primary node
1259   @type secondary_nodes: list
1260   @param secondary_nodes: list of secondary nodes as strings
1261   @type os_type: string
1262   @param os_type: the name of the instance's OS
1263   @type status: string
1264   @param status: the desired status of the instance
1265   @type minmem: string
1266   @param minmem: the minimum memory size of the instance
1267   @type maxmem: string
1268   @param maxmem: the maximum memory size of the instance
1269   @type vcpus: string
1270   @param vcpus: the count of VCPUs the instance has
1271   @type nics: list
1272   @param nics: list of tuples (ip, mac, mode, link) representing
1273       the NICs the instance has
1274   @type disk_template: string
1275   @param disk_template: the disk template of the instance
1276   @type disks: list
1277   @param disks: the list of (size, mode) pairs
1278   @type bep: dict
1279   @param bep: the backend parameters for the instance
1280   @type hvp: dict
1281   @param hvp: the hypervisor parameters for the instance
1282   @type hypervisor_name: string
1283   @param hypervisor_name: the hypervisor for the instance
1284   @type tags: list
1285   @param tags: list of instance tags as strings
1286   @rtype: dict
1287   @return: the hook environment for this instance
1288
1289   """
1290   env = {
1291     "OP_TARGET": name,
1292     "INSTANCE_NAME": name,
1293     "INSTANCE_PRIMARY": primary_node,
1294     "INSTANCE_SECONDARIES": " ".join(secondary_nodes),
1295     "INSTANCE_OS_TYPE": os_type,
1296     "INSTANCE_STATUS": status,
1297     "INSTANCE_MINMEM": minmem,
1298     "INSTANCE_MAXMEM": maxmem,
1299     # TODO(2.7) remove deprecated "memory" value
1300     "INSTANCE_MEMORY": maxmem,
1301     "INSTANCE_VCPUS": vcpus,
1302     "INSTANCE_DISK_TEMPLATE": disk_template,
1303     "INSTANCE_HYPERVISOR": hypervisor_name,
1304   }
1305   if nics:
1306     nic_count = len(nics)
1307     for idx, (ip, mac, mode, link) in enumerate(nics):
1308       if ip is None:
1309         ip = ""
1310       env["INSTANCE_NIC%d_IP" % idx] = ip
1311       env["INSTANCE_NIC%d_MAC" % idx] = mac
1312       env["INSTANCE_NIC%d_MODE" % idx] = mode
1313       env["INSTANCE_NIC%d_LINK" % idx] = link
1314       if mode == constants.NIC_MODE_BRIDGED:
1315         env["INSTANCE_NIC%d_BRIDGE" % idx] = link
1316   else:
1317     nic_count = 0
1318
1319   env["INSTANCE_NIC_COUNT"] = nic_count
1320
1321   if disks:
1322     disk_count = len(disks)
1323     for idx, (size, mode) in enumerate(disks):
1324       env["INSTANCE_DISK%d_SIZE" % idx] = size
1325       env["INSTANCE_DISK%d_MODE" % idx] = mode
1326   else:
1327     disk_count = 0
1328
1329   env["INSTANCE_DISK_COUNT"] = disk_count
1330
1331   if not tags:
1332     tags = []
1333
1334   env["INSTANCE_TAGS"] = " ".join(tags)
1335
1336   for source, kind in [(bep, "BE"), (hvp, "HV")]:
1337     for key, value in source.items():
1338       env["INSTANCE_%s_%s" % (kind, key)] = value
1339
1340   return env
1341
1342
1343 def _NICListToTuple(lu, nics):
1344   """Build a list of nic information tuples.
1345
1346   This list is suitable to be passed to _BuildInstanceHookEnv or as a return
1347   value in LUInstanceQueryData.
1348
1349   @type lu:  L{LogicalUnit}
1350   @param lu: the logical unit on whose behalf we execute
1351   @type nics: list of L{objects.NIC}
1352   @param nics: list of nics to convert to hooks tuples
1353
1354   """
1355   hooks_nics = []
1356   cluster = lu.cfg.GetClusterInfo()
1357   for nic in nics:
1358     ip = nic.ip
1359     mac = nic.mac
1360     filled_params = cluster.SimpleFillNIC(nic.nicparams)
1361     mode = filled_params[constants.NIC_MODE]
1362     link = filled_params[constants.NIC_LINK]
1363     hooks_nics.append((ip, mac, mode, link))
1364   return hooks_nics
1365
1366
1367 def _BuildInstanceHookEnvByObject(lu, instance, override=None):
1368   """Builds instance related env variables for hooks from an object.
1369
1370   @type lu: L{LogicalUnit}
1371   @param lu: the logical unit on whose behalf we execute
1372   @type instance: L{objects.Instance}
1373   @param instance: the instance for which we should build the
1374       environment
1375   @type override: dict
1376   @param override: dictionary with key/values that will override
1377       our values
1378   @rtype: dict
1379   @return: the hook environment dictionary
1380
1381   """
1382   cluster = lu.cfg.GetClusterInfo()
1383   bep = cluster.FillBE(instance)
1384   hvp = cluster.FillHV(instance)
1385   args = {
1386     "name": instance.name,
1387     "primary_node": instance.primary_node,
1388     "secondary_nodes": instance.secondary_nodes,
1389     "os_type": instance.os,
1390     "status": instance.admin_state,
1391     "maxmem": bep[constants.BE_MAXMEM],
1392     "minmem": bep[constants.BE_MINMEM],
1393     "vcpus": bep[constants.BE_VCPUS],
1394     "nics": _NICListToTuple(lu, instance.nics),
1395     "disk_template": instance.disk_template,
1396     "disks": [(disk.size, disk.mode) for disk in instance.disks],
1397     "bep": bep,
1398     "hvp": hvp,
1399     "hypervisor_name": instance.hypervisor,
1400     "tags": instance.tags,
1401   }
1402   if override:
1403     args.update(override)
1404   return _BuildInstanceHookEnv(**args) # pylint: disable=W0142
1405
1406
1407 def _AdjustCandidatePool(lu, exceptions):
1408   """Adjust the candidate pool after node operations.
1409
1410   """
1411   mod_list = lu.cfg.MaintainCandidatePool(exceptions)
1412   if mod_list:
1413     lu.LogInfo("Promoted nodes to master candidate role: %s",
1414                utils.CommaJoin(node.name for node in mod_list))
1415     for name in mod_list:
1416       lu.context.ReaddNode(name)
1417   mc_now, mc_max, _ = lu.cfg.GetMasterCandidateStats(exceptions)
1418   if mc_now > mc_max:
1419     lu.LogInfo("Note: more nodes are candidates (%d) than desired (%d)" %
1420                (mc_now, mc_max))
1421
1422
1423 def _DecideSelfPromotion(lu, exceptions=None):
1424   """Decide whether I should promote myself as a master candidate.
1425
1426   """
1427   cp_size = lu.cfg.GetClusterInfo().candidate_pool_size
1428   mc_now, mc_should, _ = lu.cfg.GetMasterCandidateStats(exceptions)
1429   # the new node will increase mc_max with one, so:
1430   mc_should = min(mc_should + 1, cp_size)
1431   return mc_now < mc_should
1432
1433
1434 def _CalculateGroupIPolicy(cluster, group):
1435   """Calculate instance policy for group.
1436
1437   """
1438   return cluster.SimpleFillIPolicy(group.ipolicy)
1439
1440
1441 def _ComputeViolatingInstances(ipolicy, instances):
1442   """Computes a set of instances who violates given ipolicy.
1443
1444   @param ipolicy: The ipolicy to verify
1445   @type instances: object.Instance
1446   @param instances: List of instances to verify
1447   @return: A frozenset of instance names violating the ipolicy
1448
1449   """
1450   return frozenset([inst.name for inst in instances
1451                     if _ComputeIPolicyInstanceViolation(ipolicy, inst)])
1452
1453
1454 def _CheckNicsBridgesExist(lu, target_nics, target_node):
1455   """Check that the brigdes needed by a list of nics exist.
1456
1457   """
1458   cluster = lu.cfg.GetClusterInfo()
1459   paramslist = [cluster.SimpleFillNIC(nic.nicparams) for nic in target_nics]
1460   brlist = [params[constants.NIC_LINK] for params in paramslist
1461             if params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED]
1462   if brlist:
1463     result = lu.rpc.call_bridges_exist(target_node, brlist)
1464     result.Raise("Error checking bridges on destination node '%s'" %
1465                  target_node, prereq=True, ecode=errors.ECODE_ENVIRON)
1466
1467
1468 def _CheckInstanceBridgesExist(lu, instance, node=None):
1469   """Check that the brigdes needed by an instance exist.
1470
1471   """
1472   if node is None:
1473     node = instance.primary_node
1474   _CheckNicsBridgesExist(lu, instance.nics, node)
1475
1476
1477 def _CheckOSVariant(os_obj, name):
1478   """Check whether an OS name conforms to the os variants specification.
1479
1480   @type os_obj: L{objects.OS}
1481   @param os_obj: OS object to check
1482   @type name: string
1483   @param name: OS name passed by the user, to check for validity
1484
1485   """
1486   variant = objects.OS.GetVariant(name)
1487   if not os_obj.supported_variants:
1488     if variant:
1489       raise errors.OpPrereqError("OS '%s' doesn't support variants ('%s'"
1490                                  " passed)" % (os_obj.name, variant),
1491                                  errors.ECODE_INVAL)
1492     return
1493   if not variant:
1494     raise errors.OpPrereqError("OS name must include a variant",
1495                                errors.ECODE_INVAL)
1496
1497   if variant not in os_obj.supported_variants:
1498     raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
1499
1500
1501 def _GetNodeInstancesInner(cfg, fn):
1502   return [i for i in cfg.GetAllInstancesInfo().values() if fn(i)]
1503
1504
1505 def _GetNodeInstances(cfg, node_name):
1506   """Returns a list of all primary and secondary instances on a node.
1507
1508   """
1509
1510   return _GetNodeInstancesInner(cfg, lambda inst: node_name in inst.all_nodes)
1511
1512
1513 def _GetNodePrimaryInstances(cfg, node_name):
1514   """Returns primary instances on a node.
1515
1516   """
1517   return _GetNodeInstancesInner(cfg,
1518                                 lambda inst: node_name == inst.primary_node)
1519
1520
1521 def _GetNodeSecondaryInstances(cfg, node_name):
1522   """Returns secondary instances on a node.
1523
1524   """
1525   return _GetNodeInstancesInner(cfg,
1526                                 lambda inst: node_name in inst.secondary_nodes)
1527
1528
1529 def _GetStorageTypeArgs(cfg, storage_type):
1530   """Returns the arguments for a storage type.
1531
1532   """
1533   # Special case for file storage
1534   if storage_type == constants.ST_FILE:
1535     # storage.FileStorage wants a list of storage directories
1536     return [[cfg.GetFileStorageDir(), cfg.GetSharedFileStorageDir()]]
1537
1538   return []
1539
1540
1541 def _FindFaultyInstanceDisks(cfg, rpc_runner, instance, node_name, prereq):
1542   faulty = []
1543
1544   for dev in instance.disks:
1545     cfg.SetDiskID(dev, node_name)
1546
1547   result = rpc_runner.call_blockdev_getmirrorstatus(node_name, instance.disks)
1548   result.Raise("Failed to get disk status from node %s" % node_name,
1549                prereq=prereq, ecode=errors.ECODE_ENVIRON)
1550
1551   for idx, bdev_status in enumerate(result.payload):
1552     if bdev_status and bdev_status.ldisk_status == constants.LDS_FAULTY:
1553       faulty.append(idx)
1554
1555   return faulty
1556
1557
1558 def _CheckIAllocatorOrNode(lu, iallocator_slot, node_slot):
1559   """Check the sanity of iallocator and node arguments and use the
1560   cluster-wide iallocator if appropriate.
1561
1562   Check that at most one of (iallocator, node) is specified. If none is
1563   specified, then the LU's opcode's iallocator slot is filled with the
1564   cluster-wide default iallocator.
1565
1566   @type iallocator_slot: string
1567   @param iallocator_slot: the name of the opcode iallocator slot
1568   @type node_slot: string
1569   @param node_slot: the name of the opcode target node slot
1570
1571   """
1572   node = getattr(lu.op, node_slot, None)
1573   iallocator = getattr(lu.op, iallocator_slot, None)
1574
1575   if node is not None and iallocator is not None:
1576     raise errors.OpPrereqError("Do not specify both, iallocator and node",
1577                                errors.ECODE_INVAL)
1578   elif node is None and iallocator is None:
1579     default_iallocator = lu.cfg.GetDefaultIAllocator()
1580     if default_iallocator:
1581       setattr(lu.op, iallocator_slot, default_iallocator)
1582     else:
1583       raise errors.OpPrereqError("No iallocator or node given and no"
1584                                  " cluster-wide default iallocator found;"
1585                                  " please specify either an iallocator or a"
1586                                  " node, or set a cluster-wide default"
1587                                  " iallocator")
1588
1589
1590 def _GetDefaultIAllocator(cfg, iallocator):
1591   """Decides on which iallocator to use.
1592
1593   @type cfg: L{config.ConfigWriter}
1594   @param cfg: Cluster configuration object
1595   @type iallocator: string or None
1596   @param iallocator: Iallocator specified in opcode
1597   @rtype: string
1598   @return: Iallocator name
1599
1600   """
1601   if not iallocator:
1602     # Use default iallocator
1603     iallocator = cfg.GetDefaultIAllocator()
1604
1605   if not iallocator:
1606     raise errors.OpPrereqError("No iallocator was specified, neither in the"
1607                                " opcode nor as a cluster-wide default",
1608                                errors.ECODE_INVAL)
1609
1610   return iallocator
1611
1612
1613 class LUClusterPostInit(LogicalUnit):
1614   """Logical unit for running hooks after cluster initialization.
1615
1616   """
1617   HPATH = "cluster-init"
1618   HTYPE = constants.HTYPE_CLUSTER
1619
1620   def BuildHooksEnv(self):
1621     """Build hooks env.
1622
1623     """
1624     return {
1625       "OP_TARGET": self.cfg.GetClusterName(),
1626       }
1627
1628   def BuildHooksNodes(self):
1629     """Build hooks nodes.
1630
1631     """
1632     return ([], [self.cfg.GetMasterNode()])
1633
1634   def Exec(self, feedback_fn):
1635     """Nothing to do.
1636
1637     """
1638     return True
1639
1640
1641 class LUClusterDestroy(LogicalUnit):
1642   """Logical unit for destroying the cluster.
1643
1644   """
1645   HPATH = "cluster-destroy"
1646   HTYPE = constants.HTYPE_CLUSTER
1647
1648   def BuildHooksEnv(self):
1649     """Build hooks env.
1650
1651     """
1652     return {
1653       "OP_TARGET": self.cfg.GetClusterName(),
1654       }
1655
1656   def BuildHooksNodes(self):
1657     """Build hooks nodes.
1658
1659     """
1660     return ([], [])
1661
1662   def CheckPrereq(self):
1663     """Check prerequisites.
1664
1665     This checks whether the cluster is empty.
1666
1667     Any errors are signaled by raising errors.OpPrereqError.
1668
1669     """
1670     master = self.cfg.GetMasterNode()
1671
1672     nodelist = self.cfg.GetNodeList()
1673     if len(nodelist) != 1 or nodelist[0] != master:
1674       raise errors.OpPrereqError("There are still %d node(s) in"
1675                                  " this cluster." % (len(nodelist) - 1),
1676                                  errors.ECODE_INVAL)
1677     instancelist = self.cfg.GetInstanceList()
1678     if instancelist:
1679       raise errors.OpPrereqError("There are still %d instance(s) in"
1680                                  " this cluster." % len(instancelist),
1681                                  errors.ECODE_INVAL)
1682
1683   def Exec(self, feedback_fn):
1684     """Destroys the cluster.
1685
1686     """
1687     master_params = self.cfg.GetMasterNetworkParameters()
1688
1689     # Run post hooks on master node before it's removed
1690     _RunPostHook(self, master_params.name)
1691
1692     ems = self.cfg.GetUseExternalMipScript()
1693     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
1694                                                      master_params, ems)
1695     if result.fail_msg:
1696       self.LogWarning("Error disabling the master IP address: %s",
1697                       result.fail_msg)
1698
1699     return master_params.name
1700
1701
1702 def _VerifyCertificate(filename):
1703   """Verifies a certificate for L{LUClusterVerifyConfig}.
1704
1705   @type filename: string
1706   @param filename: Path to PEM file
1707
1708   """
1709   try:
1710     cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
1711                                            utils.ReadFile(filename))
1712   except Exception, err: # pylint: disable=W0703
1713     return (LUClusterVerifyConfig.ETYPE_ERROR,
1714             "Failed to load X509 certificate %s: %s" % (filename, err))
1715
1716   (errcode, msg) = \
1717     utils.VerifyX509Certificate(cert, constants.SSL_CERT_EXPIRATION_WARN,
1718                                 constants.SSL_CERT_EXPIRATION_ERROR)
1719
1720   if msg:
1721     fnamemsg = "While verifying %s: %s" % (filename, msg)
1722   else:
1723     fnamemsg = None
1724
1725   if errcode is None:
1726     return (None, fnamemsg)
1727   elif errcode == utils.CERT_WARNING:
1728     return (LUClusterVerifyConfig.ETYPE_WARNING, fnamemsg)
1729   elif errcode == utils.CERT_ERROR:
1730     return (LUClusterVerifyConfig.ETYPE_ERROR, fnamemsg)
1731
1732   raise errors.ProgrammerError("Unhandled certificate error code %r" % errcode)
1733
1734
1735 def _GetAllHypervisorParameters(cluster, instances):
1736   """Compute the set of all hypervisor parameters.
1737
1738   @type cluster: L{objects.Cluster}
1739   @param cluster: the cluster object
1740   @param instances: list of L{objects.Instance}
1741   @param instances: additional instances from which to obtain parameters
1742   @rtype: list of (origin, hypervisor, parameters)
1743   @return: a list with all parameters found, indicating the hypervisor they
1744        apply to, and the origin (can be "cluster", "os X", or "instance Y")
1745
1746   """
1747   hvp_data = []
1748
1749   for hv_name in cluster.enabled_hypervisors:
1750     hvp_data.append(("cluster", hv_name, cluster.GetHVDefaults(hv_name)))
1751
1752   for os_name, os_hvp in cluster.os_hvp.items():
1753     for hv_name, hv_params in os_hvp.items():
1754       if hv_params:
1755         full_params = cluster.GetHVDefaults(hv_name, os_name=os_name)
1756         hvp_data.append(("os %s" % os_name, hv_name, full_params))
1757
1758   # TODO: collapse identical parameter values in a single one
1759   for instance in instances:
1760     if instance.hvparams:
1761       hvp_data.append(("instance %s" % instance.name, instance.hypervisor,
1762                        cluster.FillHV(instance)))
1763
1764   return hvp_data
1765
1766
1767 class _VerifyErrors(object):
1768   """Mix-in for cluster/group verify LUs.
1769
1770   It provides _Error and _ErrorIf, and updates the self.bad boolean. (Expects
1771   self.op and self._feedback_fn to be available.)
1772
1773   """
1774
1775   ETYPE_FIELD = "code"
1776   ETYPE_ERROR = "ERROR"
1777   ETYPE_WARNING = "WARNING"
1778
1779   def _Error(self, ecode, item, msg, *args, **kwargs):
1780     """Format an error message.
1781
1782     Based on the opcode's error_codes parameter, either format a
1783     parseable error code, or a simpler error string.
1784
1785     This must be called only from Exec and functions called from Exec.
1786
1787     """
1788     ltype = kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR)
1789     itype, etxt, _ = ecode
1790     # first complete the msg
1791     if args:
1792       msg = msg % args
1793     # then format the whole message
1794     if self.op.error_codes: # This is a mix-in. pylint: disable=E1101
1795       msg = "%s:%s:%s:%s:%s" % (ltype, etxt, itype, item, msg)
1796     else:
1797       if item:
1798         item = " " + item
1799       else:
1800         item = ""
1801       msg = "%s: %s%s: %s" % (ltype, itype, item, msg)
1802     # and finally report it via the feedback_fn
1803     self._feedback_fn("  - %s" % msg) # Mix-in. pylint: disable=E1101
1804
1805   def _ErrorIf(self, cond, ecode, *args, **kwargs):
1806     """Log an error message if the passed condition is True.
1807
1808     """
1809     cond = (bool(cond)
1810             or self.op.debug_simulate_errors) # pylint: disable=E1101
1811
1812     # If the error code is in the list of ignored errors, demote the error to a
1813     # warning
1814     (_, etxt, _) = ecode
1815     if etxt in self.op.ignore_errors:     # pylint: disable=E1101
1816       kwargs[self.ETYPE_FIELD] = self.ETYPE_WARNING
1817
1818     if cond:
1819       self._Error(ecode, *args, **kwargs)
1820
1821     # do not mark the operation as failed for WARN cases only
1822     if kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR) == self.ETYPE_ERROR:
1823       self.bad = self.bad or cond
1824
1825
1826 class LUClusterVerify(NoHooksLU):
1827   """Submits all jobs necessary to verify the cluster.
1828
1829   """
1830   REQ_BGL = False
1831
1832   def ExpandNames(self):
1833     self.needed_locks = {}
1834
1835   def Exec(self, feedback_fn):
1836     jobs = []
1837
1838     if self.op.group_name:
1839       groups = [self.op.group_name]
1840       depends_fn = lambda: None
1841     else:
1842       groups = self.cfg.GetNodeGroupList()
1843
1844       # Verify global configuration
1845       jobs.append([
1846         opcodes.OpClusterVerifyConfig(ignore_errors=self.op.ignore_errors)
1847         ])
1848
1849       # Always depend on global verification
1850       depends_fn = lambda: [(-len(jobs), [])]
1851
1852     jobs.extend([opcodes.OpClusterVerifyGroup(group_name=group,
1853                                             ignore_errors=self.op.ignore_errors,
1854                                             depends=depends_fn())]
1855                 for group in groups)
1856
1857     # Fix up all parameters
1858     for op in itertools.chain(*jobs): # pylint: disable=W0142
1859       op.debug_simulate_errors = self.op.debug_simulate_errors
1860       op.verbose = self.op.verbose
1861       op.error_codes = self.op.error_codes
1862       try:
1863         op.skip_checks = self.op.skip_checks
1864       except AttributeError:
1865         assert not isinstance(op, opcodes.OpClusterVerifyGroup)
1866
1867     return ResultWithJobs(jobs)
1868
1869
1870 class LUClusterVerifyConfig(NoHooksLU, _VerifyErrors):
1871   """Verifies the cluster config.
1872
1873   """
1874   REQ_BGL = True
1875
1876   def _VerifyHVP(self, hvp_data):
1877     """Verifies locally the syntax of the hypervisor parameters.
1878
1879     """
1880     for item, hv_name, hv_params in hvp_data:
1881       msg = ("hypervisor %s parameters syntax check (source %s): %%s" %
1882              (item, hv_name))
1883       try:
1884         hv_class = hypervisor.GetHypervisor(hv_name)
1885         utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
1886         hv_class.CheckParameterSyntax(hv_params)
1887       except errors.GenericError, err:
1888         self._ErrorIf(True, constants.CV_ECLUSTERCFG, None, msg % str(err))
1889
1890   def ExpandNames(self):
1891     # Information can be safely retrieved as the BGL is acquired in exclusive
1892     # mode
1893     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER)
1894     self.all_group_info = self.cfg.GetAllNodeGroupsInfo()
1895     self.all_node_info = self.cfg.GetAllNodesInfo()
1896     self.all_inst_info = self.cfg.GetAllInstancesInfo()
1897     self.needed_locks = {}
1898
1899   def Exec(self, feedback_fn):
1900     """Verify integrity of cluster, performing various test on nodes.
1901
1902     """
1903     self.bad = False
1904     self._feedback_fn = feedback_fn
1905
1906     feedback_fn("* Verifying cluster config")
1907
1908     for msg in self.cfg.VerifyConfig():
1909       self._ErrorIf(True, constants.CV_ECLUSTERCFG, None, msg)
1910
1911     feedback_fn("* Verifying cluster certificate files")
1912
1913     for cert_filename in constants.ALL_CERT_FILES:
1914       (errcode, msg) = _VerifyCertificate(cert_filename)
1915       self._ErrorIf(errcode, constants.CV_ECLUSTERCERT, None, msg, code=errcode)
1916
1917     feedback_fn("* Verifying hypervisor parameters")
1918
1919     self._VerifyHVP(_GetAllHypervisorParameters(self.cfg.GetClusterInfo(),
1920                                                 self.all_inst_info.values()))
1921
1922     feedback_fn("* Verifying all nodes belong to an existing group")
1923
1924     # We do this verification here because, should this bogus circumstance
1925     # occur, it would never be caught by VerifyGroup, which only acts on
1926     # nodes/instances reachable from existing node groups.
1927
1928     dangling_nodes = set(node.name for node in self.all_node_info.values()
1929                          if node.group not in self.all_group_info)
1930
1931     dangling_instances = {}
1932     no_node_instances = []
1933
1934     for inst in self.all_inst_info.values():
1935       if inst.primary_node in dangling_nodes:
1936         dangling_instances.setdefault(inst.primary_node, []).append(inst.name)
1937       elif inst.primary_node not in self.all_node_info:
1938         no_node_instances.append(inst.name)
1939
1940     pretty_dangling = [
1941         "%s (%s)" %
1942         (node.name,
1943          utils.CommaJoin(dangling_instances.get(node.name,
1944                                                 ["no instances"])))
1945         for node in dangling_nodes]
1946
1947     self._ErrorIf(bool(dangling_nodes), constants.CV_ECLUSTERDANGLINGNODES,
1948                   None,
1949                   "the following nodes (and their instances) belong to a non"
1950                   " existing group: %s", utils.CommaJoin(pretty_dangling))
1951
1952     self._ErrorIf(bool(no_node_instances), constants.CV_ECLUSTERDANGLINGINST,
1953                   None,
1954                   "the following instances have a non-existing primary-node:"
1955                   " %s", utils.CommaJoin(no_node_instances))
1956
1957     return not self.bad
1958
1959
1960 class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
1961   """Verifies the status of a node group.
1962
1963   """
1964   HPATH = "cluster-verify"
1965   HTYPE = constants.HTYPE_CLUSTER
1966   REQ_BGL = False
1967
1968   _HOOKS_INDENT_RE = re.compile("^", re.M)
1969
1970   class NodeImage(object):
1971     """A class representing the logical and physical status of a node.
1972
1973     @type name: string
1974     @ivar name: the node name to which this object refers
1975     @ivar volumes: a structure as returned from
1976         L{ganeti.backend.GetVolumeList} (runtime)
1977     @ivar instances: a list of running instances (runtime)
1978     @ivar pinst: list of configured primary instances (config)
1979     @ivar sinst: list of configured secondary instances (config)
1980     @ivar sbp: dictionary of {primary-node: list of instances} for all
1981         instances for which this node is secondary (config)
1982     @ivar mfree: free memory, as reported by hypervisor (runtime)
1983     @ivar dfree: free disk, as reported by the node (runtime)
1984     @ivar offline: the offline status (config)
1985     @type rpc_fail: boolean
1986     @ivar rpc_fail: whether the RPC verify call was successfull (overall,
1987         not whether the individual keys were correct) (runtime)
1988     @type lvm_fail: boolean
1989     @ivar lvm_fail: whether the RPC call didn't return valid LVM data
1990     @type hyp_fail: boolean
1991     @ivar hyp_fail: whether the RPC call didn't return the instance list
1992     @type ghost: boolean
1993     @ivar ghost: whether this is a known node or not (config)
1994     @type os_fail: boolean
1995     @ivar os_fail: whether the RPC call didn't return valid OS data
1996     @type oslist: list
1997     @ivar oslist: list of OSes as diagnosed by DiagnoseOS
1998     @type vm_capable: boolean
1999     @ivar vm_capable: whether the node can host instances
2000
2001     """
2002     def __init__(self, offline=False, name=None, vm_capable=True):
2003       self.name = name
2004       self.volumes = {}
2005       self.instances = []
2006       self.pinst = []
2007       self.sinst = []
2008       self.sbp = {}
2009       self.mfree = 0
2010       self.dfree = 0
2011       self.offline = offline
2012       self.vm_capable = vm_capable
2013       self.rpc_fail = False
2014       self.lvm_fail = False
2015       self.hyp_fail = False
2016       self.ghost = False
2017       self.os_fail = False
2018       self.oslist = {}
2019
2020   def ExpandNames(self):
2021     # This raises errors.OpPrereqError on its own:
2022     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
2023
2024     # Get instances in node group; this is unsafe and needs verification later
2025     inst_names = self.cfg.GetNodeGroupInstances(self.group_uuid)
2026
2027     self.needed_locks = {
2028       locking.LEVEL_INSTANCE: inst_names,
2029       locking.LEVEL_NODEGROUP: [self.group_uuid],
2030       locking.LEVEL_NODE: [],
2031       }
2032
2033     self.share_locks = _ShareAll()
2034
2035   def DeclareLocks(self, level):
2036     if level == locking.LEVEL_NODE:
2037       # Get members of node group; this is unsafe and needs verification later
2038       nodes = set(self.cfg.GetNodeGroup(self.group_uuid).members)
2039
2040       all_inst_info = self.cfg.GetAllInstancesInfo()
2041
2042       # In Exec(), we warn about mirrored instances that have primary and
2043       # secondary living in separate node groups. To fully verify that
2044       # volumes for these instances are healthy, we will need to do an
2045       # extra call to their secondaries. We ensure here those nodes will
2046       # be locked.
2047       for inst in self.owned_locks(locking.LEVEL_INSTANCE):
2048         # Important: access only the instances whose lock is owned
2049         if all_inst_info[inst].disk_template in constants.DTS_INT_MIRROR:
2050           nodes.update(all_inst_info[inst].secondary_nodes)
2051
2052       self.needed_locks[locking.LEVEL_NODE] = nodes
2053
2054   def CheckPrereq(self):
2055     assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
2056     self.group_info = self.cfg.GetNodeGroup(self.group_uuid)
2057
2058     group_nodes = set(self.group_info.members)
2059     group_instances = self.cfg.GetNodeGroupInstances(self.group_uuid)
2060
2061     unlocked_nodes = \
2062         group_nodes.difference(self.owned_locks(locking.LEVEL_NODE))
2063
2064     unlocked_instances = \
2065         group_instances.difference(self.owned_locks(locking.LEVEL_INSTANCE))
2066
2067     if unlocked_nodes:
2068       raise errors.OpPrereqError("Missing lock for nodes: %s" %
2069                                  utils.CommaJoin(unlocked_nodes))
2070
2071     if unlocked_instances:
2072       raise errors.OpPrereqError("Missing lock for instances: %s" %
2073                                  utils.CommaJoin(unlocked_instances))
2074
2075     self.all_node_info = self.cfg.GetAllNodesInfo()
2076     self.all_inst_info = self.cfg.GetAllInstancesInfo()
2077
2078     self.my_node_names = utils.NiceSort(group_nodes)
2079     self.my_inst_names = utils.NiceSort(group_instances)
2080
2081     self.my_node_info = dict((name, self.all_node_info[name])
2082                              for name in self.my_node_names)
2083
2084     self.my_inst_info = dict((name, self.all_inst_info[name])
2085                              for name in self.my_inst_names)
2086
2087     # We detect here the nodes that will need the extra RPC calls for verifying
2088     # split LV volumes; they should be locked.
2089     extra_lv_nodes = set()
2090
2091     for inst in self.my_inst_info.values():
2092       if inst.disk_template in constants.DTS_INT_MIRROR:
2093         group = self.my_node_info[inst.primary_node].group
2094         for nname in inst.secondary_nodes:
2095           if self.all_node_info[nname].group != group:
2096             extra_lv_nodes.add(nname)
2097
2098     unlocked_lv_nodes = \
2099         extra_lv_nodes.difference(self.owned_locks(locking.LEVEL_NODE))
2100
2101     if unlocked_lv_nodes:
2102       raise errors.OpPrereqError("these nodes could be locked: %s" %
2103                                  utils.CommaJoin(unlocked_lv_nodes))
2104     self.extra_lv_nodes = list(extra_lv_nodes)
2105
2106   def _VerifyNode(self, ninfo, nresult):
2107     """Perform some basic validation on data returned from a node.
2108
2109       - check the result data structure is well formed and has all the
2110         mandatory fields
2111       - check ganeti version
2112
2113     @type ninfo: L{objects.Node}
2114     @param ninfo: the node to check
2115     @param nresult: the results from the node
2116     @rtype: boolean
2117     @return: whether overall this call was successful (and we can expect
2118          reasonable values in the respose)
2119
2120     """
2121     node = ninfo.name
2122     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2123
2124     # main result, nresult should be a non-empty dict
2125     test = not nresult or not isinstance(nresult, dict)
2126     _ErrorIf(test, constants.CV_ENODERPC, node,
2127                   "unable to verify node: no data returned")
2128     if test:
2129       return False
2130
2131     # compares ganeti version
2132     local_version = constants.PROTOCOL_VERSION
2133     remote_version = nresult.get("version", None)
2134     test = not (remote_version and
2135                 isinstance(remote_version, (list, tuple)) and
2136                 len(remote_version) == 2)
2137     _ErrorIf(test, constants.CV_ENODERPC, node,
2138              "connection to node returned invalid data")
2139     if test:
2140       return False
2141
2142     test = local_version != remote_version[0]
2143     _ErrorIf(test, constants.CV_ENODEVERSION, node,
2144              "incompatible protocol versions: master %s,"
2145              " node %s", local_version, remote_version[0])
2146     if test:
2147       return False
2148
2149     # node seems compatible, we can actually try to look into its results
2150
2151     # full package version
2152     self._ErrorIf(constants.RELEASE_VERSION != remote_version[1],
2153                   constants.CV_ENODEVERSION, node,
2154                   "software version mismatch: master %s, node %s",
2155                   constants.RELEASE_VERSION, remote_version[1],
2156                   code=self.ETYPE_WARNING)
2157
2158     hyp_result = nresult.get(constants.NV_HYPERVISOR, None)
2159     if ninfo.vm_capable and isinstance(hyp_result, dict):
2160       for hv_name, hv_result in hyp_result.iteritems():
2161         test = hv_result is not None
2162         _ErrorIf(test, constants.CV_ENODEHV, node,
2163                  "hypervisor %s verify failure: '%s'", hv_name, hv_result)
2164
2165     hvp_result = nresult.get(constants.NV_HVPARAMS, None)
2166     if ninfo.vm_capable and isinstance(hvp_result, list):
2167       for item, hv_name, hv_result in hvp_result:
2168         _ErrorIf(True, constants.CV_ENODEHV, node,
2169                  "hypervisor %s parameter verify failure (source %s): %s",
2170                  hv_name, item, hv_result)
2171
2172     test = nresult.get(constants.NV_NODESETUP,
2173                        ["Missing NODESETUP results"])
2174     _ErrorIf(test, constants.CV_ENODESETUP, node, "node setup error: %s",
2175              "; ".join(test))
2176
2177     return True
2178
2179   def _VerifyNodeTime(self, ninfo, nresult,
2180                       nvinfo_starttime, nvinfo_endtime):
2181     """Check the node time.
2182
2183     @type ninfo: L{objects.Node}
2184     @param ninfo: the node to check
2185     @param nresult: the remote results for the node
2186     @param nvinfo_starttime: the start time of the RPC call
2187     @param nvinfo_endtime: the end time of the RPC call
2188
2189     """
2190     node = ninfo.name
2191     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2192
2193     ntime = nresult.get(constants.NV_TIME, None)
2194     try:
2195       ntime_merged = utils.MergeTime(ntime)
2196     except (ValueError, TypeError):
2197       _ErrorIf(True, constants.CV_ENODETIME, node, "Node returned invalid time")
2198       return
2199
2200     if ntime_merged < (nvinfo_starttime - constants.NODE_MAX_CLOCK_SKEW):
2201       ntime_diff = "%.01fs" % abs(nvinfo_starttime - ntime_merged)
2202     elif ntime_merged > (nvinfo_endtime + constants.NODE_MAX_CLOCK_SKEW):
2203       ntime_diff = "%.01fs" % abs(ntime_merged - nvinfo_endtime)
2204     else:
2205       ntime_diff = None
2206
2207     _ErrorIf(ntime_diff is not None, constants.CV_ENODETIME, node,
2208              "Node time diverges by at least %s from master node time",
2209              ntime_diff)
2210
2211   def _VerifyNodeLVM(self, ninfo, nresult, vg_name):
2212     """Check the node LVM results.
2213
2214     @type ninfo: L{objects.Node}
2215     @param ninfo: the node to check
2216     @param nresult: the remote results for the node
2217     @param vg_name: the configured VG name
2218
2219     """
2220     if vg_name is None:
2221       return
2222
2223     node = ninfo.name
2224     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2225
2226     # checks vg existence and size > 20G
2227     vglist = nresult.get(constants.NV_VGLIST, None)
2228     test = not vglist
2229     _ErrorIf(test, constants.CV_ENODELVM, node, "unable to check volume groups")
2230     if not test:
2231       vgstatus = utils.CheckVolumeGroupSize(vglist, vg_name,
2232                                             constants.MIN_VG_SIZE)
2233       _ErrorIf(vgstatus, constants.CV_ENODELVM, node, vgstatus)
2234
2235     # check pv names
2236     pvlist = nresult.get(constants.NV_PVLIST, None)
2237     test = pvlist is None
2238     _ErrorIf(test, constants.CV_ENODELVM, node, "Can't get PV list from node")
2239     if not test:
2240       # check that ':' is not present in PV names, since it's a
2241       # special character for lvcreate (denotes the range of PEs to
2242       # use on the PV)
2243       for _, pvname, owner_vg in pvlist:
2244         test = ":" in pvname
2245         _ErrorIf(test, constants.CV_ENODELVM, node,
2246                  "Invalid character ':' in PV '%s' of VG '%s'",
2247                  pvname, owner_vg)
2248
2249   def _VerifyNodeBridges(self, ninfo, nresult, bridges):
2250     """Check the node bridges.
2251
2252     @type ninfo: L{objects.Node}
2253     @param ninfo: the node to check
2254     @param nresult: the remote results for the node
2255     @param bridges: the expected list of bridges
2256
2257     """
2258     if not bridges:
2259       return
2260
2261     node = ninfo.name
2262     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2263
2264     missing = nresult.get(constants.NV_BRIDGES, None)
2265     test = not isinstance(missing, list)
2266     _ErrorIf(test, constants.CV_ENODENET, node,
2267              "did not return valid bridge information")
2268     if not test:
2269       _ErrorIf(bool(missing), constants.CV_ENODENET, node,
2270                "missing bridges: %s" % utils.CommaJoin(sorted(missing)))
2271
2272   def _VerifyNodeUserScripts(self, ninfo, nresult):
2273     """Check the results of user scripts presence and executability on the node
2274
2275     @type ninfo: L{objects.Node}
2276     @param ninfo: the node to check
2277     @param nresult: the remote results for the node
2278
2279     """
2280     node = ninfo.name
2281
2282     test = not constants.NV_USERSCRIPTS in nresult
2283     self._ErrorIf(test, constants.CV_ENODEUSERSCRIPTS, node,
2284                   "did not return user scripts information")
2285
2286     broken_scripts = nresult.get(constants.NV_USERSCRIPTS, None)
2287     if not test:
2288       self._ErrorIf(broken_scripts, constants.CV_ENODEUSERSCRIPTS, node,
2289                     "user scripts not present or not executable: %s" %
2290                     utils.CommaJoin(sorted(broken_scripts)))
2291
2292   def _VerifyNodeNetwork(self, ninfo, nresult):
2293     """Check the node network connectivity results.
2294
2295     @type ninfo: L{objects.Node}
2296     @param ninfo: the node to check
2297     @param nresult: the remote results for the node
2298
2299     """
2300     node = ninfo.name
2301     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2302
2303     test = constants.NV_NODELIST not in nresult
2304     _ErrorIf(test, constants.CV_ENODESSH, node,
2305              "node hasn't returned node ssh connectivity data")
2306     if not test:
2307       if nresult[constants.NV_NODELIST]:
2308         for a_node, a_msg in nresult[constants.NV_NODELIST].items():
2309           _ErrorIf(True, constants.CV_ENODESSH, node,
2310                    "ssh communication with node '%s': %s", a_node, a_msg)
2311
2312     test = constants.NV_NODENETTEST not in nresult
2313     _ErrorIf(test, constants.CV_ENODENET, node,
2314              "node hasn't returned node tcp connectivity data")
2315     if not test:
2316       if nresult[constants.NV_NODENETTEST]:
2317         nlist = utils.NiceSort(nresult[constants.NV_NODENETTEST].keys())
2318         for anode in nlist:
2319           _ErrorIf(True, constants.CV_ENODENET, node,
2320                    "tcp communication with node '%s': %s",
2321                    anode, nresult[constants.NV_NODENETTEST][anode])
2322
2323     test = constants.NV_MASTERIP not in nresult
2324     _ErrorIf(test, constants.CV_ENODENET, node,
2325              "node hasn't returned node master IP reachability data")
2326     if not test:
2327       if not nresult[constants.NV_MASTERIP]:
2328         if node == self.master_node:
2329           msg = "the master node cannot reach the master IP (not configured?)"
2330         else:
2331           msg = "cannot reach the master IP"
2332         _ErrorIf(True, constants.CV_ENODENET, node, msg)
2333
2334   def _VerifyInstance(self, instance, instanceconfig, node_image,
2335                       diskstatus):
2336     """Verify an instance.
2337
2338     This function checks to see if the required block devices are
2339     available on the instance's node.
2340
2341     """
2342     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2343     node_current = instanceconfig.primary_node
2344
2345     node_vol_should = {}
2346     instanceconfig.MapLVsByNode(node_vol_should)
2347
2348     ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(), self.group_info)
2349     err = _ComputeIPolicyInstanceViolation(ipolicy, instanceconfig)
2350     _ErrorIf(err, constants.CV_EINSTANCEPOLICY, instance, err)
2351
2352     for node in node_vol_should:
2353       n_img = node_image[node]
2354       if n_img.offline or n_img.rpc_fail or n_img.lvm_fail:
2355         # ignore missing volumes on offline or broken nodes
2356         continue
2357       for volume in node_vol_should[node]:
2358         test = volume not in n_img.volumes
2359         _ErrorIf(test, constants.CV_EINSTANCEMISSINGDISK, instance,
2360                  "volume %s missing on node %s", volume, node)
2361
2362     if instanceconfig.admin_state == constants.ADMINST_UP:
2363       pri_img = node_image[node_current]
2364       test = instance not in pri_img.instances and not pri_img.offline
2365       _ErrorIf(test, constants.CV_EINSTANCEDOWN, instance,
2366                "instance not running on its primary node %s",
2367                node_current)
2368
2369     diskdata = [(nname, success, status, idx)
2370                 for (nname, disks) in diskstatus.items()
2371                 for idx, (success, status) in enumerate(disks)]
2372
2373     for nname, success, bdev_status, idx in diskdata:
2374       # the 'ghost node' construction in Exec() ensures that we have a
2375       # node here
2376       snode = node_image[nname]
2377       bad_snode = snode.ghost or snode.offline
2378       _ErrorIf(instanceconfig.admin_state == constants.ADMINST_UP and
2379                not success and not bad_snode,
2380                constants.CV_EINSTANCEFAULTYDISK, instance,
2381                "couldn't retrieve status for disk/%s on %s: %s",
2382                idx, nname, bdev_status)
2383       _ErrorIf((instanceconfig.admin_state == constants.ADMINST_UP and
2384                 success and bdev_status.ldisk_status == constants.LDS_FAULTY),
2385                constants.CV_EINSTANCEFAULTYDISK, instance,
2386                "disk/%s on %s is faulty", idx, nname)
2387
2388   def _VerifyOrphanVolumes(self, node_vol_should, node_image, reserved):
2389     """Verify if there are any unknown volumes in the cluster.
2390
2391     The .os, .swap and backup volumes are ignored. All other volumes are
2392     reported as unknown.
2393
2394     @type reserved: L{ganeti.utils.FieldSet}
2395     @param reserved: a FieldSet of reserved volume names
2396
2397     """
2398     for node, n_img in node_image.items():
2399       if n_img.offline or n_img.rpc_fail or n_img.lvm_fail:
2400         # skip non-healthy nodes
2401         continue
2402       for volume in n_img.volumes:
2403         test = ((node not in node_vol_should or
2404                 volume not in node_vol_should[node]) and
2405                 not reserved.Matches(volume))
2406         self._ErrorIf(test, constants.CV_ENODEORPHANLV, node,
2407                       "volume %s is unknown", volume)
2408
2409   def _VerifyNPlusOneMemory(self, node_image, instance_cfg):
2410     """Verify N+1 Memory Resilience.
2411
2412     Check that if one single node dies we can still start all the
2413     instances it was primary for.
2414
2415     """
2416     cluster_info = self.cfg.GetClusterInfo()
2417     for node, n_img in node_image.items():
2418       # This code checks that every node which is now listed as
2419       # secondary has enough memory to host all instances it is
2420       # supposed to should a single other node in the cluster fail.
2421       # FIXME: not ready for failover to an arbitrary node
2422       # FIXME: does not support file-backed instances
2423       # WARNING: we currently take into account down instances as well
2424       # as up ones, considering that even if they're down someone
2425       # might want to start them even in the event of a node failure.
2426       if n_img.offline:
2427         # we're skipping offline nodes from the N+1 warning, since
2428         # most likely we don't have good memory infromation from them;
2429         # we already list instances living on such nodes, and that's
2430         # enough warning
2431         continue
2432       #TODO(dynmem): use MINMEM for checking
2433       #TODO(dynmem): also consider ballooning out other instances
2434       for prinode, instances in n_img.sbp.items():
2435         needed_mem = 0
2436         for instance in instances:
2437           bep = cluster_info.FillBE(instance_cfg[instance])
2438           if bep[constants.BE_AUTO_BALANCE]:
2439             needed_mem += bep[constants.BE_MAXMEM]
2440         test = n_img.mfree < needed_mem
2441         self._ErrorIf(test, constants.CV_ENODEN1, node,
2442                       "not enough memory to accomodate instance failovers"
2443                       " should node %s fail (%dMiB needed, %dMiB available)",
2444                       prinode, needed_mem, n_img.mfree)
2445
2446   @classmethod
2447   def _VerifyFiles(cls, errorif, nodeinfo, master_node, all_nvinfo,
2448                    (files_all, files_opt, files_mc, files_vm)):
2449     """Verifies file checksums collected from all nodes.
2450
2451     @param errorif: Callback for reporting errors
2452     @param nodeinfo: List of L{objects.Node} objects
2453     @param master_node: Name of master node
2454     @param all_nvinfo: RPC results
2455
2456     """
2457     # Define functions determining which nodes to consider for a file
2458     files2nodefn = [
2459       (files_all, None),
2460       (files_mc, lambda node: (node.master_candidate or
2461                                node.name == master_node)),
2462       (files_vm, lambda node: node.vm_capable),
2463       ]
2464
2465     # Build mapping from filename to list of nodes which should have the file
2466     nodefiles = {}
2467     for (files, fn) in files2nodefn:
2468       if fn is None:
2469         filenodes = nodeinfo
2470       else:
2471         filenodes = filter(fn, nodeinfo)
2472       nodefiles.update((filename,
2473                         frozenset(map(operator.attrgetter("name"), filenodes)))
2474                        for filename in files)
2475
2476     assert set(nodefiles) == (files_all | files_mc | files_vm)
2477
2478     fileinfo = dict((filename, {}) for filename in nodefiles)
2479     ignore_nodes = set()
2480
2481     for node in nodeinfo:
2482       if node.offline:
2483         ignore_nodes.add(node.name)
2484         continue
2485
2486       nresult = all_nvinfo[node.name]
2487
2488       if nresult.fail_msg or not nresult.payload:
2489         node_files = None
2490       else:
2491         node_files = nresult.payload.get(constants.NV_FILELIST, None)
2492
2493       test = not (node_files and isinstance(node_files, dict))
2494       errorif(test, constants.CV_ENODEFILECHECK, node.name,
2495               "Node did not return file checksum data")
2496       if test:
2497         ignore_nodes.add(node.name)
2498         continue
2499
2500       # Build per-checksum mapping from filename to nodes having it
2501       for (filename, checksum) in node_files.items():
2502         assert filename in nodefiles
2503         fileinfo[filename].setdefault(checksum, set()).add(node.name)
2504
2505     for (filename, checksums) in fileinfo.items():
2506       assert compat.all(len(i) > 10 for i in checksums), "Invalid checksum"
2507
2508       # Nodes having the file
2509       with_file = frozenset(node_name
2510                             for nodes in fileinfo[filename].values()
2511                             for node_name in nodes) - ignore_nodes
2512
2513       expected_nodes = nodefiles[filename] - ignore_nodes
2514
2515       # Nodes missing file
2516       missing_file = expected_nodes - with_file
2517
2518       if filename in files_opt:
2519         # All or no nodes
2520         errorif(missing_file and missing_file != expected_nodes,
2521                 constants.CV_ECLUSTERFILECHECK, None,
2522                 "File %s is optional, but it must exist on all or no"
2523                 " nodes (not found on %s)",
2524                 filename, utils.CommaJoin(utils.NiceSort(missing_file)))
2525       else:
2526         errorif(missing_file, constants.CV_ECLUSTERFILECHECK, None,
2527                 "File %s is missing from node(s) %s", filename,
2528                 utils.CommaJoin(utils.NiceSort(missing_file)))
2529
2530         # Warn if a node has a file it shouldn't
2531         unexpected = with_file - expected_nodes
2532         errorif(unexpected,
2533                 constants.CV_ECLUSTERFILECHECK, None,
2534                 "File %s should not exist on node(s) %s",
2535                 filename, utils.CommaJoin(utils.NiceSort(unexpected)))
2536
2537       # See if there are multiple versions of the file
2538       test = len(checksums) > 1
2539       if test:
2540         variants = ["variant %s on %s" %
2541                     (idx + 1, utils.CommaJoin(utils.NiceSort(nodes)))
2542                     for (idx, (checksum, nodes)) in
2543                       enumerate(sorted(checksums.items()))]
2544       else:
2545         variants = []
2546
2547       errorif(test, constants.CV_ECLUSTERFILECHECK, None,
2548               "File %s found with %s different checksums (%s)",
2549               filename, len(checksums), "; ".join(variants))
2550
2551   def _VerifyNodeDrbd(self, ninfo, nresult, instanceinfo, drbd_helper,
2552                       drbd_map):
2553     """Verifies and the node DRBD status.
2554
2555     @type ninfo: L{objects.Node}
2556     @param ninfo: the node to check
2557     @param nresult: the remote results for the node
2558     @param instanceinfo: the dict of instances
2559     @param drbd_helper: the configured DRBD usermode helper
2560     @param drbd_map: the DRBD map as returned by
2561         L{ganeti.config.ConfigWriter.ComputeDRBDMap}
2562
2563     """
2564     node = ninfo.name
2565     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2566
2567     if drbd_helper:
2568       helper_result = nresult.get(constants.NV_DRBDHELPER, None)
2569       test = (helper_result == None)
2570       _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2571                "no drbd usermode helper returned")
2572       if helper_result:
2573         status, payload = helper_result
2574         test = not status
2575         _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2576                  "drbd usermode helper check unsuccessful: %s", payload)
2577         test = status and (payload != drbd_helper)
2578         _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2579                  "wrong drbd usermode helper: %s", payload)
2580
2581     # compute the DRBD minors
2582     node_drbd = {}
2583     for minor, instance in drbd_map[node].items():
2584       test = instance not in instanceinfo
2585       _ErrorIf(test, constants.CV_ECLUSTERCFG, None,
2586                "ghost instance '%s' in temporary DRBD map", instance)
2587         # ghost instance should not be running, but otherwise we
2588         # don't give double warnings (both ghost instance and
2589         # unallocated minor in use)
2590       if test:
2591         node_drbd[minor] = (instance, False)
2592       else:
2593         instance = instanceinfo[instance]
2594         node_drbd[minor] = (instance.name,
2595                             instance.admin_state == constants.ADMINST_UP)
2596
2597     # and now check them
2598     used_minors = nresult.get(constants.NV_DRBDLIST, [])
2599     test = not isinstance(used_minors, (tuple, list))
2600     _ErrorIf(test, constants.CV_ENODEDRBD, node,
2601              "cannot parse drbd status file: %s", str(used_minors))
2602     if test:
2603       # we cannot check drbd status
2604       return
2605
2606     for minor, (iname, must_exist) in node_drbd.items():
2607       test = minor not in used_minors and must_exist
2608       _ErrorIf(test, constants.CV_ENODEDRBD, node,
2609                "drbd minor %d of instance %s is not active", minor, iname)
2610     for minor in used_minors:
2611       test = minor not in node_drbd
2612       _ErrorIf(test, constants.CV_ENODEDRBD, node,
2613                "unallocated drbd minor %d is in use", minor)
2614
2615   def _UpdateNodeOS(self, ninfo, nresult, nimg):
2616     """Builds the node OS structures.
2617
2618     @type ninfo: L{objects.Node}
2619     @param ninfo: the node to check
2620     @param nresult: the remote results for the node
2621     @param nimg: the node image object
2622
2623     """
2624     node = ninfo.name
2625     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2626
2627     remote_os = nresult.get(constants.NV_OSLIST, None)
2628     test = (not isinstance(remote_os, list) or
2629             not compat.all(isinstance(v, list) and len(v) == 7
2630                            for v in remote_os))
2631
2632     _ErrorIf(test, constants.CV_ENODEOS, node,
2633              "node hasn't returned valid OS data")
2634
2635     nimg.os_fail = test
2636
2637     if test:
2638       return
2639
2640     os_dict = {}
2641
2642     for (name, os_path, status, diagnose,
2643          variants, parameters, api_ver) in nresult[constants.NV_OSLIST]:
2644
2645       if name not in os_dict:
2646         os_dict[name] = []
2647
2648       # parameters is a list of lists instead of list of tuples due to
2649       # JSON lacking a real tuple type, fix it:
2650       parameters = [tuple(v) for v in parameters]
2651       os_dict[name].append((os_path, status, diagnose,
2652                             set(variants), set(parameters), set(api_ver)))
2653
2654     nimg.oslist = os_dict
2655
2656   def _VerifyNodeOS(self, ninfo, nimg, base):
2657     """Verifies the node OS list.
2658
2659     @type ninfo: L{objects.Node}
2660     @param ninfo: the node to check
2661     @param nimg: the node image object
2662     @param base: the 'template' node we match against (e.g. from the master)
2663
2664     """
2665     node = ninfo.name
2666     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2667
2668     assert not nimg.os_fail, "Entered _VerifyNodeOS with failed OS rpc?"
2669
2670     beautify_params = lambda l: ["%s: %s" % (k, v) for (k, v) in l]
2671     for os_name, os_data in nimg.oslist.items():
2672       assert os_data, "Empty OS status for OS %s?!" % os_name
2673       f_path, f_status, f_diag, f_var, f_param, f_api = os_data[0]
2674       _ErrorIf(not f_status, constants.CV_ENODEOS, node,
2675                "Invalid OS %s (located at %s): %s", os_name, f_path, f_diag)
2676       _ErrorIf(len(os_data) > 1, constants.CV_ENODEOS, node,
2677                "OS '%s' has multiple entries (first one shadows the rest): %s",
2678                os_name, utils.CommaJoin([v[0] for v in os_data]))
2679       # comparisons with the 'base' image
2680       test = os_name not in base.oslist
2681       _ErrorIf(test, constants.CV_ENODEOS, node,
2682                "Extra OS %s not present on reference node (%s)",
2683                os_name, base.name)
2684       if test:
2685         continue
2686       assert base.oslist[os_name], "Base node has empty OS status?"
2687       _, b_status, _, b_var, b_param, b_api = base.oslist[os_name][0]
2688       if not b_status:
2689         # base OS is invalid, skipping
2690         continue
2691       for kind, a, b in [("API version", f_api, b_api),
2692                          ("variants list", f_var, b_var),
2693                          ("parameters", beautify_params(f_param),
2694                           beautify_params(b_param))]:
2695         _ErrorIf(a != b, constants.CV_ENODEOS, node,
2696                  "OS %s for %s differs from reference node %s: [%s] vs. [%s]",
2697                  kind, os_name, base.name,
2698                  utils.CommaJoin(sorted(a)), utils.CommaJoin(sorted(b)))
2699
2700     # check any missing OSes
2701     missing = set(base.oslist.keys()).difference(nimg.oslist.keys())
2702     _ErrorIf(missing, constants.CV_ENODEOS, node,
2703              "OSes present on reference node %s but missing on this node: %s",
2704              base.name, utils.CommaJoin(missing))
2705
2706   def _VerifyOob(self, ninfo, nresult):
2707     """Verifies out of band functionality of a node.
2708
2709     @type ninfo: L{objects.Node}
2710     @param ninfo: the node to check
2711     @param nresult: the remote results for the node
2712
2713     """
2714     node = ninfo.name
2715     # We just have to verify the paths on master and/or master candidates
2716     # as the oob helper is invoked on the master
2717     if ((ninfo.master_candidate or ninfo.master_capable) and
2718         constants.NV_OOB_PATHS in nresult):
2719       for path_result in nresult[constants.NV_OOB_PATHS]:
2720         self._ErrorIf(path_result, constants.CV_ENODEOOBPATH, node, path_result)
2721
2722   def _UpdateNodeVolumes(self, ninfo, nresult, nimg, vg_name):
2723     """Verifies and updates the node volume data.
2724
2725     This function will update a L{NodeImage}'s internal structures
2726     with data from the remote call.
2727
2728     @type ninfo: L{objects.Node}
2729     @param ninfo: the node to check
2730     @param nresult: the remote results for the node
2731     @param nimg: the node image object
2732     @param vg_name: the configured VG name
2733
2734     """
2735     node = ninfo.name
2736     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2737
2738     nimg.lvm_fail = True
2739     lvdata = nresult.get(constants.NV_LVLIST, "Missing LV data")
2740     if vg_name is None:
2741       pass
2742     elif isinstance(lvdata, basestring):
2743       _ErrorIf(True, constants.CV_ENODELVM, node, "LVM problem on node: %s",
2744                utils.SafeEncode(lvdata))
2745     elif not isinstance(lvdata, dict):
2746       _ErrorIf(True, constants.CV_ENODELVM, node,
2747                "rpc call to node failed (lvlist)")
2748     else:
2749       nimg.volumes = lvdata
2750       nimg.lvm_fail = False
2751
2752   def _UpdateNodeInstances(self, ninfo, nresult, nimg):
2753     """Verifies and updates the node instance list.
2754
2755     If the listing was successful, then updates this node's instance
2756     list. Otherwise, it marks the RPC call as failed for the instance
2757     list key.
2758
2759     @type ninfo: L{objects.Node}
2760     @param ninfo: the node to check
2761     @param nresult: the remote results for the node
2762     @param nimg: the node image object
2763
2764     """
2765     idata = nresult.get(constants.NV_INSTANCELIST, None)
2766     test = not isinstance(idata, list)
2767     self._ErrorIf(test, constants.CV_ENODEHV, ninfo.name,
2768                   "rpc call to node failed (instancelist): %s",
2769                   utils.SafeEncode(str(idata)))
2770     if test:
2771       nimg.hyp_fail = True
2772     else:
2773       nimg.instances = idata
2774
2775   def _UpdateNodeInfo(self, ninfo, nresult, nimg, vg_name):
2776     """Verifies and computes a node information map
2777
2778     @type ninfo: L{objects.Node}
2779     @param ninfo: the node to check
2780     @param nresult: the remote results for the node
2781     @param nimg: the node image object
2782     @param vg_name: the configured VG name
2783
2784     """
2785     node = ninfo.name
2786     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2787
2788     # try to read free memory (from the hypervisor)
2789     hv_info = nresult.get(constants.NV_HVINFO, None)
2790     test = not isinstance(hv_info, dict) or "memory_free" not in hv_info
2791     _ErrorIf(test, constants.CV_ENODEHV, node,
2792              "rpc call to node failed (hvinfo)")
2793     if not test:
2794       try:
2795         nimg.mfree = int(hv_info["memory_free"])
2796       except (ValueError, TypeError):
2797         _ErrorIf(True, constants.CV_ENODERPC, node,
2798                  "node returned invalid nodeinfo, check hypervisor")
2799
2800     # FIXME: devise a free space model for file based instances as well
2801     if vg_name is not None:
2802       test = (constants.NV_VGLIST not in nresult or
2803               vg_name not in nresult[constants.NV_VGLIST])
2804       _ErrorIf(test, constants.CV_ENODELVM, node,
2805                "node didn't return data for the volume group '%s'"
2806                " - it is either missing or broken", vg_name)
2807       if not test:
2808         try:
2809           nimg.dfree = int(nresult[constants.NV_VGLIST][vg_name])
2810         except (ValueError, TypeError):
2811           _ErrorIf(True, constants.CV_ENODERPC, node,
2812                    "node returned invalid LVM info, check LVM status")
2813
2814   def _CollectDiskInfo(self, nodelist, node_image, instanceinfo):
2815     """Gets per-disk status information for all instances.
2816
2817     @type nodelist: list of strings
2818     @param nodelist: Node names
2819     @type node_image: dict of (name, L{objects.Node})
2820     @param node_image: Node objects
2821     @type instanceinfo: dict of (name, L{objects.Instance})
2822     @param instanceinfo: Instance objects
2823     @rtype: {instance: {node: [(succes, payload)]}}
2824     @return: a dictionary of per-instance dictionaries with nodes as
2825         keys and disk information as values; the disk information is a
2826         list of tuples (success, payload)
2827
2828     """
2829     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2830
2831     node_disks = {}
2832     node_disks_devonly = {}
2833     diskless_instances = set()
2834     diskless = constants.DT_DISKLESS
2835
2836     for nname in nodelist:
2837       node_instances = list(itertools.chain(node_image[nname].pinst,
2838                                             node_image[nname].sinst))
2839       diskless_instances.update(inst for inst in node_instances
2840                                 if instanceinfo[inst].disk_template == diskless)
2841       disks = [(inst, disk)
2842                for inst in node_instances
2843                for disk in instanceinfo[inst].disks]
2844
2845       if not disks:
2846         # No need to collect data
2847         continue
2848
2849       node_disks[nname] = disks
2850
2851       # Creating copies as SetDiskID below will modify the objects and that can
2852       # lead to incorrect data returned from nodes
2853       devonly = [dev.Copy() for (_, dev) in disks]
2854
2855       for dev in devonly:
2856         self.cfg.SetDiskID(dev, nname)
2857
2858       node_disks_devonly[nname] = devonly
2859
2860     assert len(node_disks) == len(node_disks_devonly)
2861
2862     # Collect data from all nodes with disks
2863     result = self.rpc.call_blockdev_getmirrorstatus_multi(node_disks.keys(),
2864                                                           node_disks_devonly)
2865
2866     assert len(result) == len(node_disks)
2867
2868     instdisk = {}
2869
2870     for (nname, nres) in result.items():
2871       disks = node_disks[nname]
2872
2873       if nres.offline:
2874         # No data from this node
2875         data = len(disks) * [(False, "node offline")]
2876       else:
2877         msg = nres.fail_msg
2878         _ErrorIf(msg, constants.CV_ENODERPC, nname,
2879                  "while getting disk information: %s", msg)
2880         if msg:
2881           # No data from this node
2882           data = len(disks) * [(False, msg)]
2883         else:
2884           data = []
2885           for idx, i in enumerate(nres.payload):
2886             if isinstance(i, (tuple, list)) and len(i) == 2:
2887               data.append(i)
2888             else:
2889               logging.warning("Invalid result from node %s, entry %d: %s",
2890                               nname, idx, i)
2891               data.append((False, "Invalid result from the remote node"))
2892
2893       for ((inst, _), status) in zip(disks, data):
2894         instdisk.setdefault(inst, {}).setdefault(nname, []).append(status)
2895
2896     # Add empty entries for diskless instances.
2897     for inst in diskless_instances:
2898       assert inst not in instdisk
2899       instdisk[inst] = {}
2900
2901     assert compat.all(len(statuses) == len(instanceinfo[inst].disks) and
2902                       len(nnames) <= len(instanceinfo[inst].all_nodes) and
2903                       compat.all(isinstance(s, (tuple, list)) and
2904                                  len(s) == 2 for s in statuses)
2905                       for inst, nnames in instdisk.items()
2906                       for nname, statuses in nnames.items())
2907     assert set(instdisk) == set(instanceinfo), "instdisk consistency failure"
2908
2909     return instdisk
2910
2911   @staticmethod
2912   def _SshNodeSelector(group_uuid, all_nodes):
2913     """Create endless iterators for all potential SSH check hosts.
2914
2915     """
2916     nodes = [node for node in all_nodes
2917              if (node.group != group_uuid and
2918                  not node.offline)]
2919     keyfunc = operator.attrgetter("group")
2920
2921     return map(itertools.cycle,
2922                [sorted(map(operator.attrgetter("name"), names))
2923                 for _, names in itertools.groupby(sorted(nodes, key=keyfunc),
2924                                                   keyfunc)])
2925
2926   @classmethod
2927   def _SelectSshCheckNodes(cls, group_nodes, group_uuid, all_nodes):
2928     """Choose which nodes should talk to which other nodes.
2929
2930     We will make nodes contact all nodes in their group, and one node from
2931     every other group.
2932
2933     @warning: This algorithm has a known issue if one node group is much
2934       smaller than others (e.g. just one node). In such a case all other
2935       nodes will talk to the single node.
2936
2937     """
2938     online_nodes = sorted(node.name for node in group_nodes if not node.offline)
2939     sel = cls._SshNodeSelector(group_uuid, all_nodes)
2940
2941     return (online_nodes,
2942             dict((name, sorted([i.next() for i in sel]))
2943                  for name in online_nodes))
2944
2945   def BuildHooksEnv(self):
2946     """Build hooks env.
2947
2948     Cluster-Verify hooks just ran in the post phase and their failure makes
2949     the output be logged in the verify output and the verification to fail.
2950
2951     """
2952     env = {
2953       "CLUSTER_TAGS": " ".join(self.cfg.GetClusterInfo().GetTags())
2954       }
2955
2956     env.update(("NODE_TAGS_%s" % node.name, " ".join(node.GetTags()))
2957                for node in self.my_node_info.values())
2958
2959     return env
2960
2961   def BuildHooksNodes(self):
2962     """Build hooks nodes.
2963
2964     """
2965     return ([], self.my_node_names)
2966
2967   def Exec(self, feedback_fn):
2968     """Verify integrity of the node group, performing various test on nodes.
2969
2970     """
2971     # This method has too many local variables. pylint: disable=R0914
2972     feedback_fn("* Verifying group '%s'" % self.group_info.name)
2973
2974     if not self.my_node_names:
2975       # empty node group
2976       feedback_fn("* Empty node group, skipping verification")
2977       return True
2978
2979     self.bad = False
2980     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2981     verbose = self.op.verbose
2982     self._feedback_fn = feedback_fn
2983
2984     vg_name = self.cfg.GetVGName()
2985     drbd_helper = self.cfg.GetDRBDHelper()
2986     cluster = self.cfg.GetClusterInfo()
2987     groupinfo = self.cfg.GetAllNodeGroupsInfo()
2988     hypervisors = cluster.enabled_hypervisors
2989     node_data_list = [self.my_node_info[name] for name in self.my_node_names]
2990
2991     i_non_redundant = [] # Non redundant instances
2992     i_non_a_balanced = [] # Non auto-balanced instances
2993     i_offline = 0 # Count of offline instances
2994     n_offline = 0 # Count of offline nodes
2995     n_drained = 0 # Count of nodes being drained
2996     node_vol_should = {}
2997
2998     # FIXME: verify OS list
2999
3000     # File verification
3001     filemap = _ComputeAncillaryFiles(cluster, False)
3002
3003     # do local checksums
3004     master_node = self.master_node = self.cfg.GetMasterNode()
3005     master_ip = self.cfg.GetMasterIP()
3006
3007     feedback_fn("* Gathering data (%d nodes)" % len(self.my_node_names))
3008
3009     user_scripts = []
3010     if self.cfg.GetUseExternalMipScript():
3011       user_scripts.append(constants.EXTERNAL_MASTER_SETUP_SCRIPT)
3012
3013     node_verify_param = {
3014       constants.NV_FILELIST:
3015         utils.UniqueSequence(filename
3016                              for files in filemap
3017                              for filename in files),
3018       constants.NV_NODELIST:
3019         self._SelectSshCheckNodes(node_data_list, self.group_uuid,
3020                                   self.all_node_info.values()),
3021       constants.NV_HYPERVISOR: hypervisors,
3022       constants.NV_HVPARAMS:
3023         _GetAllHypervisorParameters(cluster, self.all_inst_info.values()),
3024       constants.NV_NODENETTEST: [(node.name, node.primary_ip, node.secondary_ip)
3025                                  for node in node_data_list
3026                                  if not node.offline],
3027       constants.NV_INSTANCELIST: hypervisors,
3028       constants.NV_VERSION: None,
3029       constants.NV_HVINFO: self.cfg.GetHypervisorType(),
3030       constants.NV_NODESETUP: None,
3031       constants.NV_TIME: None,
3032       constants.NV_MASTERIP: (master_node, master_ip),
3033       constants.NV_OSLIST: None,
3034       constants.NV_VMNODES: self.cfg.GetNonVmCapableNodeList(),
3035       constants.NV_USERSCRIPTS: user_scripts,
3036       }
3037
3038     if vg_name is not None:
3039       node_verify_param[constants.NV_VGLIST] = None
3040       node_verify_param[constants.NV_LVLIST] = vg_name
3041       node_verify_param[constants.NV_PVLIST] = [vg_name]
3042       node_verify_param[constants.NV_DRBDLIST] = None
3043
3044     if drbd_helper:
3045       node_verify_param[constants.NV_DRBDHELPER] = drbd_helper
3046
3047     # bridge checks
3048     # FIXME: this needs to be changed per node-group, not cluster-wide
3049     bridges = set()
3050     default_nicpp = cluster.nicparams[constants.PP_DEFAULT]
3051     if default_nicpp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
3052       bridges.add(default_nicpp[constants.NIC_LINK])
3053     for instance in self.my_inst_info.values():
3054       for nic in instance.nics:
3055         full_nic = cluster.SimpleFillNIC(nic.nicparams)
3056         if full_nic[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
3057           bridges.add(full_nic[constants.NIC_LINK])
3058
3059     if bridges:
3060       node_verify_param[constants.NV_BRIDGES] = list(bridges)
3061
3062     # Build our expected cluster state
3063     node_image = dict((node.name, self.NodeImage(offline=node.offline,
3064                                                  name=node.name,
3065                                                  vm_capable=node.vm_capable))
3066                       for node in node_data_list)
3067
3068     # Gather OOB paths
3069     oob_paths = []
3070     for node in self.all_node_info.values():
3071       path = _SupportsOob(self.cfg, node)
3072       if path and path not in oob_paths:
3073         oob_paths.append(path)
3074
3075     if oob_paths:
3076       node_verify_param[constants.NV_OOB_PATHS] = oob_paths
3077
3078     for instance in self.my_inst_names:
3079       inst_config = self.my_inst_info[instance]
3080
3081       for nname in inst_config.all_nodes:
3082         if nname not in node_image:
3083           gnode = self.NodeImage(name=nname)
3084           gnode.ghost = (nname not in self.all_node_info)
3085           node_image[nname] = gnode
3086
3087       inst_config.MapLVsByNode(node_vol_should)
3088
3089       pnode = inst_config.primary_node
3090       node_image[pnode].pinst.append(instance)
3091
3092       for snode in inst_config.secondary_nodes:
3093         nimg = node_image[snode]
3094         nimg.sinst.append(instance)
3095         if pnode not in nimg.sbp:
3096           nimg.sbp[pnode] = []
3097         nimg.sbp[pnode].append(instance)
3098
3099     # At this point, we have the in-memory data structures complete,
3100     # except for the runtime information, which we'll gather next
3101
3102     # Due to the way our RPC system works, exact response times cannot be
3103     # guaranteed (e.g. a broken node could run into a timeout). By keeping the
3104     # time before and after executing the request, we can at least have a time
3105     # window.
3106     nvinfo_starttime = time.time()
3107     all_nvinfo = self.rpc.call_node_verify(self.my_node_names,
3108                                            node_verify_param,
3109                                            self.cfg.GetClusterName())
3110     nvinfo_endtime = time.time()
3111
3112     if self.extra_lv_nodes and vg_name is not None:
3113       extra_lv_nvinfo = \
3114           self.rpc.call_node_verify(self.extra_lv_nodes,
3115                                     {constants.NV_LVLIST: vg_name},
3116                                     self.cfg.GetClusterName())
3117     else:
3118       extra_lv_nvinfo = {}
3119
3120     all_drbd_map = self.cfg.ComputeDRBDMap()
3121
3122     feedback_fn("* Gathering disk information (%s nodes)" %
3123                 len(self.my_node_names))
3124     instdisk = self._CollectDiskInfo(self.my_node_names, node_image,
3125                                      self.my_inst_info)
3126
3127     feedback_fn("* Verifying configuration file consistency")
3128
3129     # If not all nodes are being checked, we need to make sure the master node
3130     # and a non-checked vm_capable node are in the list.
3131     absent_nodes = set(self.all_node_info).difference(self.my_node_info)
3132     if absent_nodes:
3133       vf_nvinfo = all_nvinfo.copy()
3134       vf_node_info = list(self.my_node_info.values())
3135       additional_nodes = []
3136       if master_node not in self.my_node_info:
3137         additional_nodes.append(master_node)
3138         vf_node_info.append(self.all_node_info[master_node])
3139       # Add the first vm_capable node we find which is not included
3140       for node in absent_nodes:
3141         nodeinfo = self.all_node_info[node]
3142         if nodeinfo.vm_capable and not nodeinfo.offline:
3143           additional_nodes.append(node)
3144           vf_node_info.append(self.all_node_info[node])
3145           break
3146       key = constants.NV_FILELIST
3147       vf_nvinfo.update(self.rpc.call_node_verify(additional_nodes,
3148                                                  {key: node_verify_param[key]},
3149                                                  self.cfg.GetClusterName()))
3150     else:
3151       vf_nvinfo = all_nvinfo
3152       vf_node_info = self.my_node_info.values()
3153
3154     self._VerifyFiles(_ErrorIf, vf_node_info, master_node, vf_nvinfo, filemap)
3155
3156     feedback_fn("* Verifying node status")
3157
3158     refos_img = None
3159
3160     for node_i in node_data_list:
3161       node = node_i.name
3162       nimg = node_image[node]
3163
3164       if node_i.offline:
3165         if verbose:
3166           feedback_fn("* Skipping offline node %s" % (node,))
3167         n_offline += 1
3168         continue
3169
3170       if node == master_node:
3171         ntype = "master"
3172       elif node_i.master_candidate:
3173         ntype = "master candidate"
3174       elif node_i.drained:
3175         ntype = "drained"
3176         n_drained += 1
3177       else:
3178         ntype = "regular"
3179       if verbose:
3180         feedback_fn("* Verifying node %s (%s)" % (node, ntype))
3181
3182       msg = all_nvinfo[node].fail_msg
3183       _ErrorIf(msg, constants.CV_ENODERPC, node, "while contacting node: %s",
3184                msg)
3185       if msg:
3186         nimg.rpc_fail = True
3187         continue
3188
3189       nresult = all_nvinfo[node].payload
3190
3191       nimg.call_ok = self._VerifyNode(node_i, nresult)
3192       self._VerifyNodeTime(node_i, nresult, nvinfo_starttime, nvinfo_endtime)
3193       self._VerifyNodeNetwork(node_i, nresult)
3194       self._VerifyNodeUserScripts(node_i, nresult)
3195       self._VerifyOob(node_i, nresult)
3196
3197       if nimg.vm_capable:
3198         self._VerifyNodeLVM(node_i, nresult, vg_name)
3199         self._VerifyNodeDrbd(node_i, nresult, self.all_inst_info, drbd_helper,
3200                              all_drbd_map)
3201
3202         self._UpdateNodeVolumes(node_i, nresult, nimg, vg_name)
3203         self._UpdateNodeInstances(node_i, nresult, nimg)
3204         self._UpdateNodeInfo(node_i, nresult, nimg, vg_name)
3205         self._UpdateNodeOS(node_i, nresult, nimg)
3206
3207         if not nimg.os_fail:
3208           if refos_img is None:
3209             refos_img = nimg
3210           self._VerifyNodeOS(node_i, nimg, refos_img)
3211         self._VerifyNodeBridges(node_i, nresult, bridges)
3212
3213         # Check whether all running instancies are primary for the node. (This
3214         # can no longer be done from _VerifyInstance below, since some of the
3215         # wrong instances could be from other node groups.)
3216         non_primary_inst = set(nimg.instances).difference(nimg.pinst)
3217
3218         for inst in non_primary_inst:
3219           # FIXME: investigate best way to handle offline insts
3220           if inst.admin_state == constants.ADMINST_OFFLINE:
3221             if verbose:
3222               feedback_fn("* Skipping offline instance %s" % inst.name)
3223             i_offline += 1
3224             continue
3225           test = inst in self.all_inst_info
3226           _ErrorIf(test, constants.CV_EINSTANCEWRONGNODE, inst,
3227                    "instance should not run on node %s", node_i.name)
3228           _ErrorIf(not test, constants.CV_ENODEORPHANINSTANCE, node_i.name,
3229                    "node is running unknown instance %s", inst)
3230
3231     for node, result in extra_lv_nvinfo.items():
3232       self._UpdateNodeVolumes(self.all_node_info[node], result.payload,
3233                               node_image[node], vg_name)
3234
3235     feedback_fn("* Verifying instance status")
3236     for instance in self.my_inst_names:
3237       if verbose:
3238         feedback_fn("* Verifying instance %s" % instance)
3239       inst_config = self.my_inst_info[instance]
3240       self._VerifyInstance(instance, inst_config, node_image,
3241                            instdisk[instance])
3242       inst_nodes_offline = []
3243
3244       pnode = inst_config.primary_node
3245       pnode_img = node_image[pnode]
3246       _ErrorIf(pnode_img.rpc_fail and not pnode_img.offline,
3247                constants.CV_ENODERPC, pnode, "instance %s, connection to"
3248                " primary node failed", instance)
3249
3250       _ErrorIf(inst_config.admin_state == constants.ADMINST_UP and
3251                pnode_img.offline,
3252                constants.CV_EINSTANCEBADNODE, instance,
3253                "instance is marked as running and lives on offline node %s",
3254                inst_config.primary_node)
3255
3256       # If the instance is non-redundant we cannot survive losing its primary
3257       # node, so we are not N+1 compliant. On the other hand we have no disk
3258       # templates with more than one secondary so that situation is not well
3259       # supported either.
3260       # FIXME: does not support file-backed instances
3261       if not inst_config.secondary_nodes:
3262         i_non_redundant.append(instance)
3263
3264       _ErrorIf(len(inst_config.secondary_nodes) > 1,
3265                constants.CV_EINSTANCELAYOUT,
3266                instance, "instance has multiple secondary nodes: %s",
3267                utils.CommaJoin(inst_config.secondary_nodes),
3268                code=self.ETYPE_WARNING)
3269
3270       if inst_config.disk_template in constants.DTS_INT_MIRROR:
3271         pnode = inst_config.primary_node
3272         instance_nodes = utils.NiceSort(inst_config.all_nodes)
3273         instance_groups = {}
3274
3275         for node in instance_nodes:
3276           instance_groups.setdefault(self.all_node_info[node].group,
3277                                      []).append(node)
3278
3279         pretty_list = [
3280           "%s (group %s)" % (utils.CommaJoin(nodes), groupinfo[group].name)
3281           # Sort so that we always list the primary node first.
3282           for group, nodes in sorted(instance_groups.items(),
3283                                      key=lambda (_, nodes): pnode in nodes,
3284                                      reverse=True)]
3285
3286         self._ErrorIf(len(instance_groups) > 1,
3287                       constants.CV_EINSTANCESPLITGROUPS,
3288                       instance, "instance has primary and secondary nodes in"
3289                       " different groups: %s", utils.CommaJoin(pretty_list),
3290                       code=self.ETYPE_WARNING)
3291
3292       if not cluster.FillBE(inst_config)[constants.BE_AUTO_BALANCE]:
3293         i_non_a_balanced.append(instance)
3294
3295       for snode in inst_config.secondary_nodes:
3296         s_img = node_image[snode]
3297         _ErrorIf(s_img.rpc_fail and not s_img.offline, constants.CV_ENODERPC,
3298                  snode, "instance %s, connection to secondary node failed",
3299                  instance)
3300
3301         if s_img.offline:
3302           inst_nodes_offline.append(snode)
3303
3304       # warn that the instance lives on offline nodes
3305       _ErrorIf(inst_nodes_offline, constants.CV_EINSTANCEBADNODE, instance,
3306                "instance has offline secondary node(s) %s",
3307                utils.CommaJoin(inst_nodes_offline))
3308       # ... or ghost/non-vm_capable nodes
3309       for node in inst_config.all_nodes:
3310         _ErrorIf(node_image[node].ghost, constants.CV_EINSTANCEBADNODE,
3311                  instance, "instance lives on ghost node %s", node)
3312         _ErrorIf(not node_image[node].vm_capable, constants.CV_EINSTANCEBADNODE,
3313                  instance, "instance lives on non-vm_capable node %s", node)
3314
3315     feedback_fn("* Verifying orphan volumes")
3316     reserved = utils.FieldSet(*cluster.reserved_lvs)
3317
3318     # We will get spurious "unknown volume" warnings if any node of this group
3319     # is secondary for an instance whose primary is in another group. To avoid
3320     # them, we find these instances and add their volumes to node_vol_should.
3321     for inst in self.all_inst_info.values():
3322       for secondary in inst.secondary_nodes:
3323         if (secondary in self.my_node_info
3324             and inst.name not in self.my_inst_info):
3325           inst.MapLVsByNode(node_vol_should)
3326           break
3327
3328     self._VerifyOrphanVolumes(node_vol_should, node_image, reserved)
3329
3330     if constants.VERIFY_NPLUSONE_MEM not in self.op.skip_checks:
3331       feedback_fn("* Verifying N+1 Memory redundancy")
3332       self._VerifyNPlusOneMemory(node_image, self.my_inst_info)
3333
3334     feedback_fn("* Other Notes")
3335     if i_non_redundant:
3336       feedback_fn("  - NOTICE: %d non-redundant instance(s) found."
3337                   % len(i_non_redundant))
3338
3339     if i_non_a_balanced:
3340       feedback_fn("  - NOTICE: %d non-auto-balanced instance(s) found."
3341                   % len(i_non_a_balanced))
3342
3343     if i_offline:
3344       feedback_fn("  - NOTICE: %d offline instance(s) found." % i_offline)
3345
3346     if n_offline:
3347       feedback_fn("  - NOTICE: %d offline node(s) found." % n_offline)
3348
3349     if n_drained:
3350       feedback_fn("  - NOTICE: %d drained node(s) found." % n_drained)
3351
3352     return not self.bad
3353
3354   def HooksCallBack(self, phase, hooks_results, feedback_fn, lu_result):
3355     """Analyze the post-hooks' result
3356
3357     This method analyses the hook result, handles it, and sends some
3358     nicely-formatted feedback back to the user.
3359
3360     @param phase: one of L{constants.HOOKS_PHASE_POST} or
3361         L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
3362     @param hooks_results: the results of the multi-node hooks rpc call
3363     @param feedback_fn: function used send feedback back to the caller
3364     @param lu_result: previous Exec result
3365     @return: the new Exec result, based on the previous result
3366         and hook results
3367
3368     """
3369     # We only really run POST phase hooks, only for non-empty groups,
3370     # and are only interested in their results
3371     if not self.my_node_names:
3372       # empty node group
3373       pass
3374     elif phase == constants.HOOKS_PHASE_POST:
3375       # Used to change hooks' output to proper indentation
3376       feedback_fn("* Hooks Results")
3377       assert hooks_results, "invalid result from hooks"
3378
3379       for node_name in hooks_results:
3380         res = hooks_results[node_name]
3381         msg = res.fail_msg
3382         test = msg and not res.offline
3383         self._ErrorIf(test, constants.CV_ENODEHOOKS, node_name,
3384                       "Communication failure in hooks execution: %s", msg)
3385         if res.offline or msg:
3386           # No need to investigate payload if node is offline or gave
3387           # an error.
3388           continue
3389         for script, hkr, output in res.payload:
3390           test = hkr == constants.HKR_FAIL
3391           self._ErrorIf(test, constants.CV_ENODEHOOKS, node_name,
3392                         "Script %s failed, output:", script)
3393           if test:
3394             output = self._HOOKS_INDENT_RE.sub("      ", output)
3395             feedback_fn("%s" % output)
3396             lu_result = False
3397
3398     return lu_result
3399
3400
3401 class LUClusterVerifyDisks(NoHooksLU):
3402   """Verifies the cluster disks status.
3403
3404   """
3405   REQ_BGL = False
3406
3407   def ExpandNames(self):
3408     self.share_locks = _ShareAll()
3409     self.needed_locks = {
3410       locking.LEVEL_NODEGROUP: locking.ALL_SET,
3411       }
3412
3413   def Exec(self, feedback_fn):
3414     group_names = self.owned_locks(locking.LEVEL_NODEGROUP)
3415
3416     # Submit one instance of L{opcodes.OpGroupVerifyDisks} per node group
3417     return ResultWithJobs([[opcodes.OpGroupVerifyDisks(group_name=group)]
3418                            for group in group_names])
3419
3420
3421 class LUGroupVerifyDisks(NoHooksLU):
3422   """Verifies the status of all disks in a node group.
3423
3424   """
3425   REQ_BGL = False
3426
3427   def ExpandNames(self):
3428     # Raises errors.OpPrereqError on its own if group can't be found
3429     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
3430
3431     self.share_locks = _ShareAll()
3432     self.needed_locks = {
3433       locking.LEVEL_INSTANCE: [],
3434       locking.LEVEL_NODEGROUP: [],
3435       locking.LEVEL_NODE: [],
3436       }
3437
3438   def DeclareLocks(self, level):
3439     if level == locking.LEVEL_INSTANCE:
3440       assert not self.needed_locks[locking.LEVEL_INSTANCE]
3441
3442       # Lock instances optimistically, needs verification once node and group
3443       # locks have been acquired
3444       self.needed_locks[locking.LEVEL_INSTANCE] = \
3445         self.cfg.GetNodeGroupInstances(self.group_uuid)
3446
3447     elif level == locking.LEVEL_NODEGROUP:
3448       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
3449
3450       self.needed_locks[locking.LEVEL_NODEGROUP] = \
3451         set([self.group_uuid] +
3452             # Lock all groups used by instances optimistically; this requires
3453             # going via the node before it's locked, requiring verification
3454             # later on
3455             [group_uuid
3456              for instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
3457              for group_uuid in self.cfg.GetInstanceNodeGroups(instance_name)])
3458
3459     elif level == locking.LEVEL_NODE:
3460       # This will only lock the nodes in the group to be verified which contain
3461       # actual instances
3462       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
3463       self._LockInstancesNodes()
3464
3465       # Lock all nodes in group to be verified
3466       assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
3467       member_nodes = self.cfg.GetNodeGroup(self.group_uuid).members
3468       self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
3469
3470   def CheckPrereq(self):
3471     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
3472     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
3473     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
3474
3475     assert self.group_uuid in owned_groups
3476
3477     # Check if locked instances are still correct
3478     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
3479
3480     # Get instance information
3481     self.instances = dict(self.cfg.GetMultiInstanceInfo(owned_instances))
3482
3483     # Check if node groups for locked instances are still correct
3484     for (instance_name, inst) in self.instances.items():
3485       assert owned_nodes.issuperset(inst.all_nodes), \
3486         "Instance %s's nodes changed while we kept the lock" % instance_name
3487
3488       inst_groups = _CheckInstanceNodeGroups(self.cfg, instance_name,
3489                                              owned_groups)
3490
3491       assert self.group_uuid in inst_groups, \
3492         "Instance %s has no node in group %s" % (instance_name, self.group_uuid)
3493
3494   def Exec(self, feedback_fn):
3495     """Verify integrity of cluster disks.
3496
3497     @rtype: tuple of three items
3498     @return: a tuple of (dict of node-to-node_error, list of instances
3499         which need activate-disks, dict of instance: (node, volume) for
3500         missing volumes
3501
3502     """
3503     res_nodes = {}
3504     res_instances = set()
3505     res_missing = {}
3506
3507     nv_dict = _MapInstanceDisksToNodes([inst
3508             for inst in self.instances.values()
3509             if inst.admin_state == constants.ADMINST_UP])
3510
3511     if nv_dict:
3512       nodes = utils.NiceSort(set(self.owned_locks(locking.LEVEL_NODE)) &
3513                              set(self.cfg.GetVmCapableNodeList()))
3514
3515       node_lvs = self.rpc.call_lv_list(nodes, [])
3516
3517       for (node, node_res) in node_lvs.items():
3518         if node_res.offline:
3519           continue
3520
3521         msg = node_res.fail_msg
3522         if msg:
3523           logging.warning("Error enumerating LVs on node %s: %s", node, msg)
3524           res_nodes[node] = msg
3525           continue
3526
3527         for lv_name, (_, _, lv_online) in node_res.payload.items():
3528           inst = nv_dict.pop((node, lv_name), None)
3529           if not (lv_online or inst is None):
3530             res_instances.add(inst)
3531
3532       # any leftover items in nv_dict are missing LVs, let's arrange the data
3533       # better
3534       for key, inst in nv_dict.iteritems():
3535         res_missing.setdefault(inst, []).append(list(key))
3536
3537     return (res_nodes, list(res_instances), res_missing)
3538
3539
3540 class LUClusterRepairDiskSizes(NoHooksLU):
3541   """Verifies the cluster disks sizes.
3542
3543   """
3544   REQ_BGL = False
3545
3546   def ExpandNames(self):
3547     if self.op.instances:
3548       self.wanted_names = _GetWantedInstances(self, self.op.instances)
3549       self.needed_locks = {
3550         locking.LEVEL_NODE_RES: [],
3551         locking.LEVEL_INSTANCE: self.wanted_names,
3552         }
3553       self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
3554     else:
3555       self.wanted_names = None
3556       self.needed_locks = {
3557         locking.LEVEL_NODE_RES: locking.ALL_SET,
3558         locking.LEVEL_INSTANCE: locking.ALL_SET,
3559         }
3560     self.share_locks = {
3561       locking.LEVEL_NODE_RES: 1,
3562       locking.LEVEL_INSTANCE: 0,
3563       }
3564
3565   def DeclareLocks(self, level):
3566     if level == locking.LEVEL_NODE_RES and self.wanted_names is not None:
3567       self._LockInstancesNodes(primary_only=True, level=level)
3568
3569   def CheckPrereq(self):
3570     """Check prerequisites.
3571
3572     This only checks the optional instance list against the existing names.
3573
3574     """
3575     if self.wanted_names is None:
3576       self.wanted_names = self.owned_locks(locking.LEVEL_INSTANCE)
3577
3578     self.wanted_instances = \
3579         map(compat.snd, self.cfg.GetMultiInstanceInfo(self.wanted_names))
3580
3581   def _EnsureChildSizes(self, disk):
3582     """Ensure children of the disk have the needed disk size.
3583
3584     This is valid mainly for DRBD8 and fixes an issue where the
3585     children have smaller disk size.
3586
3587     @param disk: an L{ganeti.objects.Disk} object
3588
3589     """
3590     if disk.dev_type == constants.LD_DRBD8:
3591       assert disk.children, "Empty children for DRBD8?"
3592       fchild = disk.children[0]
3593       mismatch = fchild.size < disk.size
3594       if mismatch:
3595         self.LogInfo("Child disk has size %d, parent %d, fixing",
3596                      fchild.size, disk.size)
3597         fchild.size = disk.size
3598
3599       # and we recurse on this child only, not on the metadev
3600       return self._EnsureChildSizes(fchild) or mismatch
3601     else:
3602       return False
3603
3604   def Exec(self, feedback_fn):
3605     """Verify the size of cluster disks.
3606
3607     """
3608     # TODO: check child disks too
3609     # TODO: check differences in size between primary/secondary nodes
3610     per_node_disks = {}
3611     for instance in self.wanted_instances:
3612       pnode = instance.primary_node
3613       if pnode not in per_node_disks:
3614         per_node_disks[pnode] = []
3615       for idx, disk in enumerate(instance.disks):
3616         per_node_disks[pnode].append((instance, idx, disk))
3617
3618     assert not (frozenset(per_node_disks.keys()) -
3619                 self.owned_locks(locking.LEVEL_NODE_RES)), \
3620       "Not owning correct locks"
3621     assert not self.owned_locks(locking.LEVEL_NODE)
3622
3623     changed = []
3624     for node, dskl in per_node_disks.items():
3625       newl = [v[2].Copy() for v in dskl]
3626       for dsk in newl:
3627         self.cfg.SetDiskID(dsk, node)
3628       result = self.rpc.call_blockdev_getsize(node, newl)
3629       if result.fail_msg:
3630         self.LogWarning("Failure in blockdev_getsize call to node"
3631                         " %s, ignoring", node)
3632         continue
3633       if len(result.payload) != len(dskl):
3634         logging.warning("Invalid result from node %s: len(dksl)=%d,"
3635                         " result.payload=%s", node, len(dskl), result.payload)
3636         self.LogWarning("Invalid result from node %s, ignoring node results",
3637                         node)
3638         continue
3639       for ((instance, idx, disk), size) in zip(dskl, result.payload):
3640         if size is None:
3641           self.LogWarning("Disk %d of instance %s did not return size"
3642                           " information, ignoring", idx, instance.name)
3643           continue
3644         if not isinstance(size, (int, long)):
3645           self.LogWarning("Disk %d of instance %s did not return valid"
3646                           " size information, ignoring", idx, instance.name)
3647           continue
3648         size = size >> 20
3649         if size != disk.size:
3650           self.LogInfo("Disk %d of instance %s has mismatched size,"
3651                        " correcting: recorded %d, actual %d", idx,
3652                        instance.name, disk.size, size)
3653           disk.size = size
3654           self.cfg.Update(instance, feedback_fn)
3655           changed.append((instance.name, idx, size))
3656         if self._EnsureChildSizes(disk):
3657           self.cfg.Update(instance, feedback_fn)
3658           changed.append((instance.name, idx, disk.size))
3659     return changed
3660
3661
3662 class LUClusterRename(LogicalUnit):
3663   """Rename the cluster.
3664
3665   """
3666   HPATH = "cluster-rename"
3667   HTYPE = constants.HTYPE_CLUSTER
3668
3669   def BuildHooksEnv(self):
3670     """Build hooks env.
3671
3672     """
3673     return {
3674       "OP_TARGET": self.cfg.GetClusterName(),
3675       "NEW_NAME": self.op.name,
3676       }
3677
3678   def BuildHooksNodes(self):
3679     """Build hooks nodes.
3680
3681     """
3682     return ([self.cfg.GetMasterNode()], self.cfg.GetNodeList())
3683
3684   def CheckPrereq(self):
3685     """Verify that the passed name is a valid one.
3686
3687     """
3688     hostname = netutils.GetHostname(name=self.op.name,
3689                                     family=self.cfg.GetPrimaryIPFamily())
3690
3691     new_name = hostname.name
3692     self.ip = new_ip = hostname.ip
3693     old_name = self.cfg.GetClusterName()
3694     old_ip = self.cfg.GetMasterIP()
3695     if new_name == old_name and new_ip == old_ip:
3696       raise errors.OpPrereqError("Neither the name nor the IP address of the"
3697                                  " cluster has changed",
3698                                  errors.ECODE_INVAL)
3699     if new_ip != old_ip:
3700       if netutils.TcpPing(new_ip, constants.DEFAULT_NODED_PORT):
3701         raise errors.OpPrereqError("The given cluster IP address (%s) is"
3702                                    " reachable on the network" %
3703                                    new_ip, errors.ECODE_NOTUNIQUE)
3704
3705     self.op.name = new_name
3706
3707   def Exec(self, feedback_fn):
3708     """Rename the cluster.
3709
3710     """
3711     clustername = self.op.name
3712     new_ip = self.ip
3713
3714     # shutdown the master IP
3715     master_params = self.cfg.GetMasterNetworkParameters()
3716     ems = self.cfg.GetUseExternalMipScript()
3717     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
3718                                                      master_params, ems)
3719     result.Raise("Could not disable the master role")
3720
3721     try:
3722       cluster = self.cfg.GetClusterInfo()
3723       cluster.cluster_name = clustername
3724       cluster.master_ip = new_ip
3725       self.cfg.Update(cluster, feedback_fn)
3726
3727       # update the known hosts file
3728       ssh.WriteKnownHostsFile(self.cfg, constants.SSH_KNOWN_HOSTS_FILE)
3729       node_list = self.cfg.GetOnlineNodeList()
3730       try:
3731         node_list.remove(master_params.name)
3732       except ValueError:
3733         pass
3734       _UploadHelper(self, node_list, constants.SSH_KNOWN_HOSTS_FILE)
3735     finally:
3736       master_params.ip = new_ip
3737       result = self.rpc.call_node_activate_master_ip(master_params.name,
3738                                                      master_params, ems)
3739       msg = result.fail_msg
3740       if msg:
3741         self.LogWarning("Could not re-enable the master role on"
3742                         " the master, please restart manually: %s", msg)
3743
3744     return clustername
3745
3746
3747 def _ValidateNetmask(cfg, netmask):
3748   """Checks if a netmask is valid.
3749
3750   @type cfg: L{config.ConfigWriter}
3751   @param cfg: The cluster configuration
3752   @type netmask: int
3753   @param netmask: the netmask to be verified
3754   @raise errors.OpPrereqError: if the validation fails
3755
3756   """
3757   ip_family = cfg.GetPrimaryIPFamily()
3758   try:
3759     ipcls = netutils.IPAddress.GetClassFromIpFamily(ip_family)
3760   except errors.ProgrammerError:
3761     raise errors.OpPrereqError("Invalid primary ip family: %s." %
3762                                ip_family)
3763   if not ipcls.ValidateNetmask(netmask):
3764     raise errors.OpPrereqError("CIDR netmask (%s) not valid" %
3765                                 (netmask))
3766
3767
3768 class LUClusterSetParams(LogicalUnit):
3769   """Change the parameters of the cluster.
3770
3771   """
3772   HPATH = "cluster-modify"
3773   HTYPE = constants.HTYPE_CLUSTER
3774   REQ_BGL = False
3775
3776   def CheckArguments(self):
3777     """Check parameters
3778
3779     """
3780     if self.op.uid_pool:
3781       uidpool.CheckUidPool(self.op.uid_pool)
3782
3783     if self.op.add_uids:
3784       uidpool.CheckUidPool(self.op.add_uids)
3785
3786     if self.op.remove_uids:
3787       uidpool.CheckUidPool(self.op.remove_uids)
3788
3789     if self.op.master_netmask is not None:
3790       _ValidateNetmask(self.cfg, self.op.master_netmask)
3791
3792     if self.op.diskparams:
3793       for dt_params in self.op.diskparams.values():
3794         utils.ForceDictType(dt_params, constants.DISK_DT_TYPES)
3795
3796   def ExpandNames(self):
3797     # FIXME: in the future maybe other cluster params won't require checking on
3798     # all nodes to be modified.
3799     self.needed_locks = {
3800       locking.LEVEL_NODE: locking.ALL_SET,
3801       locking.LEVEL_INSTANCE: locking.ALL_SET,
3802       locking.LEVEL_NODEGROUP: locking.ALL_SET,
3803     }
3804     self.share_locks = {
3805         locking.LEVEL_NODE: 1,
3806         locking.LEVEL_INSTANCE: 1,
3807         locking.LEVEL_NODEGROUP: 1,
3808     }
3809
3810   def BuildHooksEnv(self):
3811     """Build hooks env.
3812
3813     """
3814     return {
3815       "OP_TARGET": self.cfg.GetClusterName(),
3816       "NEW_VG_NAME": self.op.vg_name,
3817       }
3818
3819   def BuildHooksNodes(self):
3820     """Build hooks nodes.
3821
3822     """
3823     mn = self.cfg.GetMasterNode()
3824     return ([mn], [mn])
3825
3826   def CheckPrereq(self):
3827     """Check prerequisites.
3828
3829     This checks whether the given params don't conflict and
3830     if the given volume group is valid.
3831
3832     """
3833     if self.op.vg_name is not None and not self.op.vg_name:
3834       if self.cfg.HasAnyDiskOfType(constants.LD_LV):
3835         raise errors.OpPrereqError("Cannot disable lvm storage while lvm-based"
3836                                    " instances exist", errors.ECODE_INVAL)
3837
3838     if self.op.drbd_helper is not None and not self.op.drbd_helper:
3839       if self.cfg.HasAnyDiskOfType(constants.LD_DRBD8):
3840         raise errors.OpPrereqError("Cannot disable drbd helper while"
3841                                    " drbd-based instances exist",
3842                                    errors.ECODE_INVAL)
3843
3844     node_list = self.owned_locks(locking.LEVEL_NODE)
3845
3846     # if vg_name not None, checks given volume group on all nodes
3847     if self.op.vg_name:
3848       vglist = self.rpc.call_vg_list(node_list)
3849       for node in node_list:
3850         msg = vglist[node].fail_msg
3851         if msg:
3852           # ignoring down node
3853           self.LogWarning("Error while gathering data on node %s"
3854                           " (ignoring node): %s", node, msg)
3855           continue
3856         vgstatus = utils.CheckVolumeGroupSize(vglist[node].payload,
3857                                               self.op.vg_name,
3858                                               constants.MIN_VG_SIZE)
3859         if vgstatus:
3860           raise errors.OpPrereqError("Error on node '%s': %s" %
3861                                      (node, vgstatus), errors.ECODE_ENVIRON)
3862
3863     if self.op.drbd_helper:
3864       # checks given drbd helper on all nodes
3865       helpers = self.rpc.call_drbd_helper(node_list)
3866       for (node, ninfo) in self.cfg.GetMultiNodeInfo(node_list):
3867         if ninfo.offline:
3868           self.LogInfo("Not checking drbd helper on offline node %s", node)
3869           continue
3870         msg = helpers[node].fail_msg
3871         if msg:
3872           raise errors.OpPrereqError("Error checking drbd helper on node"
3873                                      " '%s': %s" % (node, msg),
3874                                      errors.ECODE_ENVIRON)
3875         node_helper = helpers[node].payload
3876         if node_helper != self.op.drbd_helper:
3877           raise errors.OpPrereqError("Error on node '%s': drbd helper is %s" %
3878                                      (node, node_helper), errors.ECODE_ENVIRON)
3879
3880     self.cluster = cluster = self.cfg.GetClusterInfo()
3881     # validate params changes
3882     if self.op.beparams:
3883       objects.UpgradeBeParams(self.op.beparams)
3884       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
3885       self.new_beparams = cluster.SimpleFillBE(self.op.beparams)
3886
3887     if self.op.ndparams:
3888       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
3889       self.new_ndparams = cluster.SimpleFillND(self.op.ndparams)
3890
3891       # TODO: we need a more general way to handle resetting
3892       # cluster-level parameters to default values
3893       if self.new_ndparams["oob_program"] == "":
3894         self.new_ndparams["oob_program"] = \
3895             constants.NDC_DEFAULTS[constants.ND_OOB_PROGRAM]
3896
3897     if self.op.hv_state:
3898       new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
3899                                             self.cluster.hv_state_static)
3900       self.new_hv_state = dict((hv, cluster.SimpleFillHvState(values))
3901                                for hv, values in new_hv_state.items())
3902
3903     if self.op.disk_state:
3904       new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state,
3905                                                 self.cluster.disk_state_static)
3906       self.new_disk_state = \
3907         dict((storage, dict((name, cluster.SimpleFillDiskState(values))
3908                             for name, values in svalues.items()))
3909              for storage, svalues in new_disk_state.items())
3910
3911     if self.op.ipolicy:
3912       self.new_ipolicy = _GetUpdatedIPolicy(cluster.ipolicy, self.op.ipolicy,
3913                                             group_policy=False)
3914
3915       all_instances = self.cfg.GetAllInstancesInfo().values()
3916       violations = set()
3917       for group in self.cfg.GetAllNodeGroupsInfo().values():
3918         instances = frozenset([inst for inst in all_instances
3919                                if compat.any(node in group.members
3920                                              for node in inst.all_nodes)])
3921         new_ipolicy = objects.FillIPolicy(self.new_ipolicy, group.ipolicy)
3922         new = _ComputeNewInstanceViolations(_CalculateGroupIPolicy(cluster,
3923                                                                    group),
3924                                             new_ipolicy, instances)
3925         if new:
3926           violations.update(new)
3927
3928       if violations:
3929         self.LogWarning("After the ipolicy change the following instances"
3930                         " violate them: %s",
3931                         utils.CommaJoin(violations))
3932
3933     if self.op.nicparams:
3934       utils.ForceDictType(self.op.nicparams, constants.NICS_PARAMETER_TYPES)
3935       self.new_nicparams = cluster.SimpleFillNIC(self.op.nicparams)
3936       objects.NIC.CheckParameterSyntax(self.new_nicparams)
3937       nic_errors = []
3938
3939       # check all instances for consistency
3940       for instance in self.cfg.GetAllInstancesInfo().values():
3941         for nic_idx, nic in enumerate(instance.nics):
3942           params_copy = copy.deepcopy(nic.nicparams)
3943           params_filled = objects.FillDict(self.new_nicparams, params_copy)
3944
3945           # check parameter syntax
3946           try:
3947             objects.NIC.CheckParameterSyntax(params_filled)
3948           except errors.ConfigurationError, err:
3949             nic_errors.append("Instance %s, nic/%d: %s" %
3950                               (instance.name, nic_idx, err))
3951
3952           # if we're moving instances to routed, check that they have an ip
3953           target_mode = params_filled[constants.NIC_MODE]
3954           if target_mode == constants.NIC_MODE_ROUTED and not nic.ip:
3955             nic_errors.append("Instance %s, nic/%d: routed NIC with no ip"
3956                               " address" % (instance.name, nic_idx))
3957       if nic_errors:
3958         raise errors.OpPrereqError("Cannot apply the change, errors:\n%s" %
3959                                    "\n".join(nic_errors))
3960
3961     # hypervisor list/parameters
3962     self.new_hvparams = new_hvp = objects.FillDict(cluster.hvparams, {})
3963     if self.op.hvparams:
3964       for hv_name, hv_dict in self.op.hvparams.items():
3965         if hv_name not in self.new_hvparams:
3966           self.new_hvparams[hv_name] = hv_dict
3967         else:
3968           self.new_hvparams[hv_name].update(hv_dict)
3969
3970     # disk template parameters
3971     self.new_diskparams = objects.FillDict(cluster.diskparams, {})
3972     if self.op.diskparams:
3973       for dt_name, dt_params in self.op.diskparams.items():
3974         if dt_name not in self.op.diskparams:
3975           self.new_diskparams[dt_name] = dt_params
3976         else:
3977           self.new_diskparams[dt_name].update(dt_params)
3978
3979     # os hypervisor parameters
3980     self.new_os_hvp = objects.FillDict(cluster.os_hvp, {})
3981     if self.op.os_hvp:
3982       for os_name, hvs in self.op.os_hvp.items():
3983         if os_name not in self.new_os_hvp:
3984           self.new_os_hvp[os_name] = hvs
3985         else:
3986           for hv_name, hv_dict in hvs.items():
3987             if hv_name not in self.new_os_hvp[os_name]:
3988               self.new_os_hvp[os_name][hv_name] = hv_dict
3989             else:
3990               self.new_os_hvp[os_name][hv_name].update(hv_dict)
3991
3992     # os parameters
3993     self.new_osp = objects.FillDict(cluster.osparams, {})
3994     if self.op.osparams:
3995       for os_name, osp in self.op.osparams.items():
3996         if os_name not in self.new_osp:
3997           self.new_osp[os_name] = {}
3998
3999         self.new_osp[os_name] = _GetUpdatedParams(self.new_osp[os_name], osp,
4000                                                   use_none=True)
4001
4002         if not self.new_osp[os_name]:
4003           # we removed all parameters
4004           del self.new_osp[os_name]
4005         else:
4006           # check the parameter validity (remote check)
4007           _CheckOSParams(self, False, [self.cfg.GetMasterNode()],
4008                          os_name, self.new_osp[os_name])
4009
4010     # changes to the hypervisor list
4011     if self.op.enabled_hypervisors is not None:
4012       self.hv_list = self.op.enabled_hypervisors
4013       for hv in self.hv_list:
4014         # if the hypervisor doesn't already exist in the cluster
4015         # hvparams, we initialize it to empty, and then (in both
4016         # cases) we make sure to fill the defaults, as we might not
4017         # have a complete defaults list if the hypervisor wasn't
4018         # enabled before
4019         if hv not in new_hvp:
4020           new_hvp[hv] = {}
4021         new_hvp[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], new_hvp[hv])
4022         utils.ForceDictType(new_hvp[hv], constants.HVS_PARAMETER_TYPES)
4023     else:
4024       self.hv_list = cluster.enabled_hypervisors
4025
4026     if self.op.hvparams or self.op.enabled_hypervisors is not None:
4027       # either the enabled list has changed, or the parameters have, validate
4028       for hv_name, hv_params in self.new_hvparams.items():
4029         if ((self.op.hvparams and hv_name in self.op.hvparams) or
4030             (self.op.enabled_hypervisors and
4031              hv_name in self.op.enabled_hypervisors)):
4032           # either this is a new hypervisor, or its parameters have changed
4033           hv_class = hypervisor.GetHypervisor(hv_name)
4034           utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
4035           hv_class.CheckParameterSyntax(hv_params)
4036           _CheckHVParams(self, node_list, hv_name, hv_params)
4037
4038     if self.op.os_hvp:
4039       # no need to check any newly-enabled hypervisors, since the
4040       # defaults have already been checked in the above code-block
4041       for os_name, os_hvp in self.new_os_hvp.items():
4042         for hv_name, hv_params in os_hvp.items():
4043           utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
4044           # we need to fill in the new os_hvp on top of the actual hv_p
4045           cluster_defaults = self.new_hvparams.get(hv_name, {})
4046           new_osp = objects.FillDict(cluster_defaults, hv_params)
4047           hv_class = hypervisor.GetHypervisor(hv_name)
4048           hv_class.CheckParameterSyntax(new_osp)
4049           _CheckHVParams(self, node_list, hv_name, new_osp)
4050
4051     if self.op.default_iallocator:
4052       alloc_script = utils.FindFile(self.op.default_iallocator,
4053                                     constants.IALLOCATOR_SEARCH_PATH,
4054                                     os.path.isfile)
4055       if alloc_script is None:
4056         raise errors.OpPrereqError("Invalid default iallocator script '%s'"
4057                                    " specified" % self.op.default_iallocator,
4058                                    errors.ECODE_INVAL)
4059
4060   def Exec(self, feedback_fn):
4061     """Change the parameters of the cluster.
4062
4063     """
4064     if self.op.vg_name is not None:
4065       new_volume = self.op.vg_name
4066       if not new_volume:
4067         new_volume = None
4068       if new_volume != self.cfg.GetVGName():
4069         self.cfg.SetVGName(new_volume)
4070       else:
4071         feedback_fn("Cluster LVM configuration already in desired"
4072                     " state, not changing")
4073     if self.op.drbd_helper is not None:
4074       new_helper = self.op.drbd_helper
4075       if not new_helper:
4076         new_helper = None
4077       if new_helper != self.cfg.GetDRBDHelper():
4078         self.cfg.SetDRBDHelper(new_helper)
4079       else:
4080         feedback_fn("Cluster DRBD helper already in desired state,"
4081                     " not changing")
4082     if self.op.hvparams:
4083       self.cluster.hvparams = self.new_hvparams
4084     if self.op.os_hvp:
4085       self.cluster.os_hvp = self.new_os_hvp
4086     if self.op.enabled_hypervisors is not None:
4087       self.cluster.hvparams = self.new_hvparams
4088       self.cluster.enabled_hypervisors = self.op.enabled_hypervisors
4089     if self.op.beparams:
4090       self.cluster.beparams[constants.PP_DEFAULT] = self.new_beparams
4091     if self.op.nicparams:
4092       self.cluster.nicparams[constants.PP_DEFAULT] = self.new_nicparams
4093     if self.op.ipolicy:
4094       self.cluster.ipolicy = self.new_ipolicy
4095     if self.op.osparams:
4096       self.cluster.osparams = self.new_osp
4097     if self.op.ndparams:
4098       self.cluster.ndparams = self.new_ndparams
4099     if self.op.diskparams:
4100       self.cluster.diskparams = self.new_diskparams
4101     if self.op.hv_state:
4102       self.cluster.hv_state_static = self.new_hv_state
4103     if self.op.disk_state:
4104       self.cluster.disk_state_static = self.new_disk_state
4105
4106     if self.op.candidate_pool_size is not None:
4107       self.cluster.candidate_pool_size = self.op.candidate_pool_size
4108       # we need to update the pool size here, otherwise the save will fail
4109       _AdjustCandidatePool(self, [])
4110
4111     if self.op.maintain_node_health is not None:
4112       if self.op.maintain_node_health and not constants.ENABLE_CONFD:
4113         feedback_fn("Note: CONFD was disabled at build time, node health"
4114                     " maintenance is not useful (still enabling it)")
4115       self.cluster.maintain_node_health = self.op.maintain_node_health
4116
4117     if self.op.prealloc_wipe_disks is not None:
4118       self.cluster.prealloc_wipe_disks = self.op.prealloc_wipe_disks
4119
4120     if self.op.add_uids is not None:
4121       uidpool.AddToUidPool(self.cluster.uid_pool, self.op.add_uids)
4122
4123     if self.op.remove_uids is not None:
4124       uidpool.RemoveFromUidPool(self.cluster.uid_pool, self.op.remove_uids)
4125
4126     if self.op.uid_pool is not None:
4127       self.cluster.uid_pool = self.op.uid_pool
4128
4129     if self.op.default_iallocator is not None:
4130       self.cluster.default_iallocator = self.op.default_iallocator
4131
4132     if self.op.reserved_lvs is not None:
4133       self.cluster.reserved_lvs = self.op.reserved_lvs
4134
4135     if self.op.use_external_mip_script is not None:
4136       self.cluster.use_external_mip_script = self.op.use_external_mip_script
4137
4138     def helper_os(aname, mods, desc):
4139       desc += " OS list"
4140       lst = getattr(self.cluster, aname)
4141       for key, val in mods:
4142         if key == constants.DDM_ADD:
4143           if val in lst:
4144             feedback_fn("OS %s already in %s, ignoring" % (val, desc))
4145           else:
4146             lst.append(val)
4147         elif key == constants.DDM_REMOVE:
4148           if val in lst:
4149             lst.remove(val)
4150           else:
4151             feedback_fn("OS %s not found in %s, ignoring" % (val, desc))
4152         else:
4153           raise errors.ProgrammerError("Invalid modification '%s'" % key)
4154
4155     if self.op.hidden_os:
4156       helper_os("hidden_os", self.op.hidden_os, "hidden")
4157
4158     if self.op.blacklisted_os:
4159       helper_os("blacklisted_os", self.op.blacklisted_os, "blacklisted")
4160
4161     if self.op.master_netdev:
4162       master_params = self.cfg.GetMasterNetworkParameters()
4163       ems = self.cfg.GetUseExternalMipScript()
4164       feedback_fn("Shutting down master ip on the current netdev (%s)" %
4165                   self.cluster.master_netdev)
4166       result = self.rpc.call_node_deactivate_master_ip(master_params.name,
4167                                                        master_params, ems)
4168       result.Raise("Could not disable the master ip")
4169       feedback_fn("Changing master_netdev from %s to %s" %
4170                   (master_params.netdev, self.op.master_netdev))
4171       self.cluster.master_netdev = self.op.master_netdev
4172
4173     if self.op.master_netmask:
4174       master_params = self.cfg.GetMasterNetworkParameters()
4175       feedback_fn("Changing master IP netmask to %s" % self.op.master_netmask)
4176       result = self.rpc.call_node_change_master_netmask(master_params.name,
4177                                                         master_params.netmask,
4178                                                         self.op.master_netmask,
4179                                                         master_params.ip,
4180                                                         master_params.netdev)
4181       if result.fail_msg:
4182         msg = "Could not change the master IP netmask: %s" % result.fail_msg
4183         feedback_fn(msg)
4184
4185       self.cluster.master_netmask = self.op.master_netmask
4186
4187     self.cfg.Update(self.cluster, feedback_fn)
4188
4189     if self.op.master_netdev:
4190       master_params = self.cfg.GetMasterNetworkParameters()
4191       feedback_fn("Starting the master ip on the new master netdev (%s)" %
4192                   self.op.master_netdev)
4193       ems = self.cfg.GetUseExternalMipScript()
4194       result = self.rpc.call_node_activate_master_ip(master_params.name,
4195                                                      master_params, ems)
4196       if result.fail_msg:
4197         self.LogWarning("Could not re-enable the master ip on"
4198                         " the master, please restart manually: %s",
4199                         result.fail_msg)
4200
4201
4202 def _UploadHelper(lu, nodes, fname):
4203   """Helper for uploading a file and showing warnings.
4204
4205   """
4206   if os.path.exists(fname):
4207     result = lu.rpc.call_upload_file(nodes, fname)
4208     for to_node, to_result in result.items():
4209       msg = to_result.fail_msg
4210       if msg:
4211         msg = ("Copy of file %s to node %s failed: %s" %
4212                (fname, to_node, msg))
4213         lu.proc.LogWarning(msg)
4214
4215
4216 def _ComputeAncillaryFiles(cluster, redist):
4217   """Compute files external to Ganeti which need to be consistent.
4218
4219   @type redist: boolean
4220   @param redist: Whether to include files which need to be redistributed
4221
4222   """
4223   # Compute files for all nodes
4224   files_all = set([
4225     constants.SSH_KNOWN_HOSTS_FILE,
4226     constants.CONFD_HMAC_KEY,
4227     constants.CLUSTER_DOMAIN_SECRET_FILE,
4228     constants.SPICE_CERT_FILE,
4229     constants.SPICE_CACERT_FILE,
4230     constants.RAPI_USERS_FILE,
4231     ])
4232
4233   if not redist:
4234     files_all.update(constants.ALL_CERT_FILES)
4235     files_all.update(ssconf.SimpleStore().GetFileList())
4236   else:
4237     # we need to ship at least the RAPI certificate
4238     files_all.add(constants.RAPI_CERT_FILE)
4239
4240   if cluster.modify_etc_hosts:
4241     files_all.add(constants.ETC_HOSTS)
4242
4243   # Files which are optional, these must:
4244   # - be present in one other category as well
4245   # - either exist or not exist on all nodes of that category (mc, vm all)
4246   files_opt = set([
4247     constants.RAPI_USERS_FILE,
4248     ])
4249
4250   # Files which should only be on master candidates
4251   files_mc = set()
4252
4253   if not redist:
4254     files_mc.add(constants.CLUSTER_CONF_FILE)
4255
4256     # FIXME: this should also be replicated but Ganeti doesn't support files_mc
4257     # replication
4258     files_mc.add(constants.DEFAULT_MASTER_SETUP_SCRIPT)
4259
4260   # Files which should only be on VM-capable nodes
4261   files_vm = set(filename
4262     for hv_name in cluster.enabled_hypervisors
4263     for filename in hypervisor.GetHypervisor(hv_name).GetAncillaryFiles()[0])
4264
4265   files_opt |= set(filename
4266     for hv_name in cluster.enabled_hypervisors
4267     for filename in hypervisor.GetHypervisor(hv_name).GetAncillaryFiles()[1])
4268
4269   # Filenames in each category must be unique
4270   all_files_set = files_all | files_mc | files_vm
4271   assert (len(all_files_set) ==
4272           sum(map(len, [files_all, files_mc, files_vm]))), \
4273          "Found file listed in more than one file list"
4274
4275   # Optional files must be present in one other category
4276   assert all_files_set.issuperset(files_opt), \
4277          "Optional file not in a different required list"
4278
4279   return (files_all, files_opt, files_mc, files_vm)
4280
4281
4282 def _RedistributeAncillaryFiles(lu, additional_nodes=None, additional_vm=True):
4283   """Distribute additional files which are part of the cluster configuration.
4284
4285   ConfigWriter takes care of distributing the config and ssconf files, but
4286   there are more files which should be distributed to all nodes. This function
4287   makes sure those are copied.
4288
4289   @param lu: calling logical unit
4290   @param additional_nodes: list of nodes not in the config to distribute to
4291   @type additional_vm: boolean
4292   @param additional_vm: whether the additional nodes are vm-capable or not
4293
4294   """
4295   # Gather target nodes
4296   cluster = lu.cfg.GetClusterInfo()
4297   master_info = lu.cfg.GetNodeInfo(lu.cfg.GetMasterNode())
4298
4299   online_nodes = lu.cfg.GetOnlineNodeList()
4300   vm_nodes = lu.cfg.GetVmCapableNodeList()
4301
4302   if additional_nodes is not None:
4303     online_nodes.extend(additional_nodes)
4304     if additional_vm:
4305       vm_nodes.extend(additional_nodes)
4306
4307   # Never distribute to master node
4308   for nodelist in [online_nodes, vm_nodes]:
4309     if master_info.name in nodelist:
4310       nodelist.remove(master_info.name)
4311
4312   # Gather file lists
4313   (files_all, _, files_mc, files_vm) = \
4314     _ComputeAncillaryFiles(cluster, True)
4315
4316   # Never re-distribute configuration file from here
4317   assert not (constants.CLUSTER_CONF_FILE in files_all or
4318               constants.CLUSTER_CONF_FILE in files_vm)
4319   assert not files_mc, "Master candidates not handled in this function"
4320
4321   filemap = [
4322     (online_nodes, files_all),
4323     (vm_nodes, files_vm),
4324     ]
4325
4326   # Upload the files
4327   for (node_list, files) in filemap:
4328     for fname in files:
4329       _UploadHelper(lu, node_list, fname)
4330
4331
4332 class LUClusterRedistConf(NoHooksLU):
4333   """Force the redistribution of cluster configuration.
4334
4335   This is a very simple LU.
4336
4337   """
4338   REQ_BGL = False
4339
4340   def ExpandNames(self):
4341     self.needed_locks = {
4342       locking.LEVEL_NODE: locking.ALL_SET,
4343     }
4344     self.share_locks[locking.LEVEL_NODE] = 1
4345
4346   def Exec(self, feedback_fn):
4347     """Redistribute the configuration.
4348
4349     """
4350     self.cfg.Update(self.cfg.GetClusterInfo(), feedback_fn)
4351     _RedistributeAncillaryFiles(self)
4352
4353
4354 class LUClusterActivateMasterIp(NoHooksLU):
4355   """Activate the master IP on the master node.
4356
4357   """
4358   def Exec(self, feedback_fn):
4359     """Activate the master IP.
4360
4361     """
4362     master_params = self.cfg.GetMasterNetworkParameters()
4363     ems = self.cfg.GetUseExternalMipScript()
4364     result = self.rpc.call_node_activate_master_ip(master_params.name,
4365                                                    master_params, ems)
4366     result.Raise("Could not activate the master IP")
4367
4368
4369 class LUClusterDeactivateMasterIp(NoHooksLU):
4370   """Deactivate the master IP on the master node.
4371
4372   """
4373   def Exec(self, feedback_fn):
4374     """Deactivate the master IP.
4375
4376     """
4377     master_params = self.cfg.GetMasterNetworkParameters()
4378     ems = self.cfg.GetUseExternalMipScript()
4379     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
4380                                                      master_params, ems)
4381     result.Raise("Could not deactivate the master IP")
4382
4383
4384 def _WaitForSync(lu, instance, disks=None, oneshot=False):
4385   """Sleep and poll for an instance's disk to sync.
4386
4387   """
4388   if not instance.disks or disks is not None and not disks:
4389     return True
4390
4391   disks = _ExpandCheckDisks(instance, disks)
4392
4393   if not oneshot:
4394     lu.proc.LogInfo("Waiting for instance %s to sync disks." % instance.name)
4395
4396   node = instance.primary_node
4397
4398   for dev in disks:
4399     lu.cfg.SetDiskID(dev, node)
4400
4401   # TODO: Convert to utils.Retry
4402
4403   retries = 0
4404   degr_retries = 10 # in seconds, as we sleep 1 second each time
4405   while True:
4406     max_time = 0
4407     done = True
4408     cumul_degraded = False
4409     rstats = lu.rpc.call_blockdev_getmirrorstatus(node, disks)
4410     msg = rstats.fail_msg
4411     if msg:
4412       lu.LogWarning("Can't get any data from node %s: %s", node, msg)
4413       retries += 1
4414       if retries >= 10:
4415         raise errors.RemoteError("Can't contact node %s for mirror data,"
4416                                  " aborting." % node)
4417       time.sleep(6)
4418       continue
4419     rstats = rstats.payload
4420     retries = 0
4421     for i, mstat in enumerate(rstats):
4422       if mstat is None:
4423         lu.LogWarning("Can't compute data for node %s/%s",
4424                            node, disks[i].iv_name)
4425         continue
4426
4427       cumul_degraded = (cumul_degraded or
4428                         (mstat.is_degraded and mstat.sync_percent is None))
4429       if mstat.sync_percent is not None:
4430         done = False
4431         if mstat.estimated_time is not None:
4432           rem_time = ("%s remaining (estimated)" %
4433                       utils.FormatSeconds(mstat.estimated_time))
4434           max_time = mstat.estimated_time
4435         else:
4436           rem_time = "no time estimate"
4437         lu.proc.LogInfo("- device %s: %5.2f%% done, %s" %
4438                         (disks[i].iv_name, mstat.sync_percent, rem_time))
4439
4440     # if we're done but degraded, let's do a few small retries, to
4441     # make sure we see a stable and not transient situation; therefore
4442     # we force restart of the loop
4443     if (done or oneshot) and cumul_degraded and degr_retries > 0:
4444       logging.info("Degraded disks found, %d retries left", degr_retries)
4445       degr_retries -= 1
4446       time.sleep(1)
4447       continue
4448
4449     if done or oneshot:
4450       break
4451
4452     time.sleep(min(60, max_time))
4453
4454   if done:
4455     lu.proc.LogInfo("Instance %s's disks are in sync." % instance.name)
4456   return not cumul_degraded
4457
4458
4459 def _CheckDiskConsistency(lu, dev, node, on_primary, ldisk=False):
4460   """Check that mirrors are not degraded.
4461
4462   The ldisk parameter, if True, will change the test from the
4463   is_degraded attribute (which represents overall non-ok status for
4464   the device(s)) to the ldisk (representing the local storage status).
4465
4466   """
4467   lu.cfg.SetDiskID(dev, node)
4468
4469   result = True
4470
4471   if on_primary or dev.AssembleOnSecondary():
4472     rstats = lu.rpc.call_blockdev_find(node, dev)
4473     msg = rstats.fail_msg
4474     if msg:
4475       lu.LogWarning("Can't find disk on node %s: %s", node, msg)
4476       result = False
4477     elif not rstats.payload:
4478       lu.LogWarning("Can't find disk on node %s", node)
4479       result = False
4480     else:
4481       if ldisk:
4482         result = result and rstats.payload.ldisk_status == constants.LDS_OKAY
4483       else:
4484         result = result and not rstats.payload.is_degraded
4485
4486   if dev.children:
4487     for child in dev.children:
4488       result = result and _CheckDiskConsistency(lu, child, node, on_primary)
4489
4490   return result
4491
4492
4493 class LUOobCommand(NoHooksLU):
4494   """Logical unit for OOB handling.
4495
4496   """
4497   REG_BGL = False
4498   _SKIP_MASTER = (constants.OOB_POWER_OFF, constants.OOB_POWER_CYCLE)
4499
4500   def ExpandNames(self):
4501     """Gather locks we need.
4502
4503     """
4504     if self.op.node_names:
4505       self.op.node_names = _GetWantedNodes(self, self.op.node_names)
4506       lock_names = self.op.node_names
4507     else:
4508       lock_names = locking.ALL_SET
4509
4510     self.needed_locks = {
4511       locking.LEVEL_NODE: lock_names,
4512       }
4513
4514   def CheckPrereq(self):
4515     """Check prerequisites.
4516
4517     This checks:
4518      - the node exists in the configuration
4519      - OOB is supported
4520
4521     Any errors are signaled by raising errors.OpPrereqError.
4522
4523     """
4524     self.nodes = []
4525     self.master_node = self.cfg.GetMasterNode()
4526
4527     assert self.op.power_delay >= 0.0
4528
4529     if self.op.node_names:
4530       if (self.op.command in self._SKIP_MASTER and
4531           self.master_node in self.op.node_names):
4532         master_node_obj = self.cfg.GetNodeInfo(self.master_node)
4533         master_oob_handler = _SupportsOob(self.cfg, master_node_obj)
4534
4535         if master_oob_handler:
4536           additional_text = ("run '%s %s %s' if you want to operate on the"
4537                              " master regardless") % (master_oob_handler,
4538                                                       self.op.command,
4539                                                       self.master_node)
4540         else:
4541           additional_text = "it does not support out-of-band operations"
4542
4543         raise errors.OpPrereqError(("Operating on the master node %s is not"
4544                                     " allowed for %s; %s") %
4545                                    (self.master_node, self.op.command,
4546                                     additional_text), errors.ECODE_INVAL)
4547     else:
4548       self.op.node_names = self.cfg.GetNodeList()
4549       if self.op.command in self._SKIP_MASTER:
4550         self.op.node_names.remove(self.master_node)
4551
4552     if self.op.command in self._SKIP_MASTER:
4553       assert self.master_node not in self.op.node_names
4554
4555     for (node_name, node) in self.cfg.GetMultiNodeInfo(self.op.node_names):
4556       if node is None:
4557         raise errors.OpPrereqError("Node %s not found" % node_name,
4558                                    errors.ECODE_NOENT)
4559       else:
4560         self.nodes.append(node)
4561
4562       if (not self.op.ignore_status and
4563           (self.op.command == constants.OOB_POWER_OFF and not node.offline)):
4564         raise errors.OpPrereqError(("Cannot power off node %s because it is"
4565                                     " not marked offline") % node_name,
4566                                    errors.ECODE_STATE)
4567
4568   def Exec(self, feedback_fn):
4569     """Execute OOB and return result if we expect any.
4570
4571     """
4572     master_node = self.master_node
4573     ret = []
4574
4575     for idx, node in enumerate(utils.NiceSort(self.nodes,
4576                                               key=lambda node: node.name)):
4577       node_entry = [(constants.RS_NORMAL, node.name)]
4578       ret.append(node_entry)
4579
4580       oob_program = _SupportsOob(self.cfg, node)
4581
4582       if not oob_program:
4583         node_entry.append((constants.RS_UNAVAIL, None))
4584         continue
4585
4586       logging.info("Executing out-of-band command '%s' using '%s' on %s",
4587                    self.op.command, oob_program, node.name)
4588       result = self.rpc.call_run_oob(master_node, oob_program,
4589                                      self.op.command, node.name,
4590                                      self.op.timeout)
4591
4592       if result.fail_msg:
4593         self.LogWarning("Out-of-band RPC failed on node '%s': %s",
4594                         node.name, result.fail_msg)
4595         node_entry.append((constants.RS_NODATA, None))
4596       else:
4597         try:
4598           self._CheckPayload(result)
4599         except errors.OpExecError, err:
4600           self.LogWarning("Payload returned by node '%s' is not valid: %s",
4601                           node.name, err)
4602           node_entry.append((constants.RS_NODATA, None))
4603         else:
4604           if self.op.command == constants.OOB_HEALTH:
4605             # For health we should log important events
4606             for item, status in result.payload:
4607               if status in [constants.OOB_STATUS_WARNING,
4608                             constants.OOB_STATUS_CRITICAL]:
4609                 self.LogWarning("Item '%s' on node '%s' has status '%s'",
4610                                 item, node.name, status)
4611
4612           if self.op.command == constants.OOB_POWER_ON:
4613             node.powered = True
4614           elif self.op.command == constants.OOB_POWER_OFF:
4615             node.powered = False
4616           elif self.op.command == constants.OOB_POWER_STATUS:
4617             powered = result.payload[constants.OOB_POWER_STATUS_POWERED]
4618             if powered != node.powered:
4619               logging.warning(("Recorded power state (%s) of node '%s' does not"
4620                                " match actual power state (%s)"), node.powered,
4621                               node.name, powered)
4622
4623           # For configuration changing commands we should update the node
4624           if self.op.command in (constants.OOB_POWER_ON,
4625                                  constants.OOB_POWER_OFF):
4626             self.cfg.Update(node, feedback_fn)
4627
4628           node_entry.append((constants.RS_NORMAL, result.payload))
4629
4630           if (self.op.command == constants.OOB_POWER_ON and
4631               idx < len(self.nodes) - 1):
4632             time.sleep(self.op.power_delay)
4633
4634     return ret
4635
4636   def _CheckPayload(self, result):
4637     """Checks if the payload is valid.
4638
4639     @param result: RPC result
4640     @raises errors.OpExecError: If payload is not valid
4641
4642     """
4643     errs = []
4644     if self.op.command == constants.OOB_HEALTH:
4645       if not isinstance(result.payload, list):
4646         errs.append("command 'health' is expected to return a list but got %s" %
4647                     type(result.payload))
4648       else:
4649         for item, status in result.payload:
4650           if status not in constants.OOB_STATUSES:
4651             errs.append("health item '%s' has invalid status '%s'" %
4652                         (item, status))
4653
4654     if self.op.command == constants.OOB_POWER_STATUS:
4655       if not isinstance(result.payload, dict):
4656         errs.append("power-status is expected to return a dict but got %s" %
4657                     type(result.payload))
4658
4659     if self.op.command in [
4660         constants.OOB_POWER_ON,
4661         constants.OOB_POWER_OFF,
4662         constants.OOB_POWER_CYCLE,
4663         ]:
4664       if result.payload is not None:
4665         errs.append("%s is expected to not return payload but got '%s'" %
4666                     (self.op.command, result.payload))
4667
4668     if errs:
4669       raise errors.OpExecError("Check of out-of-band payload failed due to %s" %
4670                                utils.CommaJoin(errs))
4671
4672
4673 class _OsQuery(_QueryBase):
4674   FIELDS = query.OS_FIELDS
4675
4676   def ExpandNames(self, lu):
4677     # Lock all nodes in shared mode
4678     # Temporary removal of locks, should be reverted later
4679     # TODO: reintroduce locks when they are lighter-weight
4680     lu.needed_locks = {}
4681     #self.share_locks[locking.LEVEL_NODE] = 1
4682     #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
4683
4684     # The following variables interact with _QueryBase._GetNames
4685     if self.names:
4686       self.wanted = self.names
4687     else:
4688       self.wanted = locking.ALL_SET
4689
4690     self.do_locking = self.use_locking
4691
4692   def DeclareLocks(self, lu, level):
4693     pass
4694
4695   @staticmethod
4696   def _DiagnoseByOS(rlist):
4697     """Remaps a per-node return list into an a per-os per-node dictionary
4698
4699     @param rlist: a map with node names as keys and OS objects as values
4700
4701     @rtype: dict
4702     @return: a dictionary with osnames as keys and as value another
4703         map, with nodes as keys and tuples of (path, status, diagnose,
4704         variants, parameters, api_versions) as values, eg::
4705
4706           {"debian-etch": {"node1": [(/usr/lib/..., True, "", [], []),
4707                                      (/srv/..., False, "invalid api")],
4708                            "node2": [(/srv/..., True, "", [], [])]}
4709           }
4710
4711     """
4712     all_os = {}
4713     # we build here the list of nodes that didn't fail the RPC (at RPC
4714     # level), so that nodes with a non-responding node daemon don't
4715     # make all OSes invalid
4716     good_nodes = [node_name for node_name in rlist
4717                   if not rlist[node_name].fail_msg]
4718     for node_name, nr in rlist.items():
4719       if nr.fail_msg or not nr.payload:
4720         continue
4721       for (name, path, status, diagnose, variants,
4722            params, api_versions) in nr.payload:
4723         if name not in all_os:
4724           # build a list of nodes for this os containing empty lists
4725           # for each node in node_list
4726           all_os[name] = {}
4727           for nname in good_nodes:
4728             all_os[name][nname] = []
4729         # convert params from [name, help] to (name, help)
4730         params = [tuple(v) for v in params]
4731         all_os[name][node_name].append((path, status, diagnose,
4732                                         variants, params, api_versions))
4733     return all_os
4734
4735   def _GetQueryData(self, lu):
4736     """Computes the list of nodes and their attributes.
4737
4738     """
4739     # Locking is not used
4740     assert not (compat.any(lu.glm.is_owned(level)
4741                            for level in locking.LEVELS
4742                            if level != locking.LEVEL_CLUSTER) or
4743                 self.do_locking or self.use_locking)
4744
4745     valid_nodes = [node.name
4746                    for node in lu.cfg.GetAllNodesInfo().values()
4747                    if not node.offline and node.vm_capable]
4748     pol = self._DiagnoseByOS(lu.rpc.call_os_diagnose(valid_nodes))
4749     cluster = lu.cfg.GetClusterInfo()
4750
4751     data = {}
4752
4753     for (os_name, os_data) in pol.items():
4754       info = query.OsInfo(name=os_name, valid=True, node_status=os_data,
4755                           hidden=(os_name in cluster.hidden_os),
4756                           blacklisted=(os_name in cluster.blacklisted_os))
4757
4758       variants = set()
4759       parameters = set()
4760       api_versions = set()
4761
4762       for idx, osl in enumerate(os_data.values()):
4763         info.valid = bool(info.valid and osl and osl[0][1])
4764         if not info.valid:
4765           break
4766
4767         (node_variants, node_params, node_api) = osl[0][3:6]
4768         if idx == 0:
4769           # First entry
4770           variants.update(node_variants)
4771           parameters.update(node_params)
4772           api_versions.update(node_api)
4773         else:
4774           # Filter out inconsistent values
4775           variants.intersection_update(node_variants)
4776           parameters.intersection_update(node_params)
4777           api_versions.intersection_update(node_api)
4778
4779       info.variants = list(variants)
4780       info.parameters = list(parameters)
4781       info.api_versions = list(api_versions)
4782
4783       data[os_name] = info
4784
4785     # Prepare data in requested order
4786     return [data[name] for name in self._GetNames(lu, pol.keys(), None)
4787             if name in data]
4788
4789
4790 class LUOsDiagnose(NoHooksLU):
4791   """Logical unit for OS diagnose/query.
4792
4793   """
4794   REQ_BGL = False
4795
4796   @staticmethod
4797   def _BuildFilter(fields, names):
4798     """Builds a filter for querying OSes.
4799
4800     """
4801     name_filter = qlang.MakeSimpleFilter("name", names)
4802
4803     # Legacy behaviour: Hide hidden, blacklisted or invalid OSes if the
4804     # respective field is not requested
4805     status_filter = [[qlang.OP_NOT, [qlang.OP_TRUE, fname]]
4806                      for fname in ["hidden", "blacklisted"]
4807                      if fname not in fields]
4808     if "valid" not in fields:
4809       status_filter.append([qlang.OP_TRUE, "valid"])
4810
4811     if status_filter:
4812       status_filter.insert(0, qlang.OP_AND)
4813     else:
4814       status_filter = None
4815
4816     if name_filter and status_filter:
4817       return [qlang.OP_AND, name_filter, status_filter]
4818     elif name_filter:
4819       return name_filter
4820     else:
4821       return status_filter
4822
4823   def CheckArguments(self):
4824     self.oq = _OsQuery(self._BuildFilter(self.op.output_fields, self.op.names),
4825                        self.op.output_fields, False)
4826
4827   def ExpandNames(self):
4828     self.oq.ExpandNames(self)
4829
4830   def Exec(self, feedback_fn):
4831     return self.oq.OldStyleQuery(self)
4832
4833
4834 class LUNodeRemove(LogicalUnit):
4835   """Logical unit for removing a node.
4836
4837   """
4838   HPATH = "node-remove"
4839   HTYPE = constants.HTYPE_NODE
4840
4841   def BuildHooksEnv(self):
4842     """Build hooks env.
4843
4844     This doesn't run on the target node in the pre phase as a failed
4845     node would then be impossible to remove.
4846
4847     """
4848     return {
4849       "OP_TARGET": self.op.node_name,
4850       "NODE_NAME": self.op.node_name,
4851       }
4852
4853   def BuildHooksNodes(self):
4854     """Build hooks nodes.
4855
4856     """
4857     all_nodes = self.cfg.GetNodeList()
4858     try:
4859       all_nodes.remove(self.op.node_name)
4860     except ValueError:
4861       logging.warning("Node '%s', which is about to be removed, was not found"
4862                       " in the list of all nodes", self.op.node_name)
4863     return (all_nodes, all_nodes)
4864
4865   def CheckPrereq(self):
4866     """Check prerequisites.
4867
4868     This checks:
4869      - the node exists in the configuration
4870      - it does not have primary or secondary instances
4871      - it's not the master
4872
4873     Any errors are signaled by raising errors.OpPrereqError.
4874
4875     """
4876     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
4877     node = self.cfg.GetNodeInfo(self.op.node_name)
4878     assert node is not None
4879
4880     masternode = self.cfg.GetMasterNode()
4881     if node.name == masternode:
4882       raise errors.OpPrereqError("Node is the master node, failover to another"
4883                                  " node is required", errors.ECODE_INVAL)
4884
4885     for instance_name, instance in self.cfg.GetAllInstancesInfo().items():
4886       if node.name in instance.all_nodes:
4887         raise errors.OpPrereqError("Instance %s is still running on the node,"
4888                                    " please remove first" % instance_name,
4889                                    errors.ECODE_INVAL)
4890     self.op.node_name = node.name
4891     self.node = node
4892
4893   def Exec(self, feedback_fn):
4894     """Removes the node from the cluster.
4895
4896     """
4897     node = self.node
4898     logging.info("Stopping the node daemon and removing configs from node %s",
4899                  node.name)
4900
4901     modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
4902
4903     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER), \
4904       "Not owning BGL"
4905
4906     # Promote nodes to master candidate as needed
4907     _AdjustCandidatePool(self, exceptions=[node.name])
4908     self.context.RemoveNode(node.name)
4909
4910     # Run post hooks on the node before it's removed
4911     _RunPostHook(self, node.name)
4912
4913     result = self.rpc.call_node_leave_cluster(node.name, modify_ssh_setup)
4914     msg = result.fail_msg
4915     if msg:
4916       self.LogWarning("Errors encountered on the remote node while leaving"
4917                       " the cluster: %s", msg)
4918
4919     # Remove node from our /etc/hosts
4920     if self.cfg.GetClusterInfo().modify_etc_hosts:
4921       master_node = self.cfg.GetMasterNode()
4922       result = self.rpc.call_etc_hosts_modify(master_node,
4923                                               constants.ETC_HOSTS_REMOVE,
4924                                               node.name, None)
4925       result.Raise("Can't update hosts file with new host data")
4926       _RedistributeAncillaryFiles(self)
4927
4928
4929 class _NodeQuery(_QueryBase):
4930   FIELDS = query.NODE_FIELDS
4931
4932   def ExpandNames(self, lu):
4933     lu.needed_locks = {}
4934     lu.share_locks = _ShareAll()
4935
4936     if self.names:
4937       self.wanted = _GetWantedNodes(lu, self.names)
4938     else:
4939       self.wanted = locking.ALL_SET
4940
4941     self.do_locking = (self.use_locking and
4942                        query.NQ_LIVE in self.requested_data)
4943
4944     if self.do_locking:
4945       # If any non-static field is requested we need to lock the nodes
4946       lu.needed_locks[locking.LEVEL_NODE] = self.wanted
4947
4948   def DeclareLocks(self, lu, level):
4949     pass
4950
4951   def _GetQueryData(self, lu):
4952     """Computes the list of nodes and their attributes.
4953
4954     """
4955     all_info = lu.cfg.GetAllNodesInfo()
4956
4957     nodenames = self._GetNames(lu, all_info.keys(), locking.LEVEL_NODE)
4958
4959     # Gather data as requested
4960     if query.NQ_LIVE in self.requested_data:
4961       # filter out non-vm_capable nodes
4962       toquery_nodes = [name for name in nodenames if all_info[name].vm_capable]
4963
4964       node_data = lu.rpc.call_node_info(toquery_nodes, [lu.cfg.GetVGName()],
4965                                         [lu.cfg.GetHypervisorType()])
4966       live_data = dict((name, _MakeLegacyNodeInfo(nresult.payload))
4967                        for (name, nresult) in node_data.items()
4968                        if not nresult.fail_msg and nresult.payload)
4969     else:
4970       live_data = None
4971
4972     if query.NQ_INST in self.requested_data:
4973       node_to_primary = dict([(name, set()) for name in nodenames])
4974       node_to_secondary = dict([(name, set()) for name in nodenames])
4975
4976       inst_data = lu.cfg.GetAllInstancesInfo()
4977
4978       for inst in inst_data.values():
4979         if inst.primary_node in node_to_primary:
4980           node_to_primary[inst.primary_node].add(inst.name)
4981         for secnode in inst.secondary_nodes:
4982           if secnode in node_to_secondary:
4983             node_to_secondary[secnode].add(inst.name)
4984     else:
4985       node_to_primary = None
4986       node_to_secondary = None
4987
4988     if query.NQ_OOB in self.requested_data:
4989       oob_support = dict((name, bool(_SupportsOob(lu.cfg, node)))
4990                          for name, node in all_info.iteritems())
4991     else:
4992       oob_support = None
4993
4994     if query.NQ_GROUP in self.requested_data:
4995       groups = lu.cfg.GetAllNodeGroupsInfo()
4996     else:
4997       groups = {}
4998
4999     return query.NodeQueryData([all_info[name] for name in nodenames],
5000                                live_data, lu.cfg.GetMasterNode(),
5001                                node_to_primary, node_to_secondary, groups,
5002                                oob_support, lu.cfg.GetClusterInfo())
5003
5004
5005 class LUNodeQuery(NoHooksLU):
5006   """Logical unit for querying nodes.
5007
5008   """
5009   # pylint: disable=W0142
5010   REQ_BGL = False
5011
5012   def CheckArguments(self):
5013     self.nq = _NodeQuery(qlang.MakeSimpleFilter("name", self.op.names),
5014                          self.op.output_fields, self.op.use_locking)
5015
5016   def ExpandNames(self):
5017     self.nq.ExpandNames(self)
5018
5019   def DeclareLocks(self, level):
5020     self.nq.DeclareLocks(self, level)
5021
5022   def Exec(self, feedback_fn):
5023     return self.nq.OldStyleQuery(self)
5024
5025
5026 class LUNodeQueryvols(NoHooksLU):
5027   """Logical unit for getting volumes on node(s).
5028
5029   """
5030   REQ_BGL = False
5031   _FIELDS_DYNAMIC = utils.FieldSet("phys", "vg", "name", "size", "instance")
5032   _FIELDS_STATIC = utils.FieldSet("node")
5033
5034   def CheckArguments(self):
5035     _CheckOutputFields(static=self._FIELDS_STATIC,
5036                        dynamic=self._FIELDS_DYNAMIC,
5037                        selected=self.op.output_fields)
5038
5039   def ExpandNames(self):
5040     self.share_locks = _ShareAll()
5041     self.needed_locks = {}
5042
5043     if not self.op.nodes:
5044       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5045     else:
5046       self.needed_locks[locking.LEVEL_NODE] = \
5047         _GetWantedNodes(self, self.op.nodes)
5048
5049   def Exec(self, feedback_fn):
5050     """Computes the list of nodes and their attributes.
5051
5052     """
5053     nodenames = self.owned_locks(locking.LEVEL_NODE)
5054     volumes = self.rpc.call_node_volumes(nodenames)
5055
5056     ilist = self.cfg.GetAllInstancesInfo()
5057     vol2inst = _MapInstanceDisksToNodes(ilist.values())
5058
5059     output = []
5060     for node in nodenames:
5061       nresult = volumes[node]
5062       if nresult.offline:
5063         continue
5064       msg = nresult.fail_msg
5065       if msg:
5066         self.LogWarning("Can't compute volume data on node %s: %s", node, msg)
5067         continue
5068
5069       node_vols = sorted(nresult.payload,
5070                          key=operator.itemgetter("dev"))
5071
5072       for vol in node_vols:
5073         node_output = []
5074         for field in self.op.output_fields:
5075           if field == "node":
5076             val = node
5077           elif field == "phys":
5078             val = vol["dev"]
5079           elif field == "vg":
5080             val = vol["vg"]
5081           elif field == "name":
5082             val = vol["name"]
5083           elif field == "size":
5084             val = int(float(vol["size"]))
5085           elif field == "instance":
5086             val = vol2inst.get((node, vol["vg"] + "/" + vol["name"]), "-")
5087           else:
5088             raise errors.ParameterError(field)
5089           node_output.append(str(val))
5090
5091         output.append(node_output)
5092
5093     return output
5094
5095
5096 class LUNodeQueryStorage(NoHooksLU):
5097   """Logical unit for getting information on storage units on node(s).
5098
5099   """
5100   _FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
5101   REQ_BGL = False
5102
5103   def CheckArguments(self):
5104     _CheckOutputFields(static=self._FIELDS_STATIC,
5105                        dynamic=utils.FieldSet(*constants.VALID_STORAGE_FIELDS),
5106                        selected=self.op.output_fields)
5107
5108   def ExpandNames(self):
5109     self.share_locks = _ShareAll()
5110     self.needed_locks = {}
5111
5112     if self.op.nodes:
5113       self.needed_locks[locking.LEVEL_NODE] = \
5114         _GetWantedNodes(self, self.op.nodes)
5115     else:
5116       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5117
5118   def Exec(self, feedback_fn):
5119     """Computes the list of nodes and their attributes.
5120
5121     """
5122     self.nodes = self.owned_locks(locking.LEVEL_NODE)
5123
5124     # Always get name to sort by
5125     if constants.SF_NAME in self.op.output_fields:
5126       fields = self.op.output_fields[:]
5127     else:
5128       fields = [constants.SF_NAME] + self.op.output_fields
5129
5130     # Never ask for node or type as it's only known to the LU
5131     for extra in [constants.SF_NODE, constants.SF_TYPE]:
5132       while extra in fields:
5133         fields.remove(extra)
5134
5135     field_idx = dict([(name, idx) for (idx, name) in enumerate(fields)])
5136     name_idx = field_idx[constants.SF_NAME]
5137
5138     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
5139     data = self.rpc.call_storage_list(self.nodes,
5140                                       self.op.storage_type, st_args,
5141                                       self.op.name, fields)
5142
5143     result = []
5144
5145     for node in utils.NiceSort(self.nodes):
5146       nresult = data[node]
5147       if nresult.offline:
5148         continue
5149
5150       msg = nresult.fail_msg
5151       if msg:
5152         self.LogWarning("Can't get storage data from node %s: %s", node, msg)
5153         continue
5154
5155       rows = dict([(row[name_idx], row) for row in nresult.payload])
5156
5157       for name in utils.NiceSort(rows.keys()):
5158         row = rows[name]
5159
5160         out = []
5161
5162         for field in self.op.output_fields:
5163           if field == constants.SF_NODE:
5164             val = node
5165           elif field == constants.SF_TYPE:
5166             val = self.op.storage_type
5167           elif field in field_idx:
5168             val = row[field_idx[field]]
5169           else:
5170             raise errors.ParameterError(field)
5171
5172           out.append(val)
5173
5174         result.append(out)
5175
5176     return result
5177
5178
5179 class _InstanceQuery(_QueryBase):
5180   FIELDS = query.INSTANCE_FIELDS
5181
5182   def ExpandNames(self, lu):
5183     lu.needed_locks = {}
5184     lu.share_locks = _ShareAll()
5185
5186     if self.names:
5187       self.wanted = _GetWantedInstances(lu, self.names)
5188     else:
5189       self.wanted = locking.ALL_SET
5190
5191     self.do_locking = (self.use_locking and
5192                        query.IQ_LIVE in self.requested_data)
5193     if self.do_locking:
5194       lu.needed_locks[locking.LEVEL_INSTANCE] = self.wanted
5195       lu.needed_locks[locking.LEVEL_NODEGROUP] = []
5196       lu.needed_locks[locking.LEVEL_NODE] = []
5197       lu.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
5198
5199     self.do_grouplocks = (self.do_locking and
5200                           query.IQ_NODES in self.requested_data)
5201
5202   def DeclareLocks(self, lu, level):
5203     if self.do_locking:
5204       if level == locking.LEVEL_NODEGROUP and self.do_grouplocks:
5205         assert not lu.needed_locks[locking.LEVEL_NODEGROUP]
5206
5207         # Lock all groups used by instances optimistically; this requires going
5208         # via the node before it's locked, requiring verification later on
5209         lu.needed_locks[locking.LEVEL_NODEGROUP] = \
5210           set(group_uuid
5211               for instance_name in lu.owned_locks(locking.LEVEL_INSTANCE)
5212               for group_uuid in lu.cfg.GetInstanceNodeGroups(instance_name))
5213       elif level == locking.LEVEL_NODE:
5214         lu._LockInstancesNodes() # pylint: disable=W0212
5215
5216   @staticmethod
5217   def _CheckGroupLocks(lu):
5218     owned_instances = frozenset(lu.owned_locks(locking.LEVEL_INSTANCE))
5219     owned_groups = frozenset(lu.owned_locks(locking.LEVEL_NODEGROUP))
5220
5221     # Check if node groups for locked instances are still correct
5222     for instance_name in owned_instances:
5223       _CheckInstanceNodeGroups(lu.cfg, instance_name, owned_groups)
5224
5225   def _GetQueryData(self, lu):
5226     """Computes the list of instances and their attributes.
5227
5228     """
5229     if self.do_grouplocks:
5230       self._CheckGroupLocks(lu)
5231
5232     cluster = lu.cfg.GetClusterInfo()
5233     all_info = lu.cfg.GetAllInstancesInfo()
5234
5235     instance_names = self._GetNames(lu, all_info.keys(), locking.LEVEL_INSTANCE)
5236
5237     instance_list = [all_info[name] for name in instance_names]
5238     nodes = frozenset(itertools.chain(*(inst.all_nodes
5239                                         for inst in instance_list)))
5240     hv_list = list(set([inst.hypervisor for inst in instance_list]))
5241     bad_nodes = []
5242     offline_nodes = []
5243     wrongnode_inst = set()
5244
5245     # Gather data as requested
5246     if self.requested_data & set([query.IQ_LIVE, query.IQ_CONSOLE]):
5247       live_data = {}
5248       node_data = lu.rpc.call_all_instances_info(nodes, hv_list)
5249       for name in nodes:
5250         result = node_data[name]
5251         if result.offline:
5252           # offline nodes will be in both lists
5253           assert result.fail_msg
5254           offline_nodes.append(name)
5255         if result.fail_msg:
5256           bad_nodes.append(name)
5257         elif result.payload:
5258           for inst in result.payload:
5259             if inst in all_info:
5260               if all_info[inst].primary_node == name:
5261                 live_data.update(result.payload)
5262               else:
5263                 wrongnode_inst.add(inst)
5264             else:
5265               # orphan instance; we don't list it here as we don't
5266               # handle this case yet in the output of instance listing
5267               logging.warning("Orphan instance '%s' found on node %s",
5268                               inst, name)
5269         # else no instance is alive
5270     else:
5271       live_data = {}
5272
5273     if query.IQ_DISKUSAGE in self.requested_data:
5274       disk_usage = dict((inst.name,
5275                          _ComputeDiskSize(inst.disk_template,
5276                                           [{constants.IDISK_SIZE: disk.size}
5277                                            for disk in inst.disks]))
5278                         for inst in instance_list)
5279     else:
5280       disk_usage = None
5281
5282     if query.IQ_CONSOLE in self.requested_data:
5283       consinfo = {}
5284       for inst in instance_list:
5285         if inst.name in live_data:
5286           # Instance is running
5287           consinfo[inst.name] = _GetInstanceConsole(cluster, inst)
5288         else:
5289           consinfo[inst.name] = None
5290       assert set(consinfo.keys()) == set(instance_names)
5291     else:
5292       consinfo = None
5293
5294     if query.IQ_NODES in self.requested_data:
5295       node_names = set(itertools.chain(*map(operator.attrgetter("all_nodes"),
5296                                             instance_list)))
5297       nodes = dict(lu.cfg.GetMultiNodeInfo(node_names))
5298       groups = dict((uuid, lu.cfg.GetNodeGroup(uuid))
5299                     for uuid in set(map(operator.attrgetter("group"),
5300                                         nodes.values())))
5301     else:
5302       nodes = None
5303       groups = None
5304
5305     return query.InstanceQueryData(instance_list, lu.cfg.GetClusterInfo(),
5306                                    disk_usage, offline_nodes, bad_nodes,
5307                                    live_data, wrongnode_inst, consinfo,
5308                                    nodes, groups)
5309
5310
5311 class LUQuery(NoHooksLU):
5312   """Query for resources/items of a certain kind.
5313
5314   """
5315   # pylint: disable=W0142
5316   REQ_BGL = False
5317
5318   def CheckArguments(self):
5319     qcls = _GetQueryImplementation(self.op.what)
5320
5321     self.impl = qcls(self.op.qfilter, self.op.fields, self.op.use_locking)
5322
5323   def ExpandNames(self):
5324     self.impl.ExpandNames(self)
5325
5326   def DeclareLocks(self, level):
5327     self.impl.DeclareLocks(self, level)
5328
5329   def Exec(self, feedback_fn):
5330     return self.impl.NewStyleQuery(self)
5331
5332
5333 class LUQueryFields(NoHooksLU):
5334   """Query for resources/items of a certain kind.
5335
5336   """
5337   # pylint: disable=W0142
5338   REQ_BGL = False
5339
5340   def CheckArguments(self):
5341     self.qcls = _GetQueryImplementation(self.op.what)
5342
5343   def ExpandNames(self):
5344     self.needed_locks = {}
5345
5346   def Exec(self, feedback_fn):
5347     return query.QueryFields(self.qcls.FIELDS, self.op.fields)
5348
5349
5350 class LUNodeModifyStorage(NoHooksLU):
5351   """Logical unit for modifying a storage volume on a node.
5352
5353   """
5354   REQ_BGL = False
5355
5356   def CheckArguments(self):
5357     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5358
5359     storage_type = self.op.storage_type
5360
5361     try:
5362       modifiable = constants.MODIFIABLE_STORAGE_FIELDS[storage_type]
5363     except KeyError:
5364       raise errors.OpPrereqError("Storage units of type '%s' can not be"
5365                                  " modified" % storage_type,
5366                                  errors.ECODE_INVAL)
5367
5368     diff = set(self.op.changes.keys()) - modifiable
5369     if diff:
5370       raise errors.OpPrereqError("The following fields can not be modified for"
5371                                  " storage units of type '%s': %r" %
5372                                  (storage_type, list(diff)),
5373                                  errors.ECODE_INVAL)
5374
5375   def ExpandNames(self):
5376     self.needed_locks = {
5377       locking.LEVEL_NODE: self.op.node_name,
5378       }
5379
5380   def Exec(self, feedback_fn):
5381     """Computes the list of nodes and their attributes.
5382
5383     """
5384     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
5385     result = self.rpc.call_storage_modify(self.op.node_name,
5386                                           self.op.storage_type, st_args,
5387                                           self.op.name, self.op.changes)
5388     result.Raise("Failed to modify storage unit '%s' on %s" %
5389                  (self.op.name, self.op.node_name))
5390
5391
5392 class LUNodeAdd(LogicalUnit):
5393   """Logical unit for adding node to the cluster.
5394
5395   """
5396   HPATH = "node-add"
5397   HTYPE = constants.HTYPE_NODE
5398   _NFLAGS = ["master_capable", "vm_capable"]
5399
5400   def CheckArguments(self):
5401     self.primary_ip_family = self.cfg.GetPrimaryIPFamily()
5402     # validate/normalize the node name
5403     self.hostname = netutils.GetHostname(name=self.op.node_name,
5404                                          family=self.primary_ip_family)
5405     self.op.node_name = self.hostname.name
5406
5407     if self.op.readd and self.op.node_name == self.cfg.GetMasterNode():
5408       raise errors.OpPrereqError("Cannot readd the master node",
5409                                  errors.ECODE_STATE)
5410
5411     if self.op.readd and self.op.group:
5412       raise errors.OpPrereqError("Cannot pass a node group when a node is"
5413                                  " being readded", errors.ECODE_INVAL)
5414
5415   def BuildHooksEnv(self):
5416     """Build hooks env.
5417
5418     This will run on all nodes before, and on all nodes + the new node after.
5419
5420     """
5421     return {
5422       "OP_TARGET": self.op.node_name,
5423       "NODE_NAME": self.op.node_name,
5424       "NODE_PIP": self.op.primary_ip,
5425       "NODE_SIP": self.op.secondary_ip,
5426       "MASTER_CAPABLE": str(self.op.master_capable),
5427       "VM_CAPABLE": str(self.op.vm_capable),
5428       }
5429
5430   def BuildHooksNodes(self):
5431     """Build hooks nodes.
5432
5433     """
5434     # Exclude added node
5435     pre_nodes = list(set(self.cfg.GetNodeList()) - set([self.op.node_name]))
5436     post_nodes = pre_nodes + [self.op.node_name, ]
5437
5438     return (pre_nodes, post_nodes)
5439
5440   def CheckPrereq(self):
5441     """Check prerequisites.
5442
5443     This checks:
5444      - the new node is not already in the config
5445      - it is resolvable
5446      - its parameters (single/dual homed) matches the cluster
5447
5448     Any errors are signaled by raising errors.OpPrereqError.
5449
5450     """
5451     cfg = self.cfg
5452     hostname = self.hostname
5453     node = hostname.name
5454     primary_ip = self.op.primary_ip = hostname.ip
5455     if self.op.secondary_ip is None:
5456       if self.primary_ip_family == netutils.IP6Address.family:
5457         raise errors.OpPrereqError("When using a IPv6 primary address, a valid"
5458                                    " IPv4 address must be given as secondary",
5459                                    errors.ECODE_INVAL)
5460       self.op.secondary_ip = primary_ip
5461
5462     secondary_ip = self.op.secondary_ip
5463     if not netutils.IP4Address.IsValid(secondary_ip):
5464       raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
5465                                  " address" % secondary_ip, errors.ECODE_INVAL)
5466
5467     node_list = cfg.GetNodeList()
5468     if not self.op.readd and node in node_list:
5469       raise errors.OpPrereqError("Node %s is already in the configuration" %
5470                                  node, errors.ECODE_EXISTS)
5471     elif self.op.readd and node not in node_list:
5472       raise errors.OpPrereqError("Node %s is not in the configuration" % node,
5473                                  errors.ECODE_NOENT)
5474
5475     self.changed_primary_ip = False
5476
5477     for existing_node_name, existing_node in cfg.GetMultiNodeInfo(node_list):
5478       if self.op.readd and node == existing_node_name:
5479         if existing_node.secondary_ip != secondary_ip:
5480           raise errors.OpPrereqError("Readded node doesn't have the same IP"
5481                                      " address configuration as before",
5482                                      errors.ECODE_INVAL)
5483         if existing_node.primary_ip != primary_ip:
5484           self.changed_primary_ip = True
5485
5486         continue
5487
5488       if (existing_node.primary_ip == primary_ip or
5489           existing_node.secondary_ip == primary_ip or
5490           existing_node.primary_ip == secondary_ip or
5491           existing_node.secondary_ip == secondary_ip):
5492         raise errors.OpPrereqError("New node ip address(es) conflict with"
5493                                    " existing node %s" % existing_node.name,
5494                                    errors.ECODE_NOTUNIQUE)
5495
5496     # After this 'if' block, None is no longer a valid value for the
5497     # _capable op attributes
5498     if self.op.readd:
5499       old_node = self.cfg.GetNodeInfo(node)
5500       assert old_node is not None, "Can't retrieve locked node %s" % node
5501       for attr in self._NFLAGS:
5502         if getattr(self.op, attr) is None:
5503           setattr(self.op, attr, getattr(old_node, attr))
5504     else:
5505       for attr in self._NFLAGS:
5506         if getattr(self.op, attr) is None:
5507           setattr(self.op, attr, True)
5508
5509     if self.op.readd and not self.op.vm_capable:
5510       pri, sec = cfg.GetNodeInstances(node)
5511       if pri or sec:
5512         raise errors.OpPrereqError("Node %s being re-added with vm_capable"
5513                                    " flag set to false, but it already holds"
5514                                    " instances" % node,
5515                                    errors.ECODE_STATE)
5516
5517     # check that the type of the node (single versus dual homed) is the
5518     # same as for the master
5519     myself = cfg.GetNodeInfo(self.cfg.GetMasterNode())
5520     master_singlehomed = myself.secondary_ip == myself.primary_ip
5521     newbie_singlehomed = secondary_ip == primary_ip
5522     if master_singlehomed != newbie_singlehomed:
5523       if master_singlehomed:
5524         raise errors.OpPrereqError("The master has no secondary ip but the"
5525                                    " new node has one",
5526                                    errors.ECODE_INVAL)
5527       else:
5528         raise errors.OpPrereqError("The master has a secondary ip but the"
5529                                    " new node doesn't have one",
5530                                    errors.ECODE_INVAL)
5531
5532     # checks reachability
5533     if not netutils.TcpPing(primary_ip, constants.DEFAULT_NODED_PORT):
5534       raise errors.OpPrereqError("Node not reachable by ping",
5535                                  errors.ECODE_ENVIRON)
5536
5537     if not newbie_singlehomed:
5538       # check reachability from my secondary ip to newbie's secondary ip
5539       if not netutils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
5540                            source=myself.secondary_ip):
5541         raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
5542                                    " based ping to node daemon port",
5543                                    errors.ECODE_ENVIRON)
5544
5545     if self.op.readd:
5546       exceptions = [node]
5547     else:
5548       exceptions = []
5549
5550     if self.op.master_capable:
5551       self.master_candidate = _DecideSelfPromotion(self, exceptions=exceptions)
5552     else:
5553       self.master_candidate = False
5554
5555     if self.op.readd:
5556       self.new_node = old_node
5557     else:
5558       node_group = cfg.LookupNodeGroup(self.op.group)
5559       self.new_node = objects.Node(name=node,
5560                                    primary_ip=primary_ip,
5561                                    secondary_ip=secondary_ip,
5562                                    master_candidate=self.master_candidate,
5563                                    offline=False, drained=False,
5564                                    group=node_group)
5565
5566     if self.op.ndparams:
5567       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
5568
5569     if self.op.hv_state:
5570       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None)
5571
5572     if self.op.disk_state:
5573       self.new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state, None)
5574
5575   def Exec(self, feedback_fn):
5576     """Adds the new node to the cluster.
5577
5578     """
5579     new_node = self.new_node
5580     node = new_node.name
5581
5582     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER), \
5583       "Not owning BGL"
5584
5585     # We adding a new node so we assume it's powered
5586     new_node.powered = True
5587
5588     # for re-adds, reset the offline/drained/master-candidate flags;
5589     # we need to reset here, otherwise offline would prevent RPC calls
5590     # later in the procedure; this also means that if the re-add
5591     # fails, we are left with a non-offlined, broken node
5592     if self.op.readd:
5593       new_node.drained = new_node.offline = False # pylint: disable=W0201
5594       self.LogInfo("Readding a node, the offline/drained flags were reset")
5595       # if we demote the node, we do cleanup later in the procedure
5596       new_node.master_candidate = self.master_candidate
5597       if self.changed_primary_ip:
5598         new_node.primary_ip = self.op.primary_ip
5599
5600     # copy the master/vm_capable flags
5601     for attr in self._NFLAGS:
5602       setattr(new_node, attr, getattr(self.op, attr))
5603
5604     # notify the user about any possible mc promotion
5605     if new_node.master_candidate:
5606       self.LogInfo("Node will be a master candidate")
5607
5608     if self.op.ndparams:
5609       new_node.ndparams = self.op.ndparams
5610     else:
5611       new_node.ndparams = {}
5612
5613     if self.op.hv_state:
5614       new_node.hv_state_static = self.new_hv_state
5615
5616     if self.op.disk_state:
5617       new_node.disk_state_static = self.new_disk_state
5618
5619     # check connectivity
5620     result = self.rpc.call_version([node])[node]
5621     result.Raise("Can't get version information from node %s" % node)
5622     if constants.PROTOCOL_VERSION == result.payload:
5623       logging.info("Communication to node %s fine, sw version %s match",
5624                    node, result.payload)
5625     else:
5626       raise errors.OpExecError("Version mismatch master version %s,"
5627                                " node version %s" %
5628                                (constants.PROTOCOL_VERSION, result.payload))
5629
5630     # Add node to our /etc/hosts, and add key to known_hosts
5631     if self.cfg.GetClusterInfo().modify_etc_hosts:
5632       master_node = self.cfg.GetMasterNode()
5633       result = self.rpc.call_etc_hosts_modify(master_node,
5634                                               constants.ETC_HOSTS_ADD,
5635                                               self.hostname.name,
5636                                               self.hostname.ip)
5637       result.Raise("Can't update hosts file with new host data")
5638
5639     if new_node.secondary_ip != new_node.primary_ip:
5640       _CheckNodeHasSecondaryIP(self, new_node.name, new_node.secondary_ip,
5641                                False)
5642
5643     node_verify_list = [self.cfg.GetMasterNode()]
5644     node_verify_param = {
5645       constants.NV_NODELIST: ([node], {}),
5646       # TODO: do a node-net-test as well?
5647     }
5648
5649     result = self.rpc.call_node_verify(node_verify_list, node_verify_param,
5650                                        self.cfg.GetClusterName())
5651     for verifier in node_verify_list:
5652       result[verifier].Raise("Cannot communicate with node %s" % verifier)
5653       nl_payload = result[verifier].payload[constants.NV_NODELIST]
5654       if nl_payload:
5655         for failed in nl_payload:
5656           feedback_fn("ssh/hostname verification failed"
5657                       " (checking from %s): %s" %
5658                       (verifier, nl_payload[failed]))
5659         raise errors.OpExecError("ssh/hostname verification failed")
5660
5661     if self.op.readd:
5662       _RedistributeAncillaryFiles(self)
5663       self.context.ReaddNode(new_node)
5664       # make sure we redistribute the config
5665       self.cfg.Update(new_node, feedback_fn)
5666       # and make sure the new node will not have old files around
5667       if not new_node.master_candidate:
5668         result = self.rpc.call_node_demote_from_mc(new_node.name)
5669         msg = result.fail_msg
5670         if msg:
5671           self.LogWarning("Node failed to demote itself from master"
5672                           " candidate status: %s" % msg)
5673     else:
5674       _RedistributeAncillaryFiles(self, additional_nodes=[node],
5675                                   additional_vm=self.op.vm_capable)
5676       self.context.AddNode(new_node, self.proc.GetECId())
5677
5678
5679 class LUNodeSetParams(LogicalUnit):
5680   """Modifies the parameters of a node.
5681
5682   @cvar _F2R: a dictionary from tuples of flags (mc, drained, offline)
5683       to the node role (as _ROLE_*)
5684   @cvar _R2F: a dictionary from node role to tuples of flags
5685   @cvar _FLAGS: a list of attribute names corresponding to the flags
5686
5687   """
5688   HPATH = "node-modify"
5689   HTYPE = constants.HTYPE_NODE
5690   REQ_BGL = False
5691   (_ROLE_CANDIDATE, _ROLE_DRAINED, _ROLE_OFFLINE, _ROLE_REGULAR) = range(4)
5692   _F2R = {
5693     (True, False, False): _ROLE_CANDIDATE,
5694     (False, True, False): _ROLE_DRAINED,
5695     (False, False, True): _ROLE_OFFLINE,
5696     (False, False, False): _ROLE_REGULAR,
5697     }
5698   _R2F = dict((v, k) for k, v in _F2R.items())
5699   _FLAGS = ["master_candidate", "drained", "offline"]
5700
5701   def CheckArguments(self):
5702     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5703     all_mods = [self.op.offline, self.op.master_candidate, self.op.drained,
5704                 self.op.master_capable, self.op.vm_capable,
5705                 self.op.secondary_ip, self.op.ndparams, self.op.hv_state,
5706                 self.op.disk_state]
5707     if all_mods.count(None) == len(all_mods):
5708       raise errors.OpPrereqError("Please pass at least one modification",
5709                                  errors.ECODE_INVAL)
5710     if all_mods.count(True) > 1:
5711       raise errors.OpPrereqError("Can't set the node into more than one"
5712                                  " state at the same time",
5713                                  errors.ECODE_INVAL)
5714
5715     # Boolean value that tells us whether we might be demoting from MC
5716     self.might_demote = (self.op.master_candidate == False or
5717                          self.op.offline == True or
5718                          self.op.drained == True or
5719                          self.op.master_capable == False)
5720
5721     if self.op.secondary_ip:
5722       if not netutils.IP4Address.IsValid(self.op.secondary_ip):
5723         raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
5724                                    " address" % self.op.secondary_ip,
5725                                    errors.ECODE_INVAL)
5726
5727     self.lock_all = self.op.auto_promote and self.might_demote
5728     self.lock_instances = self.op.secondary_ip is not None
5729
5730   def _InstanceFilter(self, instance):
5731     """Filter for getting affected instances.
5732
5733     """
5734     return (instance.disk_template in constants.DTS_INT_MIRROR and
5735             self.op.node_name in instance.all_nodes)
5736
5737   def ExpandNames(self):
5738     if self.lock_all:
5739       self.needed_locks = {locking.LEVEL_NODE: locking.ALL_SET}
5740     else:
5741       self.needed_locks = {locking.LEVEL_NODE: self.op.node_name}
5742
5743     # Since modifying a node can have severe effects on currently running
5744     # operations the resource lock is at least acquired in shared mode
5745     self.needed_locks[locking.LEVEL_NODE_RES] = \
5746       self.needed_locks[locking.LEVEL_NODE]
5747
5748     # Get node resource and instance locks in shared mode; they are not used
5749     # for anything but read-only access
5750     self.share_locks[locking.LEVEL_NODE_RES] = 1
5751     self.share_locks[locking.LEVEL_INSTANCE] = 1
5752
5753     if self.lock_instances:
5754       self.needed_locks[locking.LEVEL_INSTANCE] = \
5755         frozenset(self.cfg.GetInstancesInfoByFilter(self._InstanceFilter))
5756
5757   def BuildHooksEnv(self):
5758     """Build hooks env.
5759
5760     This runs on the master node.
5761
5762     """
5763     return {
5764       "OP_TARGET": self.op.node_name,
5765       "MASTER_CANDIDATE": str(self.op.master_candidate),
5766       "OFFLINE": str(self.op.offline),
5767       "DRAINED": str(self.op.drained),
5768       "MASTER_CAPABLE": str(self.op.master_capable),
5769       "VM_CAPABLE": str(self.op.vm_capable),
5770       }
5771
5772   def BuildHooksNodes(self):
5773     """Build hooks nodes.
5774
5775     """
5776     nl = [self.cfg.GetMasterNode(), self.op.node_name]
5777     return (nl, nl)
5778
5779   def CheckPrereq(self):
5780     """Check prerequisites.
5781
5782     This only checks the instance list against the existing names.
5783
5784     """
5785     node = self.node = self.cfg.GetNodeInfo(self.op.node_name)
5786
5787     if self.lock_instances:
5788       affected_instances = \
5789         self.cfg.GetInstancesInfoByFilter(self._InstanceFilter)
5790
5791       # Verify instance locks
5792       owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
5793       wanted_instances = frozenset(affected_instances.keys())
5794       if wanted_instances - owned_instances:
5795         raise errors.OpPrereqError("Instances affected by changing node %s's"
5796                                    " secondary IP address have changed since"
5797                                    " locks were acquired, wanted '%s', have"
5798                                    " '%s'; retry the operation" %
5799                                    (self.op.node_name,
5800                                     utils.CommaJoin(wanted_instances),
5801                                     utils.CommaJoin(owned_instances)),
5802                                    errors.ECODE_STATE)
5803     else:
5804       affected_instances = None
5805
5806     if (self.op.master_candidate is not None or
5807         self.op.drained is not None or
5808         self.op.offline is not None):
5809       # we can't change the master's node flags
5810       if self.op.node_name == self.cfg.GetMasterNode():
5811         raise errors.OpPrereqError("The master role can be changed"
5812                                    " only via master-failover",
5813                                    errors.ECODE_INVAL)
5814
5815     if self.op.master_candidate and not node.master_capable:
5816       raise errors.OpPrereqError("Node %s is not master capable, cannot make"
5817                                  " it a master candidate" % node.name,
5818                                  errors.ECODE_STATE)
5819
5820     if self.op.vm_capable == False:
5821       (ipri, isec) = self.cfg.GetNodeInstances(self.op.node_name)
5822       if ipri or isec:
5823         raise errors.OpPrereqError("Node %s hosts instances, cannot unset"
5824                                    " the vm_capable flag" % node.name,
5825                                    errors.ECODE_STATE)
5826
5827     if node.master_candidate and self.might_demote and not self.lock_all:
5828       assert not self.op.auto_promote, "auto_promote set but lock_all not"
5829       # check if after removing the current node, we're missing master
5830       # candidates
5831       (mc_remaining, mc_should, _) = \
5832           self.cfg.GetMasterCandidateStats(exceptions=[node.name])
5833       if mc_remaining < mc_should:
5834         raise errors.OpPrereqError("Not enough master candidates, please"
5835                                    " pass auto promote option to allow"
5836                                    " promotion", errors.ECODE_STATE)
5837
5838     self.old_flags = old_flags = (node.master_candidate,
5839                                   node.drained, node.offline)
5840     assert old_flags in self._F2R, "Un-handled old flags %s" % str(old_flags)
5841     self.old_role = old_role = self._F2R[old_flags]
5842
5843     # Check for ineffective changes
5844     for attr in self._FLAGS:
5845       if (getattr(self.op, attr) == False and getattr(node, attr) == False):
5846         self.LogInfo("Ignoring request to unset flag %s, already unset", attr)
5847         setattr(self.op, attr, None)
5848
5849     # Past this point, any flag change to False means a transition
5850     # away from the respective state, as only real changes are kept
5851
5852     # TODO: We might query the real power state if it supports OOB
5853     if _SupportsOob(self.cfg, node):
5854       if self.op.offline is False and not (node.powered or
5855                                            self.op.powered == True):
5856         raise errors.OpPrereqError(("Node %s needs to be turned on before its"
5857                                     " offline status can be reset") %
5858                                    self.op.node_name)
5859     elif self.op.powered is not None:
5860       raise errors.OpPrereqError(("Unable to change powered state for node %s"
5861                                   " as it does not support out-of-band"
5862                                   " handling") % self.op.node_name)
5863
5864     # If we're being deofflined/drained, we'll MC ourself if needed
5865     if (self.op.drained == False or self.op.offline == False or
5866         (self.op.master_capable and not node.master_capable)):
5867       if _DecideSelfPromotion(self):
5868         self.op.master_candidate = True
5869         self.LogInfo("Auto-promoting node to master candidate")
5870
5871     # If we're no longer master capable, we'll demote ourselves from MC
5872     if self.op.master_capable == False and node.master_candidate:
5873       self.LogInfo("Demoting from master candidate")
5874       self.op.master_candidate = False
5875
5876     # Compute new role
5877     assert [getattr(self.op, attr) for attr in self._FLAGS].count(True) <= 1
5878     if self.op.master_candidate:
5879       new_role = self._ROLE_CANDIDATE
5880     elif self.op.drained:
5881       new_role = self._ROLE_DRAINED
5882     elif self.op.offline:
5883       new_role = self._ROLE_OFFLINE
5884     elif False in [self.op.master_candidate, self.op.drained, self.op.offline]:
5885       # False is still in new flags, which means we're un-setting (the
5886       # only) True flag
5887       new_role = self._ROLE_REGULAR
5888     else: # no new flags, nothing, keep old role
5889       new_role = old_role
5890
5891     self.new_role = new_role
5892
5893     if old_role == self._ROLE_OFFLINE and new_role != old_role:
5894       # Trying to transition out of offline status
5895       # TODO: Use standard RPC runner, but make sure it works when the node is
5896       # still marked offline
5897       result = rpc.BootstrapRunner().call_version([node.name])[node.name]
5898       if result.fail_msg:
5899         raise errors.OpPrereqError("Node %s is being de-offlined but fails"
5900                                    " to report its version: %s" %
5901                                    (node.name, result.fail_msg),
5902                                    errors.ECODE_STATE)
5903       else:
5904         self.LogWarning("Transitioning node from offline to online state"
5905                         " without using re-add. Please make sure the node"
5906                         " is healthy!")
5907
5908     if self.op.secondary_ip:
5909       # Ok even without locking, because this can't be changed by any LU
5910       master = self.cfg.GetNodeInfo(self.cfg.GetMasterNode())
5911       master_singlehomed = master.secondary_ip == master.primary_ip
5912       if master_singlehomed and self.op.secondary_ip:
5913         raise errors.OpPrereqError("Cannot change the secondary ip on a single"
5914                                    " homed cluster", errors.ECODE_INVAL)
5915
5916       assert not (frozenset(affected_instances) -
5917                   self.owned_locks(locking.LEVEL_INSTANCE))
5918
5919       if node.offline:
5920         if affected_instances:
5921           raise errors.OpPrereqError("Cannot change secondary IP address:"
5922                                      " offline node has instances (%s)"
5923                                      " configured to use it" %
5924                                      utils.CommaJoin(affected_instances.keys()))
5925       else:
5926         # On online nodes, check that no instances are running, and that
5927         # the node has the new ip and we can reach it.
5928         for instance in affected_instances.values():
5929           _CheckInstanceState(self, instance, INSTANCE_DOWN,
5930                               msg="cannot change secondary ip")
5931
5932         _CheckNodeHasSecondaryIP(self, node.name, self.op.secondary_ip, True)
5933         if master.name != node.name:
5934           # check reachability from master secondary ip to new secondary ip
5935           if not netutils.TcpPing(self.op.secondary_ip,
5936                                   constants.DEFAULT_NODED_PORT,
5937                                   source=master.secondary_ip):
5938             raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
5939                                        " based ping to node daemon port",
5940                                        errors.ECODE_ENVIRON)
5941
5942     if self.op.ndparams:
5943       new_ndparams = _GetUpdatedParams(self.node.ndparams, self.op.ndparams)
5944       utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES)
5945       self.new_ndparams = new_ndparams
5946
5947     if self.op.hv_state:
5948       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
5949                                                  self.node.hv_state_static)
5950
5951     if self.op.disk_state:
5952       self.new_disk_state = \
5953         _MergeAndVerifyDiskState(self.op.disk_state,
5954                                  self.node.disk_state_static)
5955
5956   def Exec(self, feedback_fn):
5957     """Modifies a node.
5958
5959     """
5960     node = self.node
5961     old_role = self.old_role
5962     new_role = self.new_role
5963
5964     result = []
5965
5966     if self.op.ndparams:
5967       node.ndparams = self.new_ndparams
5968
5969     if self.op.powered is not None:
5970       node.powered = self.op.powered
5971
5972     if self.op.hv_state:
5973       node.hv_state_static = self.new_hv_state
5974
5975     if self.op.disk_state:
5976       node.disk_state_static = self.new_disk_state
5977
5978     for attr in ["master_capable", "vm_capable"]:
5979       val = getattr(self.op, attr)
5980       if val is not None:
5981         setattr(node, attr, val)
5982         result.append((attr, str(val)))
5983
5984     if new_role != old_role:
5985       # Tell the node to demote itself, if no longer MC and not offline
5986       if old_role == self._ROLE_CANDIDATE and new_role != self._ROLE_OFFLINE:
5987         msg = self.rpc.call_node_demote_from_mc(node.name).fail_msg
5988         if msg:
5989           self.LogWarning("Node failed to demote itself: %s", msg)
5990
5991       new_flags = self._R2F[new_role]
5992       for of, nf, desc in zip(self.old_flags, new_flags, self._FLAGS):
5993         if of != nf:
5994           result.append((desc, str(nf)))
5995       (node.master_candidate, node.drained, node.offline) = new_flags
5996
5997       # we locked all nodes, we adjust the CP before updating this node
5998       if self.lock_all:
5999         _AdjustCandidatePool(self, [node.name])
6000
6001     if self.op.secondary_ip:
6002       node.secondary_ip = self.op.secondary_ip
6003       result.append(("secondary_ip", self.op.secondary_ip))
6004
6005     # this will trigger configuration file update, if needed
6006     self.cfg.Update(node, feedback_fn)
6007
6008     # this will trigger job queue propagation or cleanup if the mc
6009     # flag changed
6010     if [old_role, new_role].count(self._ROLE_CANDIDATE) == 1:
6011       self.context.ReaddNode(node)
6012
6013     return result
6014
6015
6016 class LUNodePowercycle(NoHooksLU):
6017   """Powercycles a node.
6018
6019   """
6020   REQ_BGL = False
6021
6022   def CheckArguments(self):
6023     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
6024     if self.op.node_name == self.cfg.GetMasterNode() and not self.op.force:
6025       raise errors.OpPrereqError("The node is the master and the force"
6026                                  " parameter was not set",
6027                                  errors.ECODE_INVAL)
6028
6029   def ExpandNames(self):
6030     """Locking for PowercycleNode.
6031
6032     This is a last-resort option and shouldn't block on other
6033     jobs. Therefore, we grab no locks.
6034
6035     """
6036     self.needed_locks = {}
6037
6038   def Exec(self, feedback_fn):
6039     """Reboots a node.
6040
6041     """
6042     result = self.rpc.call_node_powercycle(self.op.node_name,
6043                                            self.cfg.GetHypervisorType())
6044     result.Raise("Failed to schedule the reboot")
6045     return result.payload
6046
6047
6048 class LUClusterQuery(NoHooksLU):
6049   """Query cluster configuration.
6050
6051   """
6052   REQ_BGL = False
6053
6054   def ExpandNames(self):
6055     self.needed_locks = {}
6056
6057   def Exec(self, feedback_fn):
6058     """Return cluster config.
6059
6060     """
6061     cluster = self.cfg.GetClusterInfo()
6062     os_hvp = {}
6063
6064     # Filter just for enabled hypervisors
6065     for os_name, hv_dict in cluster.os_hvp.items():
6066       os_hvp[os_name] = {}
6067       for hv_name, hv_params in hv_dict.items():
6068         if hv_name in cluster.enabled_hypervisors:
6069           os_hvp[os_name][hv_name] = hv_params
6070
6071     # Convert ip_family to ip_version
6072     primary_ip_version = constants.IP4_VERSION
6073     if cluster.primary_ip_family == netutils.IP6Address.family:
6074       primary_ip_version = constants.IP6_VERSION
6075
6076     result = {
6077       "software_version": constants.RELEASE_VERSION,
6078       "protocol_version": constants.PROTOCOL_VERSION,
6079       "config_version": constants.CONFIG_VERSION,
6080       "os_api_version": max(constants.OS_API_VERSIONS),
6081       "export_version": constants.EXPORT_VERSION,
6082       "architecture": (platform.architecture()[0], platform.machine()),
6083       "name": cluster.cluster_name,
6084       "master": cluster.master_node,
6085       "default_hypervisor": cluster.primary_hypervisor,
6086       "enabled_hypervisors": cluster.enabled_hypervisors,
6087       "hvparams": dict([(hypervisor_name, cluster.hvparams[hypervisor_name])
6088                         for hypervisor_name in cluster.enabled_hypervisors]),
6089       "os_hvp": os_hvp,
6090       "beparams": cluster.beparams,
6091       "osparams": cluster.osparams,
6092       "ipolicy": cluster.ipolicy,
6093       "nicparams": cluster.nicparams,
6094       "ndparams": cluster.ndparams,
6095       "candidate_pool_size": cluster.candidate_pool_size,
6096       "master_netdev": cluster.master_netdev,
6097       "master_netmask": cluster.master_netmask,
6098       "use_external_mip_script": cluster.use_external_mip_script,
6099       "volume_group_name": cluster.volume_group_name,
6100       "drbd_usermode_helper": cluster.drbd_usermode_helper,
6101       "file_storage_dir": cluster.file_storage_dir,
6102       "shared_file_storage_dir": cluster.shared_file_storage_dir,
6103       "maintain_node_health": cluster.maintain_node_health,
6104       "ctime": cluster.ctime,
6105       "mtime": cluster.mtime,
6106       "uuid": cluster.uuid,
6107       "tags": list(cluster.GetTags()),
6108       "uid_pool": cluster.uid_pool,
6109       "default_iallocator": cluster.default_iallocator,
6110       "reserved_lvs": cluster.reserved_lvs,
6111       "primary_ip_version": primary_ip_version,
6112       "prealloc_wipe_disks": cluster.prealloc_wipe_disks,
6113       "hidden_os": cluster.hidden_os,
6114       "blacklisted_os": cluster.blacklisted_os,
6115       }
6116
6117     return result
6118
6119
6120 class LUClusterConfigQuery(NoHooksLU):
6121   """Return configuration values.
6122
6123   """
6124   REQ_BGL = False
6125   _FIELDS_DYNAMIC = utils.FieldSet()
6126   _FIELDS_STATIC = utils.FieldSet("cluster_name", "master_node", "drain_flag",
6127                                   "watcher_pause", "volume_group_name")
6128
6129   def CheckArguments(self):
6130     _CheckOutputFields(static=self._FIELDS_STATIC,
6131                        dynamic=self._FIELDS_DYNAMIC,
6132                        selected=self.op.output_fields)
6133
6134   def ExpandNames(self):
6135     self.needed_locks = {}
6136
6137   def Exec(self, feedback_fn):
6138     """Dump a representation of the cluster config to the standard output.
6139
6140     """
6141     values = []
6142     for field in self.op.output_fields:
6143       if field == "cluster_name":
6144         entry = self.cfg.GetClusterName()
6145       elif field == "master_node":
6146         entry = self.cfg.GetMasterNode()
6147       elif field == "drain_flag":
6148         entry = os.path.exists(constants.JOB_QUEUE_DRAIN_FILE)
6149       elif field == "watcher_pause":
6150         entry = utils.ReadWatcherPauseFile(constants.WATCHER_PAUSEFILE)
6151       elif field == "volume_group_name":
6152         entry = self.cfg.GetVGName()
6153       else:
6154         raise errors.ParameterError(field)
6155       values.append(entry)
6156     return values
6157
6158
6159 class LUInstanceActivateDisks(NoHooksLU):
6160   """Bring up an instance's disks.
6161
6162   """
6163   REQ_BGL = False
6164
6165   def ExpandNames(self):
6166     self._ExpandAndLockInstance()
6167     self.needed_locks[locking.LEVEL_NODE] = []
6168     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6169
6170   def DeclareLocks(self, level):
6171     if level == locking.LEVEL_NODE:
6172       self._LockInstancesNodes()
6173
6174   def CheckPrereq(self):
6175     """Check prerequisites.
6176
6177     This checks that the instance is in the cluster.
6178
6179     """
6180     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6181     assert self.instance is not None, \
6182       "Cannot retrieve locked instance %s" % self.op.instance_name
6183     _CheckNodeOnline(self, self.instance.primary_node)
6184
6185   def Exec(self, feedback_fn):
6186     """Activate the disks.
6187
6188     """
6189     disks_ok, disks_info = \
6190               _AssembleInstanceDisks(self, self.instance,
6191                                      ignore_size=self.op.ignore_size)
6192     if not disks_ok:
6193       raise errors.OpExecError("Cannot activate block devices")
6194
6195     return disks_info
6196
6197
6198 def _AssembleInstanceDisks(lu, instance, disks=None, ignore_secondaries=False,
6199                            ignore_size=False):
6200   """Prepare the block devices for an instance.
6201
6202   This sets up the block devices on all nodes.
6203
6204   @type lu: L{LogicalUnit}
6205   @param lu: the logical unit on whose behalf we execute
6206   @type instance: L{objects.Instance}
6207   @param instance: the instance for whose disks we assemble
6208   @type disks: list of L{objects.Disk} or None
6209   @param disks: which disks to assemble (or all, if None)
6210   @type ignore_secondaries: boolean
6211   @param ignore_secondaries: if true, errors on secondary nodes
6212       won't result in an error return from the function
6213   @type ignore_size: boolean
6214   @param ignore_size: if true, the current known size of the disk
6215       will not be used during the disk activation, useful for cases
6216       when the size is wrong
6217   @return: False if the operation failed, otherwise a list of
6218       (host, instance_visible_name, node_visible_name)
6219       with the mapping from node devices to instance devices
6220
6221   """
6222   device_info = []
6223   disks_ok = True
6224   iname = instance.name
6225   disks = _ExpandCheckDisks(instance, disks)
6226
6227   # With the two passes mechanism we try to reduce the window of
6228   # opportunity for the race condition of switching DRBD to primary
6229   # before handshaking occured, but we do not eliminate it
6230
6231   # The proper fix would be to wait (with some limits) until the
6232   # connection has been made and drbd transitions from WFConnection
6233   # into any other network-connected state (Connected, SyncTarget,
6234   # SyncSource, etc.)
6235
6236   # 1st pass, assemble on all nodes in secondary mode
6237   for idx, inst_disk in enumerate(disks):
6238     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
6239       if ignore_size:
6240         node_disk = node_disk.Copy()
6241         node_disk.UnsetSize()
6242       lu.cfg.SetDiskID(node_disk, node)
6243       result = lu.rpc.call_blockdev_assemble(node, node_disk, iname, False, idx)
6244       msg = result.fail_msg
6245       if msg:
6246         lu.proc.LogWarning("Could not prepare block device %s on node %s"
6247                            " (is_primary=False, pass=1): %s",
6248                            inst_disk.iv_name, node, msg)
6249         if not ignore_secondaries:
6250           disks_ok = False
6251
6252   # FIXME: race condition on drbd migration to primary
6253
6254   # 2nd pass, do only the primary node
6255   for idx, inst_disk in enumerate(disks):
6256     dev_path = None
6257
6258     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
6259       if node != instance.primary_node:
6260         continue
6261       if ignore_size:
6262         node_disk = node_disk.Copy()
6263         node_disk.UnsetSize()
6264       lu.cfg.SetDiskID(node_disk, node)
6265       result = lu.rpc.call_blockdev_assemble(node, node_disk, iname, True, idx)
6266       msg = result.fail_msg
6267       if msg:
6268         lu.proc.LogWarning("Could not prepare block device %s on node %s"
6269                            " (is_primary=True, pass=2): %s",
6270                            inst_disk.iv_name, node, msg)
6271         disks_ok = False
6272       else:
6273         dev_path = result.payload
6274
6275     device_info.append((instance.primary_node, inst_disk.iv_name, dev_path))
6276
6277   # leave the disks configured for the primary node
6278   # this is a workaround that would be fixed better by
6279   # improving the logical/physical id handling
6280   for disk in disks:
6281     lu.cfg.SetDiskID(disk, instance.primary_node)
6282
6283   return disks_ok, device_info
6284
6285
6286 def _StartInstanceDisks(lu, instance, force):
6287   """Start the disks of an instance.
6288
6289   """
6290   disks_ok, _ = _AssembleInstanceDisks(lu, instance,
6291                                            ignore_secondaries=force)
6292   if not disks_ok:
6293     _ShutdownInstanceDisks(lu, instance)
6294     if force is not None and not force:
6295       lu.proc.LogWarning("", hint="If the message above refers to a"
6296                          " secondary node,"
6297                          " you can retry the operation using '--force'.")
6298     raise errors.OpExecError("Disk consistency error")
6299
6300
6301 class LUInstanceDeactivateDisks(NoHooksLU):
6302   """Shutdown an instance's disks.
6303
6304   """
6305   REQ_BGL = False
6306
6307   def ExpandNames(self):
6308     self._ExpandAndLockInstance()
6309     self.needed_locks[locking.LEVEL_NODE] = []
6310     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6311
6312   def DeclareLocks(self, level):
6313     if level == locking.LEVEL_NODE:
6314       self._LockInstancesNodes()
6315
6316   def CheckPrereq(self):
6317     """Check prerequisites.
6318
6319     This checks that the instance is in the cluster.
6320
6321     """
6322     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6323     assert self.instance is not None, \
6324       "Cannot retrieve locked instance %s" % self.op.instance_name
6325
6326   def Exec(self, feedback_fn):
6327     """Deactivate the disks
6328
6329     """
6330     instance = self.instance
6331     if self.op.force:
6332       _ShutdownInstanceDisks(self, instance)
6333     else:
6334       _SafeShutdownInstanceDisks(self, instance)
6335
6336
6337 def _SafeShutdownInstanceDisks(lu, instance, disks=None):
6338   """Shutdown block devices of an instance.
6339
6340   This function checks if an instance is running, before calling
6341   _ShutdownInstanceDisks.
6342
6343   """
6344   _CheckInstanceState(lu, instance, INSTANCE_DOWN, msg="cannot shutdown disks")
6345   _ShutdownInstanceDisks(lu, instance, disks=disks)
6346
6347
6348 def _ExpandCheckDisks(instance, disks):
6349   """Return the instance disks selected by the disks list
6350
6351   @type disks: list of L{objects.Disk} or None
6352   @param disks: selected disks
6353   @rtype: list of L{objects.Disk}
6354   @return: selected instance disks to act on
6355
6356   """
6357   if disks is None:
6358     return instance.disks
6359   else:
6360     if not set(disks).issubset(instance.disks):
6361       raise errors.ProgrammerError("Can only act on disks belonging to the"
6362                                    " target instance")
6363     return disks
6364
6365
6366 def _ShutdownInstanceDisks(lu, instance, disks=None, ignore_primary=False):
6367   """Shutdown block devices of an instance.
6368
6369   This does the shutdown on all nodes of the instance.
6370
6371   If the ignore_primary is false, errors on the primary node are
6372   ignored.
6373
6374   """
6375   all_result = True
6376   disks = _ExpandCheckDisks(instance, disks)
6377
6378   for disk in disks:
6379     for node, top_disk in disk.ComputeNodeTree(instance.primary_node):
6380       lu.cfg.SetDiskID(top_disk, node)
6381       result = lu.rpc.call_blockdev_shutdown(node, top_disk)
6382       msg = result.fail_msg
6383       if msg:
6384         lu.LogWarning("Could not shutdown block device %s on node %s: %s",
6385                       disk.iv_name, node, msg)
6386         if ((node == instance.primary_node and not ignore_primary) or
6387             (node != instance.primary_node and not result.offline)):
6388           all_result = False
6389   return all_result
6390
6391
6392 def _CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name):
6393   """Checks if a node has enough free memory.
6394
6395   This function check if a given node has the needed amount of free
6396   memory. In case the node has less memory or we cannot get the
6397   information from the node, this function raise an OpPrereqError
6398   exception.
6399
6400   @type lu: C{LogicalUnit}
6401   @param lu: a logical unit from which we get configuration data
6402   @type node: C{str}
6403   @param node: the node to check
6404   @type reason: C{str}
6405   @param reason: string to use in the error message
6406   @type requested: C{int}
6407   @param requested: the amount of memory in MiB to check for
6408   @type hypervisor_name: C{str}
6409   @param hypervisor_name: the hypervisor to ask for memory stats
6410   @rtype: integer
6411   @return: node current free memory
6412   @raise errors.OpPrereqError: if the node doesn't have enough memory, or
6413       we cannot check the node
6414
6415   """
6416   nodeinfo = lu.rpc.call_node_info([node], None, [hypervisor_name])
6417   nodeinfo[node].Raise("Can't get data from node %s" % node,
6418                        prereq=True, ecode=errors.ECODE_ENVIRON)
6419   (_, _, (hv_info, )) = nodeinfo[node].payload
6420
6421   free_mem = hv_info.get("memory_free", None)
6422   if not isinstance(free_mem, int):
6423     raise errors.OpPrereqError("Can't compute free memory on node %s, result"
6424                                " was '%s'" % (node, free_mem),
6425                                errors.ECODE_ENVIRON)
6426   if requested > free_mem:
6427     raise errors.OpPrereqError("Not enough memory on node %s for %s:"
6428                                " needed %s MiB, available %s MiB" %
6429                                (node, reason, requested, free_mem),
6430                                errors.ECODE_NORES)
6431   return free_mem
6432
6433
6434 def _CheckNodesFreeDiskPerVG(lu, nodenames, req_sizes):
6435   """Checks if nodes have enough free disk space in the all VGs.
6436
6437   This function check if all given nodes have the needed amount of
6438   free disk. In case any node has less disk or we cannot get the
6439   information from the node, this function raise an OpPrereqError
6440   exception.
6441
6442   @type lu: C{LogicalUnit}
6443   @param lu: a logical unit from which we get configuration data
6444   @type nodenames: C{list}
6445   @param nodenames: the list of node names to check
6446   @type req_sizes: C{dict}
6447   @param req_sizes: the hash of vg and corresponding amount of disk in
6448       MiB to check for
6449   @raise errors.OpPrereqError: if the node doesn't have enough disk,
6450       or we cannot check the node
6451
6452   """
6453   for vg, req_size in req_sizes.items():
6454     _CheckNodesFreeDiskOnVG(lu, nodenames, vg, req_size)
6455
6456
6457 def _CheckNodesFreeDiskOnVG(lu, nodenames, vg, requested):
6458   """Checks if nodes have enough free disk space in the specified VG.
6459
6460   This function check if all given nodes have the needed amount of
6461   free disk. In case any node has less disk or we cannot get the
6462   information from the node, this function raise an OpPrereqError
6463   exception.
6464
6465   @type lu: C{LogicalUnit}
6466   @param lu: a logical unit from which we get configuration data
6467   @type nodenames: C{list}
6468   @param nodenames: the list of node names to check
6469   @type vg: C{str}
6470   @param vg: the volume group to check
6471   @type requested: C{int}
6472   @param requested: the amount of disk in MiB to check for
6473   @raise errors.OpPrereqError: if the node doesn't have enough disk,
6474       or we cannot check the node
6475
6476   """
6477   nodeinfo = lu.rpc.call_node_info(nodenames, [vg], None)
6478   for node in nodenames:
6479     info = nodeinfo[node]
6480     info.Raise("Cannot get current information from node %s" % node,
6481                prereq=True, ecode=errors.ECODE_ENVIRON)
6482     (_, (vg_info, ), _) = info.payload
6483     vg_free = vg_info.get("vg_free", None)
6484     if not isinstance(vg_free, int):
6485       raise errors.OpPrereqError("Can't compute free disk space on node"
6486                                  " %s for vg %s, result was '%s'" %
6487                                  (node, vg, vg_free), errors.ECODE_ENVIRON)
6488     if requested > vg_free:
6489       raise errors.OpPrereqError("Not enough disk space on target node %s"
6490                                  " vg %s: required %d MiB, available %d MiB" %
6491                                  (node, vg, requested, vg_free),
6492                                  errors.ECODE_NORES)
6493
6494
6495 def _CheckNodesPhysicalCPUs(lu, nodenames, requested, hypervisor_name):
6496   """Checks if nodes have enough physical CPUs
6497
6498   This function checks if all given nodes have the needed number of
6499   physical CPUs. In case any node has less CPUs or we cannot get the
6500   information from the node, this function raises an OpPrereqError
6501   exception.
6502
6503   @type lu: C{LogicalUnit}
6504   @param lu: a logical unit from which we get configuration data
6505   @type nodenames: C{list}
6506   @param nodenames: the list of node names to check
6507   @type requested: C{int}
6508   @param requested: the minimum acceptable number of physical CPUs
6509   @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
6510       or we cannot check the node
6511
6512   """
6513   nodeinfo = lu.rpc.call_node_info(nodenames, None, [hypervisor_name])
6514   for node in nodenames:
6515     info = nodeinfo[node]
6516     info.Raise("Cannot get current information from node %s" % node,
6517                prereq=True, ecode=errors.ECODE_ENVIRON)
6518     (_, _, (hv_info, )) = info.payload
6519     num_cpus = hv_info.get("cpu_total", None)
6520     if not isinstance(num_cpus, int):
6521       raise errors.OpPrereqError("Can't compute the number of physical CPUs"
6522                                  " on node %s, result was '%s'" %
6523                                  (node, num_cpus), errors.ECODE_ENVIRON)
6524     if requested > num_cpus:
6525       raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
6526                                  "required" % (node, num_cpus, requested),
6527                                  errors.ECODE_NORES)
6528
6529
6530 class LUInstanceStartup(LogicalUnit):
6531   """Starts an instance.
6532
6533   """
6534   HPATH = "instance-start"
6535   HTYPE = constants.HTYPE_INSTANCE
6536   REQ_BGL = False
6537
6538   def CheckArguments(self):
6539     # extra beparams
6540     if self.op.beparams:
6541       # fill the beparams dict
6542       objects.UpgradeBeParams(self.op.beparams)
6543       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
6544
6545   def ExpandNames(self):
6546     self._ExpandAndLockInstance()
6547     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
6548
6549   def DeclareLocks(self, level):
6550     if level == locking.LEVEL_NODE_RES:
6551       self._LockInstancesNodes(primary_only=True, level=locking.LEVEL_NODE_RES)
6552
6553   def BuildHooksEnv(self):
6554     """Build hooks env.
6555
6556     This runs on master, primary and secondary nodes of the instance.
6557
6558     """
6559     env = {
6560       "FORCE": self.op.force,
6561       }
6562
6563     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
6564
6565     return env
6566
6567   def BuildHooksNodes(self):
6568     """Build hooks nodes.
6569
6570     """
6571     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6572     return (nl, nl)
6573
6574   def CheckPrereq(self):
6575     """Check prerequisites.
6576
6577     This checks that the instance is in the cluster.
6578
6579     """
6580     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6581     assert self.instance is not None, \
6582       "Cannot retrieve locked instance %s" % self.op.instance_name
6583
6584     # extra hvparams
6585     if self.op.hvparams:
6586       # check hypervisor parameter syntax (locally)
6587       cluster = self.cfg.GetClusterInfo()
6588       utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
6589       filled_hvp = cluster.FillHV(instance)
6590       filled_hvp.update(self.op.hvparams)
6591       hv_type = hypervisor.GetHypervisor(instance.hypervisor)
6592       hv_type.CheckParameterSyntax(filled_hvp)
6593       _CheckHVParams(self, instance.all_nodes, instance.hypervisor, filled_hvp)
6594
6595     _CheckInstanceState(self, instance, INSTANCE_ONLINE)
6596
6597     self.primary_offline = self.cfg.GetNodeInfo(instance.primary_node).offline
6598
6599     if self.primary_offline and self.op.ignore_offline_nodes:
6600       self.proc.LogWarning("Ignoring offline primary node")
6601
6602       if self.op.hvparams or self.op.beparams:
6603         self.proc.LogWarning("Overridden parameters are ignored")
6604     else:
6605       _CheckNodeOnline(self, instance.primary_node)
6606
6607       bep = self.cfg.GetClusterInfo().FillBE(instance)
6608       bep.update(self.op.beparams)
6609
6610       # check bridges existence
6611       _CheckInstanceBridgesExist(self, instance)
6612
6613       remote_info = self.rpc.call_instance_info(instance.primary_node,
6614                                                 instance.name,
6615                                                 instance.hypervisor)
6616       remote_info.Raise("Error checking node %s" % instance.primary_node,
6617                         prereq=True, ecode=errors.ECODE_ENVIRON)
6618       if not remote_info.payload: # not running already
6619         _CheckNodeFreeMemory(self, instance.primary_node,
6620                              "starting instance %s" % instance.name,
6621                              bep[constants.BE_MINMEM], instance.hypervisor)
6622
6623   def Exec(self, feedback_fn):
6624     """Start the instance.
6625
6626     """
6627     instance = self.instance
6628     force = self.op.force
6629
6630     if not self.op.no_remember:
6631       self.cfg.MarkInstanceUp(instance.name)
6632
6633     if self.primary_offline:
6634       assert self.op.ignore_offline_nodes
6635       self.proc.LogInfo("Primary node offline, marked instance as started")
6636     else:
6637       node_current = instance.primary_node
6638
6639       _StartInstanceDisks(self, instance, force)
6640
6641       result = \
6642         self.rpc.call_instance_start(node_current,
6643                                      (instance, self.op.hvparams,
6644                                       self.op.beparams),
6645                                      self.op.startup_paused)
6646       msg = result.fail_msg
6647       if msg:
6648         _ShutdownInstanceDisks(self, instance)
6649         raise errors.OpExecError("Could not start instance: %s" % msg)
6650
6651
6652 class LUInstanceReboot(LogicalUnit):
6653   """Reboot an instance.
6654
6655   """
6656   HPATH = "instance-reboot"
6657   HTYPE = constants.HTYPE_INSTANCE
6658   REQ_BGL = False
6659
6660   def ExpandNames(self):
6661     self._ExpandAndLockInstance()
6662
6663   def BuildHooksEnv(self):
6664     """Build hooks env.
6665
6666     This runs on master, primary and secondary nodes of the instance.
6667
6668     """
6669     env = {
6670       "IGNORE_SECONDARIES": self.op.ignore_secondaries,
6671       "REBOOT_TYPE": self.op.reboot_type,
6672       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
6673       }
6674
6675     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
6676
6677     return env
6678
6679   def BuildHooksNodes(self):
6680     """Build hooks nodes.
6681
6682     """
6683     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6684     return (nl, nl)
6685
6686   def CheckPrereq(self):
6687     """Check prerequisites.
6688
6689     This checks that the instance is in the cluster.
6690
6691     """
6692     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6693     assert self.instance is not None, \
6694       "Cannot retrieve locked instance %s" % self.op.instance_name
6695     _CheckInstanceState(self, instance, INSTANCE_ONLINE)
6696     _CheckNodeOnline(self, instance.primary_node)
6697
6698     # check bridges existence
6699     _CheckInstanceBridgesExist(self, instance)
6700
6701   def Exec(self, feedback_fn):
6702     """Reboot the instance.
6703
6704     """
6705     instance = self.instance
6706     ignore_secondaries = self.op.ignore_secondaries
6707     reboot_type = self.op.reboot_type
6708
6709     remote_info = self.rpc.call_instance_info(instance.primary_node,
6710                                               instance.name,
6711                                               instance.hypervisor)
6712     remote_info.Raise("Error checking node %s" % instance.primary_node)
6713     instance_running = bool(remote_info.payload)
6714
6715     node_current = instance.primary_node
6716
6717     if instance_running and reboot_type in [constants.INSTANCE_REBOOT_SOFT,
6718                                             constants.INSTANCE_REBOOT_HARD]:
6719       for disk in instance.disks:
6720         self.cfg.SetDiskID(disk, node_current)
6721       result = self.rpc.call_instance_reboot(node_current, instance,
6722                                              reboot_type,
6723                                              self.op.shutdown_timeout)
6724       result.Raise("Could not reboot instance")
6725     else:
6726       if instance_running:
6727         result = self.rpc.call_instance_shutdown(node_current, instance,
6728                                                  self.op.shutdown_timeout)
6729         result.Raise("Could not shutdown instance for full reboot")
6730         _ShutdownInstanceDisks(self, instance)
6731       else:
6732         self.LogInfo("Instance %s was already stopped, starting now",
6733                      instance.name)
6734       _StartInstanceDisks(self, instance, ignore_secondaries)
6735       result = self.rpc.call_instance_start(node_current,
6736                                             (instance, None, None), False)
6737       msg = result.fail_msg
6738       if msg:
6739         _ShutdownInstanceDisks(self, instance)
6740         raise errors.OpExecError("Could not start instance for"
6741                                  " full reboot: %s" % msg)
6742
6743     self.cfg.MarkInstanceUp(instance.name)
6744
6745
6746 class LUInstanceShutdown(LogicalUnit):
6747   """Shutdown an instance.
6748
6749   """
6750   HPATH = "instance-stop"
6751   HTYPE = constants.HTYPE_INSTANCE
6752   REQ_BGL = False
6753
6754   def ExpandNames(self):
6755     self._ExpandAndLockInstance()
6756
6757   def BuildHooksEnv(self):
6758     """Build hooks env.
6759
6760     This runs on master, primary and secondary nodes of the instance.
6761
6762     """
6763     env = _BuildInstanceHookEnvByObject(self, self.instance)
6764     env["TIMEOUT"] = self.op.timeout
6765     return env
6766
6767   def BuildHooksNodes(self):
6768     """Build hooks nodes.
6769
6770     """
6771     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6772     return (nl, nl)
6773
6774   def CheckPrereq(self):
6775     """Check prerequisites.
6776
6777     This checks that the instance is in the cluster.
6778
6779     """
6780     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6781     assert self.instance is not None, \
6782       "Cannot retrieve locked instance %s" % self.op.instance_name
6783
6784     _CheckInstanceState(self, self.instance, INSTANCE_ONLINE)
6785
6786     self.primary_offline = \
6787       self.cfg.GetNodeInfo(self.instance.primary_node).offline
6788
6789     if self.primary_offline and self.op.ignore_offline_nodes:
6790       self.proc.LogWarning("Ignoring offline primary node")
6791     else:
6792       _CheckNodeOnline(self, self.instance.primary_node)
6793
6794   def Exec(self, feedback_fn):
6795     """Shutdown the instance.
6796
6797     """
6798     instance = self.instance
6799     node_current = instance.primary_node
6800     timeout = self.op.timeout
6801
6802     if not self.op.no_remember:
6803       self.cfg.MarkInstanceDown(instance.name)
6804
6805     if self.primary_offline:
6806       assert self.op.ignore_offline_nodes
6807       self.proc.LogInfo("Primary node offline, marked instance as stopped")
6808     else:
6809       result = self.rpc.call_instance_shutdown(node_current, instance, timeout)
6810       msg = result.fail_msg
6811       if msg:
6812         self.proc.LogWarning("Could not shutdown instance: %s" % msg)
6813
6814       _ShutdownInstanceDisks(self, instance)
6815
6816
6817 class LUInstanceReinstall(LogicalUnit):
6818   """Reinstall an instance.
6819
6820   """
6821   HPATH = "instance-reinstall"
6822   HTYPE = constants.HTYPE_INSTANCE
6823   REQ_BGL = False
6824
6825   def ExpandNames(self):
6826     self._ExpandAndLockInstance()
6827
6828   def BuildHooksEnv(self):
6829     """Build hooks env.
6830
6831     This runs on master, primary and secondary nodes of the instance.
6832
6833     """
6834     return _BuildInstanceHookEnvByObject(self, self.instance)
6835
6836   def BuildHooksNodes(self):
6837     """Build hooks nodes.
6838
6839     """
6840     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6841     return (nl, nl)
6842
6843   def CheckPrereq(self):
6844     """Check prerequisites.
6845
6846     This checks that the instance is in the cluster and is not running.
6847
6848     """
6849     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6850     assert instance is not None, \
6851       "Cannot retrieve locked instance %s" % self.op.instance_name
6852     _CheckNodeOnline(self, instance.primary_node, "Instance primary node"
6853                      " offline, cannot reinstall")
6854     for node in instance.secondary_nodes:
6855       _CheckNodeOnline(self, node, "Instance secondary node offline,"
6856                        " cannot reinstall")
6857
6858     if instance.disk_template == constants.DT_DISKLESS:
6859       raise errors.OpPrereqError("Instance '%s' has no disks" %
6860                                  self.op.instance_name,
6861                                  errors.ECODE_INVAL)
6862     _CheckInstanceState(self, instance, INSTANCE_DOWN, msg="cannot reinstall")
6863
6864     if self.op.os_type is not None:
6865       # OS verification
6866       pnode = _ExpandNodeName(self.cfg, instance.primary_node)
6867       _CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant)
6868       instance_os = self.op.os_type
6869     else:
6870       instance_os = instance.os
6871
6872     nodelist = list(instance.all_nodes)
6873
6874     if self.op.osparams:
6875       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
6876       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
6877       self.os_inst = i_osdict # the new dict (without defaults)
6878     else:
6879       self.os_inst = None
6880
6881     self.instance = instance
6882
6883   def Exec(self, feedback_fn):
6884     """Reinstall the instance.
6885
6886     """
6887     inst = self.instance
6888
6889     if self.op.os_type is not None:
6890       feedback_fn("Changing OS to '%s'..." % self.op.os_type)
6891       inst.os = self.op.os_type
6892       # Write to configuration
6893       self.cfg.Update(inst, feedback_fn)
6894
6895     _StartInstanceDisks(self, inst, None)
6896     try:
6897       feedback_fn("Running the instance OS create scripts...")
6898       # FIXME: pass debug option from opcode to backend
6899       result = self.rpc.call_instance_os_add(inst.primary_node,
6900                                              (inst, self.os_inst), True,
6901                                              self.op.debug_level)
6902       result.Raise("Could not install OS for instance %s on node %s" %
6903                    (inst.name, inst.primary_node))
6904     finally:
6905       _ShutdownInstanceDisks(self, inst)
6906
6907
6908 class LUInstanceRecreateDisks(LogicalUnit):
6909   """Recreate an instance's missing disks.
6910
6911   """
6912   HPATH = "instance-recreate-disks"
6913   HTYPE = constants.HTYPE_INSTANCE
6914   REQ_BGL = False
6915
6916   _MODIFYABLE = frozenset([
6917     constants.IDISK_SIZE,
6918     constants.IDISK_MODE,
6919     ])
6920
6921   # New or changed disk parameters may have different semantics
6922   assert constants.IDISK_PARAMS == (_MODIFYABLE | frozenset([
6923     constants.IDISK_ADOPT,
6924
6925     # TODO: Implement support changing VG while recreating
6926     constants.IDISK_VG,
6927     constants.IDISK_METAVG,
6928     ]))
6929
6930   def CheckArguments(self):
6931     if self.op.disks and ht.TPositiveInt(self.op.disks[0]):
6932       # Normalize and convert deprecated list of disk indices
6933       self.op.disks = [(idx, {}) for idx in sorted(frozenset(self.op.disks))]
6934
6935     duplicates = utils.FindDuplicates(map(compat.fst, self.op.disks))
6936     if duplicates:
6937       raise errors.OpPrereqError("Some disks have been specified more than"
6938                                  " once: %s" % utils.CommaJoin(duplicates),
6939                                  errors.ECODE_INVAL)
6940
6941     for (idx, params) in self.op.disks:
6942       utils.ForceDictType(params, constants.IDISK_PARAMS_TYPES)
6943       unsupported = frozenset(params.keys()) - self._MODIFYABLE
6944       if unsupported:
6945         raise errors.OpPrereqError("Parameters for disk %s try to change"
6946                                    " unmodifyable parameter(s): %s" %
6947                                    (idx, utils.CommaJoin(unsupported)),
6948                                    errors.ECODE_INVAL)
6949
6950   def ExpandNames(self):
6951     self._ExpandAndLockInstance()
6952     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
6953     if self.op.nodes:
6954       self.op.nodes = [_ExpandNodeName(self.cfg, n) for n in self.op.nodes]
6955       self.needed_locks[locking.LEVEL_NODE] = list(self.op.nodes)
6956     else:
6957       self.needed_locks[locking.LEVEL_NODE] = []
6958     self.needed_locks[locking.LEVEL_NODE_RES] = []
6959
6960   def DeclareLocks(self, level):
6961     if level == locking.LEVEL_NODE:
6962       # if we replace the nodes, we only need to lock the old primary,
6963       # otherwise we need to lock all nodes for disk re-creation
6964       primary_only = bool(self.op.nodes)
6965       self._LockInstancesNodes(primary_only=primary_only)
6966     elif level == locking.LEVEL_NODE_RES:
6967       # Copy node locks
6968       self.needed_locks[locking.LEVEL_NODE_RES] = \
6969         self.needed_locks[locking.LEVEL_NODE][:]
6970
6971   def BuildHooksEnv(self):
6972     """Build hooks env.
6973
6974     This runs on master, primary and secondary nodes of the instance.
6975
6976     """
6977     return _BuildInstanceHookEnvByObject(self, self.instance)
6978
6979   def BuildHooksNodes(self):
6980     """Build hooks nodes.
6981
6982     """
6983     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6984     return (nl, nl)
6985
6986   def CheckPrereq(self):
6987     """Check prerequisites.
6988
6989     This checks that the instance is in the cluster and is not running.
6990
6991     """
6992     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6993     assert instance is not None, \
6994       "Cannot retrieve locked instance %s" % self.op.instance_name
6995     if self.op.nodes:
6996       if len(self.op.nodes) != len(instance.all_nodes):
6997         raise errors.OpPrereqError("Instance %s currently has %d nodes, but"
6998                                    " %d replacement nodes were specified" %
6999                                    (instance.name, len(instance.all_nodes),
7000                                     len(self.op.nodes)),
7001                                    errors.ECODE_INVAL)
7002       assert instance.disk_template != constants.DT_DRBD8 or \
7003           len(self.op.nodes) == 2
7004       assert instance.disk_template != constants.DT_PLAIN or \
7005           len(self.op.nodes) == 1
7006       primary_node = self.op.nodes[0]
7007     else:
7008       primary_node = instance.primary_node
7009     _CheckNodeOnline(self, primary_node)
7010
7011     if instance.disk_template == constants.DT_DISKLESS:
7012       raise errors.OpPrereqError("Instance '%s' has no disks" %
7013                                  self.op.instance_name, errors.ECODE_INVAL)
7014
7015     # if we replace nodes *and* the old primary is offline, we don't
7016     # check
7017     assert instance.primary_node in self.owned_locks(locking.LEVEL_NODE)
7018     assert instance.primary_node in self.owned_locks(locking.LEVEL_NODE_RES)
7019     old_pnode = self.cfg.GetNodeInfo(instance.primary_node)
7020     if not (self.op.nodes and old_pnode.offline):
7021       _CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
7022                           msg="cannot recreate disks")
7023
7024     if self.op.disks:
7025       self.disks = dict(self.op.disks)
7026     else:
7027       self.disks = dict((idx, {}) for idx in range(len(instance.disks)))
7028
7029     maxidx = max(self.disks.keys())
7030     if maxidx >= len(instance.disks):
7031       raise errors.OpPrereqError("Invalid disk index '%s'" % maxidx,
7032                                  errors.ECODE_INVAL)
7033
7034     if (self.op.nodes and
7035         sorted(self.disks.keys()) != range(len(instance.disks))):
7036       raise errors.OpPrereqError("Can't recreate disks partially and"
7037                                  " change the nodes at the same time",
7038                                  errors.ECODE_INVAL)
7039
7040     self.instance = instance
7041
7042   def Exec(self, feedback_fn):
7043     """Recreate the disks.
7044
7045     """
7046     instance = self.instance
7047
7048     assert (self.owned_locks(locking.LEVEL_NODE) ==
7049             self.owned_locks(locking.LEVEL_NODE_RES))
7050
7051     to_skip = []
7052     mods = [] # keeps track of needed changes
7053
7054     for idx, disk in enumerate(instance.disks):
7055       try:
7056         changes = self.disks[idx]
7057       except KeyError:
7058         # Disk should not be recreated
7059         to_skip.append(idx)
7060         continue
7061
7062       # update secondaries for disks, if needed
7063       if self.op.nodes and disk.dev_type == constants.LD_DRBD8:
7064         # need to update the nodes and minors
7065         assert len(self.op.nodes) == 2
7066         assert len(disk.logical_id) == 6 # otherwise disk internals
7067                                          # have changed
7068         (_, _, old_port, _, _, old_secret) = disk.logical_id
7069         new_minors = self.cfg.AllocateDRBDMinor(self.op.nodes, instance.name)
7070         new_id = (self.op.nodes[0], self.op.nodes[1], old_port,
7071                   new_minors[0], new_minors[1], old_secret)
7072         assert len(disk.logical_id) == len(new_id)
7073       else:
7074         new_id = None
7075
7076       mods.append((idx, new_id, changes))
7077
7078     # now that we have passed all asserts above, we can apply the mods
7079     # in a single run (to avoid partial changes)
7080     for idx, new_id, changes in mods:
7081       disk = instance.disks[idx]
7082       if new_id is not None:
7083         assert disk.dev_type == constants.LD_DRBD8
7084         disk.logical_id = new_id
7085       if changes:
7086         disk.Update(size=changes.get(constants.IDISK_SIZE, None),
7087                     mode=changes.get(constants.IDISK_MODE, None))
7088
7089     # change primary node, if needed
7090     if self.op.nodes:
7091       instance.primary_node = self.op.nodes[0]
7092       self.LogWarning("Changing the instance's nodes, you will have to"
7093                       " remove any disks left on the older nodes manually")
7094
7095     if self.op.nodes:
7096       self.cfg.Update(instance, feedback_fn)
7097
7098     _CreateDisks(self, instance, to_skip=to_skip)
7099
7100
7101 class LUInstanceRename(LogicalUnit):
7102   """Rename an instance.
7103
7104   """
7105   HPATH = "instance-rename"
7106   HTYPE = constants.HTYPE_INSTANCE
7107
7108   def CheckArguments(self):
7109     """Check arguments.
7110
7111     """
7112     if self.op.ip_check and not self.op.name_check:
7113       # TODO: make the ip check more flexible and not depend on the name check
7114       raise errors.OpPrereqError("IP address check requires a name check",
7115                                  errors.ECODE_INVAL)
7116
7117   def BuildHooksEnv(self):
7118     """Build hooks env.
7119
7120     This runs on master, primary and secondary nodes of the instance.
7121
7122     """
7123     env = _BuildInstanceHookEnvByObject(self, self.instance)
7124     env["INSTANCE_NEW_NAME"] = self.op.new_name
7125     return env
7126
7127   def BuildHooksNodes(self):
7128     """Build hooks nodes.
7129
7130     """
7131     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7132     return (nl, nl)
7133
7134   def CheckPrereq(self):
7135     """Check prerequisites.
7136
7137     This checks that the instance is in the cluster and is not running.
7138
7139     """
7140     self.op.instance_name = _ExpandInstanceName(self.cfg,
7141                                                 self.op.instance_name)
7142     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7143     assert instance is not None
7144     _CheckNodeOnline(self, instance.primary_node)
7145     _CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
7146                         msg="cannot rename")
7147     self.instance = instance
7148
7149     new_name = self.op.new_name
7150     if self.op.name_check:
7151       hostname = netutils.GetHostname(name=new_name)
7152       if hostname.name != new_name:
7153         self.LogInfo("Resolved given name '%s' to '%s'", new_name,
7154                      hostname.name)
7155       if not utils.MatchNameComponent(self.op.new_name, [hostname.name]):
7156         raise errors.OpPrereqError(("Resolved hostname '%s' does not look the"
7157                                     " same as given hostname '%s'") %
7158                                     (hostname.name, self.op.new_name),
7159                                     errors.ECODE_INVAL)
7160       new_name = self.op.new_name = hostname.name
7161       if (self.op.ip_check and
7162           netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
7163         raise errors.OpPrereqError("IP %s of instance %s already in use" %
7164                                    (hostname.ip, new_name),
7165                                    errors.ECODE_NOTUNIQUE)
7166
7167     instance_list = self.cfg.GetInstanceList()
7168     if new_name in instance_list and new_name != instance.name:
7169       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
7170                                  new_name, errors.ECODE_EXISTS)
7171
7172   def Exec(self, feedback_fn):
7173     """Rename the instance.
7174
7175     """
7176     inst = self.instance
7177     old_name = inst.name
7178
7179     rename_file_storage = False
7180     if (inst.disk_template in constants.DTS_FILEBASED and
7181         self.op.new_name != inst.name):
7182       old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
7183       rename_file_storage = True
7184
7185     self.cfg.RenameInstance(inst.name, self.op.new_name)
7186     # Change the instance lock. This is definitely safe while we hold the BGL.
7187     # Otherwise the new lock would have to be added in acquired mode.
7188     assert self.REQ_BGL
7189     self.glm.remove(locking.LEVEL_INSTANCE, old_name)
7190     self.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
7191
7192     # re-read the instance from the configuration after rename
7193     inst = self.cfg.GetInstanceInfo(self.op.new_name)
7194
7195     if rename_file_storage:
7196       new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
7197       result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
7198                                                      old_file_storage_dir,
7199                                                      new_file_storage_dir)
7200       result.Raise("Could not rename on node %s directory '%s' to '%s'"
7201                    " (but the instance has been renamed in Ganeti)" %
7202                    (inst.primary_node, old_file_storage_dir,
7203                     new_file_storage_dir))
7204
7205     _StartInstanceDisks(self, inst, None)
7206     try:
7207       result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
7208                                                  old_name, self.op.debug_level)
7209       msg = result.fail_msg
7210       if msg:
7211         msg = ("Could not run OS rename script for instance %s on node %s"
7212                " (but the instance has been renamed in Ganeti): %s" %
7213                (inst.name, inst.primary_node, msg))
7214         self.proc.LogWarning(msg)
7215     finally:
7216       _ShutdownInstanceDisks(self, inst)
7217
7218     return inst.name
7219
7220
7221 class LUInstanceRemove(LogicalUnit):
7222   """Remove an instance.
7223
7224   """
7225   HPATH = "instance-remove"
7226   HTYPE = constants.HTYPE_INSTANCE
7227   REQ_BGL = False
7228
7229   def ExpandNames(self):
7230     self._ExpandAndLockInstance()
7231     self.needed_locks[locking.LEVEL_NODE] = []
7232     self.needed_locks[locking.LEVEL_NODE_RES] = []
7233     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7234
7235   def DeclareLocks(self, level):
7236     if level == locking.LEVEL_NODE:
7237       self._LockInstancesNodes()
7238     elif level == locking.LEVEL_NODE_RES:
7239       # Copy node locks
7240       self.needed_locks[locking.LEVEL_NODE_RES] = \
7241         self.needed_locks[locking.LEVEL_NODE][:]
7242
7243   def BuildHooksEnv(self):
7244     """Build hooks env.
7245
7246     This runs on master, primary and secondary nodes of the instance.
7247
7248     """
7249     env = _BuildInstanceHookEnvByObject(self, self.instance)
7250     env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
7251     return env
7252
7253   def BuildHooksNodes(self):
7254     """Build hooks nodes.
7255
7256     """
7257     nl = [self.cfg.GetMasterNode()]
7258     nl_post = list(self.instance.all_nodes) + nl
7259     return (nl, nl_post)
7260
7261   def CheckPrereq(self):
7262     """Check prerequisites.
7263
7264     This checks that the instance is in the cluster.
7265
7266     """
7267     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7268     assert self.instance is not None, \
7269       "Cannot retrieve locked instance %s" % self.op.instance_name
7270
7271   def Exec(self, feedback_fn):
7272     """Remove the instance.
7273
7274     """
7275     instance = self.instance
7276     logging.info("Shutting down instance %s on node %s",
7277                  instance.name, instance.primary_node)
7278
7279     result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
7280                                              self.op.shutdown_timeout)
7281     msg = result.fail_msg
7282     if msg:
7283       if self.op.ignore_failures:
7284         feedback_fn("Warning: can't shutdown instance: %s" % msg)
7285       else:
7286         raise errors.OpExecError("Could not shutdown instance %s on"
7287                                  " node %s: %s" %
7288                                  (instance.name, instance.primary_node, msg))
7289
7290     assert (self.owned_locks(locking.LEVEL_NODE) ==
7291             self.owned_locks(locking.LEVEL_NODE_RES))
7292     assert not (set(instance.all_nodes) -
7293                 self.owned_locks(locking.LEVEL_NODE)), \
7294       "Not owning correct locks"
7295
7296     _RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
7297
7298
7299 def _RemoveInstance(lu, feedback_fn, instance, ignore_failures):
7300   """Utility function to remove an instance.
7301
7302   """
7303   logging.info("Removing block devices for instance %s", instance.name)
7304
7305   if not _RemoveDisks(lu, instance):
7306     if not ignore_failures:
7307       raise errors.OpExecError("Can't remove instance's disks")
7308     feedback_fn("Warning: can't remove instance's disks")
7309
7310   logging.info("Removing instance %s out of cluster config", instance.name)
7311
7312   lu.cfg.RemoveInstance(instance.name)
7313
7314   assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \
7315     "Instance lock removal conflict"
7316
7317   # Remove lock for the instance
7318   lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
7319
7320
7321 class LUInstanceQuery(NoHooksLU):
7322   """Logical unit for querying instances.
7323
7324   """
7325   # pylint: disable=W0142
7326   REQ_BGL = False
7327
7328   def CheckArguments(self):
7329     self.iq = _InstanceQuery(qlang.MakeSimpleFilter("name", self.op.names),
7330                              self.op.output_fields, self.op.use_locking)
7331
7332   def ExpandNames(self):
7333     self.iq.ExpandNames(self)
7334
7335   def DeclareLocks(self, level):
7336     self.iq.DeclareLocks(self, level)
7337
7338   def Exec(self, feedback_fn):
7339     return self.iq.OldStyleQuery(self)
7340
7341
7342 class LUInstanceFailover(LogicalUnit):
7343   """Failover an instance.
7344
7345   """
7346   HPATH = "instance-failover"
7347   HTYPE = constants.HTYPE_INSTANCE
7348   REQ_BGL = False
7349
7350   def CheckArguments(self):
7351     """Check the arguments.
7352
7353     """
7354     self.iallocator = getattr(self.op, "iallocator", None)
7355     self.target_node = getattr(self.op, "target_node", None)
7356
7357   def ExpandNames(self):
7358     self._ExpandAndLockInstance()
7359
7360     if self.op.target_node is not None:
7361       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7362
7363     self.needed_locks[locking.LEVEL_NODE] = []
7364     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7365
7366     self.needed_locks[locking.LEVEL_NODE_RES] = []
7367     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
7368
7369     ignore_consistency = self.op.ignore_consistency
7370     shutdown_timeout = self.op.shutdown_timeout
7371     self._migrater = TLMigrateInstance(self, self.op.instance_name,
7372                                        cleanup=False,
7373                                        failover=True,
7374                                        ignore_consistency=ignore_consistency,
7375                                        shutdown_timeout=shutdown_timeout,
7376                                        ignore_ipolicy=self.op.ignore_ipolicy)
7377     self.tasklets = [self._migrater]
7378
7379   def DeclareLocks(self, level):
7380     if level == locking.LEVEL_NODE:
7381       instance = self.context.cfg.GetInstanceInfo(self.op.instance_name)
7382       if instance.disk_template in constants.DTS_EXT_MIRROR:
7383         if self.op.target_node is None:
7384           self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7385         else:
7386           self.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
7387                                                    self.op.target_node]
7388         del self.recalculate_locks[locking.LEVEL_NODE]
7389       else:
7390         self._LockInstancesNodes()
7391     elif level == locking.LEVEL_NODE_RES:
7392       # Copy node locks
7393       self.needed_locks[locking.LEVEL_NODE_RES] = \
7394         self.needed_locks[locking.LEVEL_NODE][:]
7395
7396   def BuildHooksEnv(self):
7397     """Build hooks env.
7398
7399     This runs on master, primary and secondary nodes of the instance.
7400
7401     """
7402     instance = self._migrater.instance
7403     source_node = instance.primary_node
7404     target_node = self.op.target_node
7405     env = {
7406       "IGNORE_CONSISTENCY": self.op.ignore_consistency,
7407       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
7408       "OLD_PRIMARY": source_node,
7409       "NEW_PRIMARY": target_node,
7410       }
7411
7412     if instance.disk_template in constants.DTS_INT_MIRROR:
7413       env["OLD_SECONDARY"] = instance.secondary_nodes[0]
7414       env["NEW_SECONDARY"] = source_node
7415     else:
7416       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = ""
7417
7418     env.update(_BuildInstanceHookEnvByObject(self, instance))
7419
7420     return env
7421
7422   def BuildHooksNodes(self):
7423     """Build hooks nodes.
7424
7425     """
7426     instance = self._migrater.instance
7427     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
7428     return (nl, nl + [instance.primary_node])
7429
7430
7431 class LUInstanceMigrate(LogicalUnit):
7432   """Migrate an instance.
7433
7434   This is migration without shutting down, compared to the failover,
7435   which is done with shutdown.
7436
7437   """
7438   HPATH = "instance-migrate"
7439   HTYPE = constants.HTYPE_INSTANCE
7440   REQ_BGL = False
7441
7442   def ExpandNames(self):
7443     self._ExpandAndLockInstance()
7444
7445     if self.op.target_node is not None:
7446       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7447
7448     self.needed_locks[locking.LEVEL_NODE] = []
7449     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7450
7451     self.needed_locks[locking.LEVEL_NODE] = []
7452     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7453
7454     self._migrater = TLMigrateInstance(self, self.op.instance_name,
7455                                        cleanup=self.op.cleanup,
7456                                        failover=False,
7457                                        fallback=self.op.allow_failover,
7458                                        ignore_ipolicy=self.op.ignore_ipolicy)
7459     self.tasklets = [self._migrater]
7460
7461   def DeclareLocks(self, level):
7462     if level == locking.LEVEL_NODE:
7463       instance = self.context.cfg.GetInstanceInfo(self.op.instance_name)
7464       if instance.disk_template in constants.DTS_EXT_MIRROR:
7465         if self.op.target_node is None:
7466           self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7467         else:
7468           self.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
7469                                                    self.op.target_node]
7470         del self.recalculate_locks[locking.LEVEL_NODE]
7471       else:
7472         self._LockInstancesNodes()
7473     elif level == locking.LEVEL_NODE_RES:
7474       # Copy node locks
7475       self.needed_locks[locking.LEVEL_NODE_RES] = \
7476         self.needed_locks[locking.LEVEL_NODE][:]
7477
7478   def BuildHooksEnv(self):
7479     """Build hooks env.
7480
7481     This runs on master, primary and secondary nodes of the instance.
7482
7483     """
7484     instance = self._migrater.instance
7485     source_node = instance.primary_node
7486     target_node = self.op.target_node
7487     env = _BuildInstanceHookEnvByObject(self, instance)
7488     env.update({
7489       "MIGRATE_LIVE": self._migrater.live,
7490       "MIGRATE_CLEANUP": self.op.cleanup,
7491       "OLD_PRIMARY": source_node,
7492       "NEW_PRIMARY": target_node,
7493       })
7494
7495     if instance.disk_template in constants.DTS_INT_MIRROR:
7496       env["OLD_SECONDARY"] = target_node
7497       env["NEW_SECONDARY"] = source_node
7498     else:
7499       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = None
7500
7501     return env
7502
7503   def BuildHooksNodes(self):
7504     """Build hooks nodes.
7505
7506     """
7507     instance = self._migrater.instance
7508     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
7509     return (nl, nl + [instance.primary_node])
7510
7511
7512 class LUInstanceMove(LogicalUnit):
7513   """Move an instance by data-copying.
7514
7515   """
7516   HPATH = "instance-move"
7517   HTYPE = constants.HTYPE_INSTANCE
7518   REQ_BGL = False
7519
7520   def ExpandNames(self):
7521     self._ExpandAndLockInstance()
7522     target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7523     self.op.target_node = target_node
7524     self.needed_locks[locking.LEVEL_NODE] = [target_node]
7525     self.needed_locks[locking.LEVEL_NODE_RES] = []
7526     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
7527
7528   def DeclareLocks(self, level):
7529     if level == locking.LEVEL_NODE:
7530       self._LockInstancesNodes(primary_only=True)
7531     elif level == locking.LEVEL_NODE_RES:
7532       # Copy node locks
7533       self.needed_locks[locking.LEVEL_NODE_RES] = \
7534         self.needed_locks[locking.LEVEL_NODE][:]
7535
7536   def BuildHooksEnv(self):
7537     """Build hooks env.
7538
7539     This runs on master, primary and secondary nodes of the instance.
7540
7541     """
7542     env = {
7543       "TARGET_NODE": self.op.target_node,
7544       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
7545       }
7546     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
7547     return env
7548
7549   def BuildHooksNodes(self):
7550     """Build hooks nodes.
7551
7552     """
7553     nl = [
7554       self.cfg.GetMasterNode(),
7555       self.instance.primary_node,
7556       self.op.target_node,
7557       ]
7558     return (nl, nl)
7559
7560   def CheckPrereq(self):
7561     """Check prerequisites.
7562
7563     This checks that the instance is in the cluster.
7564
7565     """
7566     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7567     assert self.instance is not None, \
7568       "Cannot retrieve locked instance %s" % self.op.instance_name
7569
7570     node = self.cfg.GetNodeInfo(self.op.target_node)
7571     assert node is not None, \
7572       "Cannot retrieve locked node %s" % self.op.target_node
7573
7574     self.target_node = target_node = node.name
7575
7576     if target_node == instance.primary_node:
7577       raise errors.OpPrereqError("Instance %s is already on the node %s" %
7578                                  (instance.name, target_node),
7579                                  errors.ECODE_STATE)
7580
7581     bep = self.cfg.GetClusterInfo().FillBE(instance)
7582
7583     for idx, dsk in enumerate(instance.disks):
7584       if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
7585         raise errors.OpPrereqError("Instance disk %d has a complex layout,"
7586                                    " cannot copy" % idx, errors.ECODE_STATE)
7587
7588     _CheckNodeOnline(self, target_node)
7589     _CheckNodeNotDrained(self, target_node)
7590     _CheckNodeVmCapable(self, target_node)
7591     ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(),
7592                                      self.cfg.GetNodeGroup(node.group))
7593     _CheckTargetNodeIPolicy(self, ipolicy, instance, node,
7594                             ignore=self.op.ignore_ipolicy)
7595
7596     if instance.admin_state == constants.ADMINST_UP:
7597       # check memory requirements on the secondary node
7598       _CheckNodeFreeMemory(self, target_node, "failing over instance %s" %
7599                            instance.name, bep[constants.BE_MAXMEM],
7600                            instance.hypervisor)
7601     else:
7602       self.LogInfo("Not checking memory on the secondary node as"
7603                    " instance will not be started")
7604
7605     # check bridge existance
7606     _CheckInstanceBridgesExist(self, instance, node=target_node)
7607
7608   def Exec(self, feedback_fn):
7609     """Move an instance.
7610
7611     The move is done by shutting it down on its present node, copying
7612     the data over (slow) and starting it on the new node.
7613
7614     """
7615     instance = self.instance
7616
7617     source_node = instance.primary_node
7618     target_node = self.target_node
7619
7620     self.LogInfo("Shutting down instance %s on source node %s",
7621                  instance.name, source_node)
7622
7623     assert (self.owned_locks(locking.LEVEL_NODE) ==
7624             self.owned_locks(locking.LEVEL_NODE_RES))
7625
7626     result = self.rpc.call_instance_shutdown(source_node, instance,
7627                                              self.op.shutdown_timeout)
7628     msg = result.fail_msg
7629     if msg:
7630       if self.op.ignore_consistency:
7631         self.proc.LogWarning("Could not shutdown instance %s on node %s."
7632                              " Proceeding anyway. Please make sure node"
7633                              " %s is down. Error details: %s",
7634                              instance.name, source_node, source_node, msg)
7635       else:
7636         raise errors.OpExecError("Could not shutdown instance %s on"
7637                                  " node %s: %s" %
7638                                  (instance.name, source_node, msg))
7639
7640     # create the target disks
7641     try:
7642       _CreateDisks(self, instance, target_node=target_node)
7643     except errors.OpExecError:
7644       self.LogWarning("Device creation failed, reverting...")
7645       try:
7646         _RemoveDisks(self, instance, target_node=target_node)
7647       finally:
7648         self.cfg.ReleaseDRBDMinors(instance.name)
7649         raise
7650
7651     cluster_name = self.cfg.GetClusterInfo().cluster_name
7652
7653     errs = []
7654     # activate, get path, copy the data over
7655     for idx, disk in enumerate(instance.disks):
7656       self.LogInfo("Copying data for disk %d", idx)
7657       result = self.rpc.call_blockdev_assemble(target_node, disk,
7658                                                instance.name, True, idx)
7659       if result.fail_msg:
7660         self.LogWarning("Can't assemble newly created disk %d: %s",
7661                         idx, result.fail_msg)
7662         errs.append(result.fail_msg)
7663         break
7664       dev_path = result.payload
7665       result = self.rpc.call_blockdev_export(source_node, disk,
7666                                              target_node, dev_path,
7667                                              cluster_name)
7668       if result.fail_msg:
7669         self.LogWarning("Can't copy data over for disk %d: %s",
7670                         idx, result.fail_msg)
7671         errs.append(result.fail_msg)
7672         break
7673
7674     if errs:
7675       self.LogWarning("Some disks failed to copy, aborting")
7676       try:
7677         _RemoveDisks(self, instance, target_node=target_node)
7678       finally:
7679         self.cfg.ReleaseDRBDMinors(instance.name)
7680         raise errors.OpExecError("Errors during disk copy: %s" %
7681                                  (",".join(errs),))
7682
7683     instance.primary_node = target_node
7684     self.cfg.Update(instance, feedback_fn)
7685
7686     self.LogInfo("Removing the disks on the original node")
7687     _RemoveDisks(self, instance, target_node=source_node)
7688
7689     # Only start the instance if it's marked as up
7690     if instance.admin_state == constants.ADMINST_UP:
7691       self.LogInfo("Starting instance %s on node %s",
7692                    instance.name, target_node)
7693
7694       disks_ok, _ = _AssembleInstanceDisks(self, instance,
7695                                            ignore_secondaries=True)
7696       if not disks_ok:
7697         _ShutdownInstanceDisks(self, instance)
7698         raise errors.OpExecError("Can't activate the instance's disks")
7699
7700       result = self.rpc.call_instance_start(target_node,
7701                                             (instance, None, None), False)
7702       msg = result.fail_msg
7703       if msg:
7704         _ShutdownInstanceDisks(self, instance)
7705         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
7706                                  (instance.name, target_node, msg))
7707
7708
7709 class LUNodeMigrate(LogicalUnit):
7710   """Migrate all instances from a node.
7711
7712   """
7713   HPATH = "node-migrate"
7714   HTYPE = constants.HTYPE_NODE
7715   REQ_BGL = False
7716
7717   def CheckArguments(self):
7718     pass
7719
7720   def ExpandNames(self):
7721     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
7722
7723     self.share_locks = _ShareAll()
7724     self.needed_locks = {
7725       locking.LEVEL_NODE: [self.op.node_name],
7726       }
7727
7728   def BuildHooksEnv(self):
7729     """Build hooks env.
7730
7731     This runs on the master, the primary and all the secondaries.
7732
7733     """
7734     return {
7735       "NODE_NAME": self.op.node_name,
7736       }
7737
7738   def BuildHooksNodes(self):
7739     """Build hooks nodes.
7740
7741     """
7742     nl = [self.cfg.GetMasterNode()]
7743     return (nl, nl)
7744
7745   def CheckPrereq(self):
7746     pass
7747
7748   def Exec(self, feedback_fn):
7749     # Prepare jobs for migration instances
7750     jobs = [
7751       [opcodes.OpInstanceMigrate(instance_name=inst.name,
7752                                  mode=self.op.mode,
7753                                  live=self.op.live,
7754                                  iallocator=self.op.iallocator,
7755                                  target_node=self.op.target_node,
7756                                  ignore_ipolicy=self.op.ignore_ipolicy)]
7757       for inst in _GetNodePrimaryInstances(self.cfg, self.op.node_name)
7758       ]
7759
7760     # TODO: Run iallocator in this opcode and pass correct placement options to
7761     # OpInstanceMigrate. Since other jobs can modify the cluster between
7762     # running the iallocator and the actual migration, a good consistency model
7763     # will have to be found.
7764
7765     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
7766             frozenset([self.op.node_name]))
7767
7768     return ResultWithJobs(jobs)
7769
7770
7771 class TLMigrateInstance(Tasklet):
7772   """Tasklet class for instance migration.
7773
7774   @type live: boolean
7775   @ivar live: whether the migration will be done live or non-live;
7776       this variable is initalized only after CheckPrereq has run
7777   @type cleanup: boolean
7778   @ivar cleanup: Wheater we cleanup from a failed migration
7779   @type iallocator: string
7780   @ivar iallocator: The iallocator used to determine target_node
7781   @type target_node: string
7782   @ivar target_node: If given, the target_node to reallocate the instance to
7783   @type failover: boolean
7784   @ivar failover: Whether operation results in failover or migration
7785   @type fallback: boolean
7786   @ivar fallback: Whether fallback to failover is allowed if migration not
7787                   possible
7788   @type ignore_consistency: boolean
7789   @ivar ignore_consistency: Wheter we should ignore consistency between source
7790                             and target node
7791   @type shutdown_timeout: int
7792   @ivar shutdown_timeout: In case of failover timeout of the shutdown
7793   @type ignore_ipolicy: bool
7794   @ivar ignore_ipolicy: If true, we can ignore instance policy when migrating
7795
7796   """
7797
7798   # Constants
7799   _MIGRATION_POLL_INTERVAL = 1      # seconds
7800   _MIGRATION_FEEDBACK_INTERVAL = 10 # seconds
7801
7802   def __init__(self, lu, instance_name, cleanup=False,
7803                failover=False, fallback=False,
7804                ignore_consistency=False,
7805                shutdown_timeout=constants.DEFAULT_SHUTDOWN_TIMEOUT,
7806                ignore_ipolicy=False):
7807     """Initializes this class.
7808
7809     """
7810     Tasklet.__init__(self, lu)
7811
7812     # Parameters
7813     self.instance_name = instance_name
7814     self.cleanup = cleanup
7815     self.live = False # will be overridden later
7816     self.failover = failover
7817     self.fallback = fallback
7818     self.ignore_consistency = ignore_consistency
7819     self.shutdown_timeout = shutdown_timeout
7820     self.ignore_ipolicy = ignore_ipolicy
7821
7822   def CheckPrereq(self):
7823     """Check prerequisites.
7824
7825     This checks that the instance is in the cluster.
7826
7827     """
7828     instance_name = _ExpandInstanceName(self.lu.cfg, self.instance_name)
7829     instance = self.cfg.GetInstanceInfo(instance_name)
7830     assert instance is not None
7831     self.instance = instance
7832     cluster = self.cfg.GetClusterInfo()
7833
7834     if (not self.cleanup and
7835         not instance.admin_state == constants.ADMINST_UP and
7836         not self.failover and self.fallback):
7837       self.lu.LogInfo("Instance is marked down or offline, fallback allowed,"
7838                       " switching to failover")
7839       self.failover = True
7840
7841     if instance.disk_template not in constants.DTS_MIRRORED:
7842       if self.failover:
7843         text = "failovers"
7844       else:
7845         text = "migrations"
7846       raise errors.OpPrereqError("Instance's disk layout '%s' does not allow"
7847                                  " %s" % (instance.disk_template, text),
7848                                  errors.ECODE_STATE)
7849
7850     if instance.disk_template in constants.DTS_EXT_MIRROR:
7851       _CheckIAllocatorOrNode(self.lu, "iallocator", "target_node")
7852
7853       if self.lu.op.iallocator:
7854         self._RunAllocator()
7855       else:
7856         # We set set self.target_node as it is required by
7857         # BuildHooksEnv
7858         self.target_node = self.lu.op.target_node
7859
7860       # Check that the target node is correct in terms of instance policy
7861       nodeinfo = self.cfg.GetNodeInfo(self.target_node)
7862       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
7863       ipolicy = _CalculateGroupIPolicy(cluster, group_info)
7864       _CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo,
7865                               ignore=self.ignore_ipolicy)
7866
7867       # self.target_node is already populated, either directly or by the
7868       # iallocator run
7869       target_node = self.target_node
7870       if self.target_node == instance.primary_node:
7871         raise errors.OpPrereqError("Cannot migrate instance %s"
7872                                    " to its primary (%s)" %
7873                                    (instance.name, instance.primary_node))
7874
7875       if len(self.lu.tasklets) == 1:
7876         # It is safe to release locks only when we're the only tasklet
7877         # in the LU
7878         _ReleaseLocks(self.lu, locking.LEVEL_NODE,
7879                       keep=[instance.primary_node, self.target_node])
7880
7881     else:
7882       secondary_nodes = instance.secondary_nodes
7883       if not secondary_nodes:
7884         raise errors.ConfigurationError("No secondary node but using"
7885                                         " %s disk template" %
7886                                         instance.disk_template)
7887       target_node = secondary_nodes[0]
7888       if self.lu.op.iallocator or (self.lu.op.target_node and
7889                                    self.lu.op.target_node != target_node):
7890         if self.failover:
7891           text = "failed over"
7892         else:
7893           text = "migrated"
7894         raise errors.OpPrereqError("Instances with disk template %s cannot"
7895                                    " be %s to arbitrary nodes"
7896                                    " (neither an iallocator nor a target"
7897                                    " node can be passed)" %
7898                                    (instance.disk_template, text),
7899                                    errors.ECODE_INVAL)
7900       nodeinfo = self.cfg.GetNodeInfo(target_node)
7901       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
7902       ipolicy = _CalculateGroupIPolicy(cluster, group_info)
7903       _CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo,
7904                               ignore=self.ignore_ipolicy)
7905
7906     i_be = cluster.FillBE(instance)
7907
7908     # check memory requirements on the secondary node
7909     if (not self.cleanup and
7910          (not self.failover or instance.admin_state == constants.ADMINST_UP)):
7911       self.tgt_free_mem = _CheckNodeFreeMemory(self.lu, target_node,
7912                                                "migrating instance %s" %
7913                                                instance.name,
7914                                                i_be[constants.BE_MINMEM],
7915                                                instance.hypervisor)
7916     else:
7917       self.lu.LogInfo("Not checking memory on the secondary node as"
7918                       " instance will not be started")
7919
7920     # check if failover must be forced instead of migration
7921     if (not self.cleanup and not self.failover and
7922         i_be[constants.BE_ALWAYS_FAILOVER]):
7923       if self.fallback:
7924         self.lu.LogInfo("Instance configured to always failover; fallback"
7925                         " to failover")
7926         self.failover = True
7927       else:
7928         raise errors.OpPrereqError("This instance has been configured to"
7929                                    " always failover, please allow failover",
7930                                    errors.ECODE_STATE)
7931
7932     # check bridge existance
7933     _CheckInstanceBridgesExist(self.lu, instance, node=target_node)
7934
7935     if not self.cleanup:
7936       _CheckNodeNotDrained(self.lu, target_node)
7937       if not self.failover:
7938         result = self.rpc.call_instance_migratable(instance.primary_node,
7939                                                    instance)
7940         if result.fail_msg and self.fallback:
7941           self.lu.LogInfo("Can't migrate, instance offline, fallback to"
7942                           " failover")
7943           self.failover = True
7944         else:
7945           result.Raise("Can't migrate, please use failover",
7946                        prereq=True, ecode=errors.ECODE_STATE)
7947
7948     assert not (self.failover and self.cleanup)
7949
7950     if not self.failover:
7951       if self.lu.op.live is not None and self.lu.op.mode is not None:
7952         raise errors.OpPrereqError("Only one of the 'live' and 'mode'"
7953                                    " parameters are accepted",
7954                                    errors.ECODE_INVAL)
7955       if self.lu.op.live is not None:
7956         if self.lu.op.live:
7957           self.lu.op.mode = constants.HT_MIGRATION_LIVE
7958         else:
7959           self.lu.op.mode = constants.HT_MIGRATION_NONLIVE
7960         # reset the 'live' parameter to None so that repeated
7961         # invocations of CheckPrereq do not raise an exception
7962         self.lu.op.live = None
7963       elif self.lu.op.mode is None:
7964         # read the default value from the hypervisor
7965         i_hv = cluster.FillHV(self.instance, skip_globals=False)
7966         self.lu.op.mode = i_hv[constants.HV_MIGRATION_MODE]
7967
7968       self.live = self.lu.op.mode == constants.HT_MIGRATION_LIVE
7969     else:
7970       # Failover is never live
7971       self.live = False
7972
7973     if not (self.failover or self.cleanup):
7974       remote_info = self.rpc.call_instance_info(instance.primary_node,
7975                                                 instance.name,
7976                                                 instance.hypervisor)
7977       remote_info.Raise("Error checking instance on node %s" %
7978                         instance.primary_node)
7979       instance_running = bool(remote_info.payload)
7980       if instance_running:
7981         self.current_mem = int(remote_info.payload["memory"])
7982
7983   def _RunAllocator(self):
7984     """Run the allocator based on input opcode.
7985
7986     """
7987     # FIXME: add a self.ignore_ipolicy option
7988     ial = IAllocator(self.cfg, self.rpc,
7989                      mode=constants.IALLOCATOR_MODE_RELOC,
7990                      name=self.instance_name,
7991                      # TODO See why hail breaks with a single node below
7992                      relocate_from=[self.instance.primary_node,
7993                                     self.instance.primary_node],
7994                      )
7995
7996     ial.Run(self.lu.op.iallocator)
7997
7998     if not ial.success:
7999       raise errors.OpPrereqError("Can't compute nodes using"
8000                                  " iallocator '%s': %s" %
8001                                  (self.lu.op.iallocator, ial.info),
8002                                  errors.ECODE_NORES)
8003     if len(ial.result) != ial.required_nodes:
8004       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
8005                                  " of nodes (%s), required %s" %
8006                                  (self.lu.op.iallocator, len(ial.result),
8007                                   ial.required_nodes), errors.ECODE_FAULT)
8008     self.target_node = ial.result[0]
8009     self.lu.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
8010                  self.instance_name, self.lu.op.iallocator,
8011                  utils.CommaJoin(ial.result))
8012
8013   def _WaitUntilSync(self):
8014     """Poll with custom rpc for disk sync.
8015
8016     This uses our own step-based rpc call.
8017
8018     """
8019     self.feedback_fn("* wait until resync is done")
8020     all_done = False
8021     while not all_done:
8022       all_done = True
8023       result = self.rpc.call_drbd_wait_sync(self.all_nodes,
8024                                             self.nodes_ip,
8025                                             self.instance.disks)
8026       min_percent = 100
8027       for node, nres in result.items():
8028         nres.Raise("Cannot resync disks on node %s" % node)
8029         node_done, node_percent = nres.payload
8030         all_done = all_done and node_done
8031         if node_percent is not None:
8032           min_percent = min(min_percent, node_percent)
8033       if not all_done:
8034         if min_percent < 100:
8035           self.feedback_fn("   - progress: %.1f%%" % min_percent)
8036         time.sleep(2)
8037
8038   def _EnsureSecondary(self, node):
8039     """Demote a node to secondary.
8040
8041     """
8042     self.feedback_fn("* switching node %s to secondary mode" % node)
8043
8044     for dev in self.instance.disks:
8045       self.cfg.SetDiskID(dev, node)
8046
8047     result = self.rpc.call_blockdev_close(node, self.instance.name,
8048                                           self.instance.disks)
8049     result.Raise("Cannot change disk to secondary on node %s" % node)
8050
8051   def _GoStandalone(self):
8052     """Disconnect from the network.
8053
8054     """
8055     self.feedback_fn("* changing into standalone mode")
8056     result = self.rpc.call_drbd_disconnect_net(self.all_nodes, self.nodes_ip,
8057                                                self.instance.disks)
8058     for node, nres in result.items():
8059       nres.Raise("Cannot disconnect disks node %s" % node)
8060
8061   def _GoReconnect(self, multimaster):
8062     """Reconnect to the network.
8063
8064     """
8065     if multimaster:
8066       msg = "dual-master"
8067     else:
8068       msg = "single-master"
8069     self.feedback_fn("* changing disks into %s mode" % msg)
8070     result = self.rpc.call_drbd_attach_net(self.all_nodes, self.nodes_ip,
8071                                            self.instance.disks,
8072                                            self.instance.name, multimaster)
8073     for node, nres in result.items():
8074       nres.Raise("Cannot change disks config on node %s" % node)
8075
8076   def _ExecCleanup(self):
8077     """Try to cleanup after a failed migration.
8078
8079     The cleanup is done by:
8080       - check that the instance is running only on one node
8081         (and update the config if needed)
8082       - change disks on its secondary node to secondary
8083       - wait until disks are fully synchronized
8084       - disconnect from the network
8085       - change disks into single-master mode
8086       - wait again until disks are fully synchronized
8087
8088     """
8089     instance = self.instance
8090     target_node = self.target_node
8091     source_node = self.source_node
8092
8093     # check running on only one node
8094     self.feedback_fn("* checking where the instance actually runs"
8095                      " (if this hangs, the hypervisor might be in"
8096                      " a bad state)")
8097     ins_l = self.rpc.call_instance_list(self.all_nodes, [instance.hypervisor])
8098     for node, result in ins_l.items():
8099       result.Raise("Can't contact node %s" % node)
8100
8101     runningon_source = instance.name in ins_l[source_node].payload
8102     runningon_target = instance.name in ins_l[target_node].payload
8103
8104     if runningon_source and runningon_target:
8105       raise errors.OpExecError("Instance seems to be running on two nodes,"
8106                                " or the hypervisor is confused; you will have"
8107                                " to ensure manually that it runs only on one"
8108                                " and restart this operation")
8109
8110     if not (runningon_source or runningon_target):
8111       raise errors.OpExecError("Instance does not seem to be running at all;"
8112                                " in this case it's safer to repair by"
8113                                " running 'gnt-instance stop' to ensure disk"
8114                                " shutdown, and then restarting it")
8115
8116     if runningon_target:
8117       # the migration has actually succeeded, we need to update the config
8118       self.feedback_fn("* instance running on secondary node (%s),"
8119                        " updating config" % target_node)
8120       instance.primary_node = target_node
8121       self.cfg.Update(instance, self.feedback_fn)
8122       demoted_node = source_node
8123     else:
8124       self.feedback_fn("* instance confirmed to be running on its"
8125                        " primary node (%s)" % source_node)
8126       demoted_node = target_node
8127
8128     if instance.disk_template in constants.DTS_INT_MIRROR:
8129       self._EnsureSecondary(demoted_node)
8130       try:
8131         self._WaitUntilSync()
8132       except errors.OpExecError:
8133         # we ignore here errors, since if the device is standalone, it
8134         # won't be able to sync
8135         pass
8136       self._GoStandalone()
8137       self._GoReconnect(False)
8138       self._WaitUntilSync()
8139
8140     self.feedback_fn("* done")
8141
8142   def _RevertDiskStatus(self):
8143     """Try to revert the disk status after a failed migration.
8144
8145     """
8146     target_node = self.target_node
8147     if self.instance.disk_template in constants.DTS_EXT_MIRROR:
8148       return
8149
8150     try:
8151       self._EnsureSecondary(target_node)
8152       self._GoStandalone()
8153       self._GoReconnect(False)
8154       self._WaitUntilSync()
8155     except errors.OpExecError, err:
8156       self.lu.LogWarning("Migration failed and I can't reconnect the drives,"
8157                          " please try to recover the instance manually;"
8158                          " error '%s'" % str(err))
8159
8160   def _AbortMigration(self):
8161     """Call the hypervisor code to abort a started migration.
8162
8163     """
8164     instance = self.instance
8165     target_node = self.target_node
8166     source_node = self.source_node
8167     migration_info = self.migration_info
8168
8169     abort_result = self.rpc.call_instance_finalize_migration_dst(target_node,
8170                                                                  instance,
8171                                                                  migration_info,
8172                                                                  False)
8173     abort_msg = abort_result.fail_msg
8174     if abort_msg:
8175       logging.error("Aborting migration failed on target node %s: %s",
8176                     target_node, abort_msg)
8177       # Don't raise an exception here, as we stil have to try to revert the
8178       # disk status, even if this step failed.
8179
8180     abort_result = self.rpc.call_instance_finalize_migration_src(source_node,
8181         instance, False, self.live)
8182     abort_msg = abort_result.fail_msg
8183     if abort_msg:
8184       logging.error("Aborting migration failed on source node %s: %s",
8185                     source_node, abort_msg)
8186
8187   def _ExecMigration(self):
8188     """Migrate an instance.
8189
8190     The migrate is done by:
8191       - change the disks into dual-master mode
8192       - wait until disks are fully synchronized again
8193       - migrate the instance
8194       - change disks on the new secondary node (the old primary) to secondary
8195       - wait until disks are fully synchronized
8196       - change disks into single-master mode
8197
8198     """
8199     instance = self.instance
8200     target_node = self.target_node
8201     source_node = self.source_node
8202
8203     # Check for hypervisor version mismatch and warn the user.
8204     nodeinfo = self.rpc.call_node_info([source_node, target_node],
8205                                        None, [self.instance.hypervisor])
8206     for ninfo in nodeinfo.values():
8207       ninfo.Raise("Unable to retrieve node information from node '%s'" %
8208                   ninfo.node)
8209     (_, _, (src_info, )) = nodeinfo[source_node].payload
8210     (_, _, (dst_info, )) = nodeinfo[target_node].payload
8211
8212     if ((constants.HV_NODEINFO_KEY_VERSION in src_info) and
8213         (constants.HV_NODEINFO_KEY_VERSION in dst_info)):
8214       src_version = src_info[constants.HV_NODEINFO_KEY_VERSION]
8215       dst_version = dst_info[constants.HV_NODEINFO_KEY_VERSION]
8216       if src_version != dst_version:
8217         self.feedback_fn("* warning: hypervisor version mismatch between"
8218                          " source (%s) and target (%s) node" %
8219                          (src_version, dst_version))
8220
8221     self.feedback_fn("* checking disk consistency between source and target")
8222     for dev in instance.disks:
8223       if not _CheckDiskConsistency(self.lu, dev, target_node, False):
8224         raise errors.OpExecError("Disk %s is degraded or not fully"
8225                                  " synchronized on target node,"
8226                                  " aborting migration" % dev.iv_name)
8227
8228     if self.current_mem > self.tgt_free_mem:
8229       self.feedback_fn("* setting instance memory to %s" % self.tgt_free_mem)
8230       rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
8231                                                      instance,
8232                                                      self.tgt_free_mem)
8233       rpcres.Raise("Cannot modify instance runtime memory")
8234
8235     # First get the migration information from the remote node
8236     result = self.rpc.call_migration_info(source_node, instance)
8237     msg = result.fail_msg
8238     if msg:
8239       log_err = ("Failed fetching source migration information from %s: %s" %
8240                  (source_node, msg))
8241       logging.error(log_err)
8242       raise errors.OpExecError(log_err)
8243
8244     self.migration_info = migration_info = result.payload
8245
8246     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
8247       # Then switch the disks to master/master mode
8248       self._EnsureSecondary(target_node)
8249       self._GoStandalone()
8250       self._GoReconnect(True)
8251       self._WaitUntilSync()
8252
8253     self.feedback_fn("* preparing %s to accept the instance" % target_node)
8254     result = self.rpc.call_accept_instance(target_node,
8255                                            instance,
8256                                            migration_info,
8257                                            self.nodes_ip[target_node])
8258
8259     msg = result.fail_msg
8260     if msg:
8261       logging.error("Instance pre-migration failed, trying to revert"
8262                     " disk status: %s", msg)
8263       self.feedback_fn("Pre-migration failed, aborting")
8264       self._AbortMigration()
8265       self._RevertDiskStatus()
8266       raise errors.OpExecError("Could not pre-migrate instance %s: %s" %
8267                                (instance.name, msg))
8268
8269     self.feedback_fn("* migrating instance to %s" % target_node)
8270     result = self.rpc.call_instance_migrate(source_node, instance,
8271                                             self.nodes_ip[target_node],
8272                                             self.live)
8273     msg = result.fail_msg
8274     if msg:
8275       logging.error("Instance migration failed, trying to revert"
8276                     " disk status: %s", msg)
8277       self.feedback_fn("Migration failed, aborting")
8278       self._AbortMigration()
8279       self._RevertDiskStatus()
8280       raise errors.OpExecError("Could not migrate instance %s: %s" %
8281                                (instance.name, msg))
8282
8283     self.feedback_fn("* starting memory transfer")
8284     last_feedback = time.time()
8285     while True:
8286       result = self.rpc.call_instance_get_migration_status(source_node,
8287                                                            instance)
8288       msg = result.fail_msg
8289       ms = result.payload   # MigrationStatus instance
8290       if msg or (ms.status in constants.HV_MIGRATION_FAILED_STATUSES):
8291         logging.error("Instance migration failed, trying to revert"
8292                       " disk status: %s", msg)
8293         self.feedback_fn("Migration failed, aborting")
8294         self._AbortMigration()
8295         self._RevertDiskStatus()
8296         raise errors.OpExecError("Could not migrate instance %s: %s" %
8297                                  (instance.name, msg))
8298
8299       if result.payload.status != constants.HV_MIGRATION_ACTIVE:
8300         self.feedback_fn("* memory transfer complete")
8301         break
8302
8303       if (utils.TimeoutExpired(last_feedback,
8304                                self._MIGRATION_FEEDBACK_INTERVAL) and
8305           ms.transferred_ram is not None):
8306         mem_progress = 100 * float(ms.transferred_ram) / float(ms.total_ram)
8307         self.feedback_fn("* memory transfer progress: %.2f %%" % mem_progress)
8308         last_feedback = time.time()
8309
8310       time.sleep(self._MIGRATION_POLL_INTERVAL)
8311
8312     result = self.rpc.call_instance_finalize_migration_src(source_node,
8313                                                            instance,
8314                                                            True,
8315                                                            self.live)
8316     msg = result.fail_msg
8317     if msg:
8318       logging.error("Instance migration succeeded, but finalization failed"
8319                     " on the source node: %s", msg)
8320       raise errors.OpExecError("Could not finalize instance migration: %s" %
8321                                msg)
8322
8323     instance.primary_node = target_node
8324
8325     # distribute new instance config to the other nodes
8326     self.cfg.Update(instance, self.feedback_fn)
8327
8328     result = self.rpc.call_instance_finalize_migration_dst(target_node,
8329                                                            instance,
8330                                                            migration_info,
8331                                                            True)
8332     msg = result.fail_msg
8333     if msg:
8334       logging.error("Instance migration succeeded, but finalization failed"
8335                     " on the target node: %s", msg)
8336       raise errors.OpExecError("Could not finalize instance migration: %s" %
8337                                msg)
8338
8339     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
8340       self._EnsureSecondary(source_node)
8341       self._WaitUntilSync()
8342       self._GoStandalone()
8343       self._GoReconnect(False)
8344       self._WaitUntilSync()
8345
8346     # If the instance's disk template is `rbd' and there was a successful
8347     # migration, unmap the device from the source node.
8348     if self.instance.disk_template == constants.DT_RBD:
8349       disks = _ExpandCheckDisks(instance, instance.disks)
8350       self.feedback_fn("* unmapping instance's disks from %s" % source_node)
8351       for disk in disks:
8352         result = self.rpc.call_blockdev_shutdown(source_node, disk)
8353         msg = result.fail_msg
8354         if msg:
8355           logging.error("Migration was successful, but couldn't unmap the"
8356                         " block device %s on source node %s: %s",
8357                         disk.iv_name, source_node, msg)
8358           logging.error("You need to unmap the device %s manually on %s",
8359                         disk.iv_name, source_node)
8360
8361     self.feedback_fn("* done")
8362
8363   def _ExecFailover(self):
8364     """Failover an instance.
8365
8366     The failover is done by shutting it down on its present node and
8367     starting it on the secondary.
8368
8369     """
8370     instance = self.instance
8371     primary_node = self.cfg.GetNodeInfo(instance.primary_node)
8372
8373     source_node = instance.primary_node
8374     target_node = self.target_node
8375
8376     if instance.admin_state == constants.ADMINST_UP:
8377       self.feedback_fn("* checking disk consistency between source and target")
8378       for dev in instance.disks:
8379         # for drbd, these are drbd over lvm
8380         if not _CheckDiskConsistency(self.lu, dev, target_node, False):
8381           if primary_node.offline:
8382             self.feedback_fn("Node %s is offline, ignoring degraded disk %s on"
8383                              " target node %s" %
8384                              (primary_node.name, dev.iv_name, target_node))
8385           elif not self.ignore_consistency:
8386             raise errors.OpExecError("Disk %s is degraded on target node,"
8387                                      " aborting failover" % dev.iv_name)
8388     else:
8389       self.feedback_fn("* not checking disk consistency as instance is not"
8390                        " running")
8391
8392     self.feedback_fn("* shutting down instance on source node")
8393     logging.info("Shutting down instance %s on node %s",
8394                  instance.name, source_node)
8395
8396     result = self.rpc.call_instance_shutdown(source_node, instance,
8397                                              self.shutdown_timeout)
8398     msg = result.fail_msg
8399     if msg:
8400       if self.ignore_consistency or primary_node.offline:
8401         self.lu.LogWarning("Could not shutdown instance %s on node %s,"
8402                            " proceeding anyway; please make sure node"
8403                            " %s is down; error details: %s",
8404                            instance.name, source_node, source_node, msg)
8405       else:
8406         raise errors.OpExecError("Could not shutdown instance %s on"
8407                                  " node %s: %s" %
8408                                  (instance.name, source_node, msg))
8409
8410     self.feedback_fn("* deactivating the instance's disks on source node")
8411     if not _ShutdownInstanceDisks(self.lu, instance, ignore_primary=True):
8412       raise errors.OpExecError("Can't shut down the instance's disks")
8413
8414     instance.primary_node = target_node
8415     # distribute new instance config to the other nodes
8416     self.cfg.Update(instance, self.feedback_fn)
8417
8418     # Only start the instance if it's marked as up
8419     if instance.admin_state == constants.ADMINST_UP:
8420       self.feedback_fn("* activating the instance's disks on target node %s" %
8421                        target_node)
8422       logging.info("Starting instance %s on node %s",
8423                    instance.name, target_node)
8424
8425       disks_ok, _ = _AssembleInstanceDisks(self.lu, instance,
8426                                            ignore_secondaries=True)
8427       if not disks_ok:
8428         _ShutdownInstanceDisks(self.lu, instance)
8429         raise errors.OpExecError("Can't activate the instance's disks")
8430
8431       self.feedback_fn("* starting the instance on the target node %s" %
8432                        target_node)
8433       result = self.rpc.call_instance_start(target_node, (instance, None, None),
8434                                             False)
8435       msg = result.fail_msg
8436       if msg:
8437         _ShutdownInstanceDisks(self.lu, instance)
8438         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
8439                                  (instance.name, target_node, msg))
8440
8441   def Exec(self, feedback_fn):
8442     """Perform the migration.
8443
8444     """
8445     self.feedback_fn = feedback_fn
8446     self.source_node = self.instance.primary_node
8447
8448     # FIXME: if we implement migrate-to-any in DRBD, this needs fixing
8449     if self.instance.disk_template in constants.DTS_INT_MIRROR:
8450       self.target_node = self.instance.secondary_nodes[0]
8451       # Otherwise self.target_node has been populated either
8452       # directly, or through an iallocator.
8453
8454     self.all_nodes = [self.source_node, self.target_node]
8455     self.nodes_ip = dict((name, node.secondary_ip) for (name, node)
8456                          in self.cfg.GetMultiNodeInfo(self.all_nodes))
8457
8458     if self.failover:
8459       feedback_fn("Failover instance %s" % self.instance.name)
8460       self._ExecFailover()
8461     else:
8462       feedback_fn("Migrating instance %s" % self.instance.name)
8463
8464       if self.cleanup:
8465         return self._ExecCleanup()
8466       else:
8467         return self._ExecMigration()
8468
8469
8470 def _CreateBlockDev(lu, node, instance, device, force_create,
8471                     info, force_open):
8472   """Create a tree of block devices on a given node.
8473
8474   If this device type has to be created on secondaries, create it and
8475   all its children.
8476
8477   If not, just recurse to children keeping the same 'force' value.
8478
8479   @param lu: the lu on whose behalf we execute
8480   @param node: the node on which to create the device
8481   @type instance: L{objects.Instance}
8482   @param instance: the instance which owns the device
8483   @type device: L{objects.Disk}
8484   @param device: the device to create
8485   @type force_create: boolean
8486   @param force_create: whether to force creation of this device; this
8487       will be change to True whenever we find a device which has
8488       CreateOnSecondary() attribute
8489   @param info: the extra 'metadata' we should attach to the device
8490       (this will be represented as a LVM tag)
8491   @type force_open: boolean
8492   @param force_open: this parameter will be passes to the
8493       L{backend.BlockdevCreate} function where it specifies
8494       whether we run on primary or not, and it affects both
8495       the child assembly and the device own Open() execution
8496
8497   """
8498   if device.CreateOnSecondary():
8499     force_create = True
8500
8501   if device.children:
8502     for child in device.children:
8503       _CreateBlockDev(lu, node, instance, child, force_create,
8504                       info, force_open)
8505
8506   if not force_create:
8507     return
8508
8509   _CreateSingleBlockDev(lu, node, instance, device, info, force_open)
8510
8511
8512 def _CreateSingleBlockDev(lu, node, instance, device, info, force_open):
8513   """Create a single block device on a given node.
8514
8515   This will not recurse over children of the device, so they must be
8516   created in advance.
8517
8518   @param lu: the lu on whose behalf we execute
8519   @param node: the node on which to create the device
8520   @type instance: L{objects.Instance}
8521   @param instance: the instance which owns the device
8522   @type device: L{objects.Disk}
8523   @param device: the device to create
8524   @param info: the extra 'metadata' we should attach to the device
8525       (this will be represented as a LVM tag)
8526   @type force_open: boolean
8527   @param force_open: this parameter will be passes to the
8528       L{backend.BlockdevCreate} function where it specifies
8529       whether we run on primary or not, and it affects both
8530       the child assembly and the device own Open() execution
8531
8532   """
8533   lu.cfg.SetDiskID(device, node)
8534   result = lu.rpc.call_blockdev_create(node, device, device.size,
8535                                        instance.name, force_open, info)
8536   result.Raise("Can't create block device %s on"
8537                " node %s for instance %s" % (device, node, instance.name))
8538   if device.physical_id is None:
8539     device.physical_id = result.payload
8540
8541
8542 def _GenerateUniqueNames(lu, exts):
8543   """Generate a suitable LV name.
8544
8545   This will generate a logical volume name for the given instance.
8546
8547   """
8548   results = []
8549   for val in exts:
8550     new_id = lu.cfg.GenerateUniqueID(lu.proc.GetECId())
8551     results.append("%s%s" % (new_id, val))
8552   return results
8553
8554
8555 def _ComputeLDParams(disk_template, disk_params):
8556   """Computes Logical Disk parameters from Disk Template parameters.
8557
8558   @type disk_template: string
8559   @param disk_template: disk template, one of L{constants.DISK_TEMPLATES}
8560   @type disk_params: dict
8561   @param disk_params: disk template parameters; dict(template_name -> parameters
8562   @rtype: list(dict)
8563   @return: a list of dicts, one for each node of the disk hierarchy. Each dict
8564     contains the LD parameters of the node. The tree is flattened in-order.
8565
8566   """
8567   if disk_template not in constants.DISK_TEMPLATES:
8568     raise errors.ProgrammerError("Unknown disk template %s" % disk_template)
8569
8570   result = list()
8571   dt_params = disk_params[disk_template]
8572   if disk_template == constants.DT_DRBD8:
8573     drbd_params = {
8574       constants.LDP_RESYNC_RATE: dt_params[constants.DRBD_RESYNC_RATE],
8575       constants.LDP_BARRIERS: dt_params[constants.DRBD_DISK_BARRIERS],
8576       constants.LDP_NO_META_FLUSH: dt_params[constants.DRBD_META_BARRIERS],
8577       constants.LDP_DEFAULT_METAVG: dt_params[constants.DRBD_DEFAULT_METAVG],
8578       constants.LDP_DISK_CUSTOM: dt_params[constants.DRBD_DISK_CUSTOM],
8579       constants.LDP_NET_CUSTOM: dt_params[constants.DRBD_NET_CUSTOM],
8580       constants.LDP_DYNAMIC_RESYNC: dt_params[constants.DRBD_DYNAMIC_RESYNC],
8581       constants.LDP_PLAN_AHEAD: dt_params[constants.DRBD_PLAN_AHEAD],
8582       constants.LDP_FILL_TARGET: dt_params[constants.DRBD_FILL_TARGET],
8583       constants.LDP_DELAY_TARGET: dt_params[constants.DRBD_DELAY_TARGET],
8584       constants.LDP_MAX_RATE: dt_params[constants.DRBD_MAX_RATE],
8585       constants.LDP_MIN_RATE: dt_params[constants.DRBD_MIN_RATE],
8586       }
8587
8588     drbd_params = \
8589       objects.FillDict(constants.DISK_LD_DEFAULTS[constants.LD_DRBD8],
8590                        drbd_params)
8591
8592     result.append(drbd_params)
8593
8594     # data LV
8595     data_params = {
8596       constants.LDP_STRIPES: dt_params[constants.DRBD_DATA_STRIPES],
8597       }
8598     data_params = \
8599       objects.FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV],
8600                        data_params)
8601     result.append(data_params)
8602
8603     # metadata LV
8604     meta_params = {
8605       constants.LDP_STRIPES: dt_params[constants.DRBD_META_STRIPES],
8606       }
8607     meta_params = \
8608       objects.FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV],
8609                        meta_params)
8610     result.append(meta_params)
8611
8612   elif (disk_template == constants.DT_FILE or
8613         disk_template == constants.DT_SHARED_FILE):
8614     result.append(constants.DISK_LD_DEFAULTS[constants.LD_FILE])
8615
8616   elif disk_template == constants.DT_PLAIN:
8617     params = {
8618       constants.LDP_STRIPES: dt_params[constants.LV_STRIPES],
8619       }
8620     params = \
8621       objects.FillDict(constants.DISK_LD_DEFAULTS[constants.LD_LV],
8622                        params)
8623     result.append(params)
8624
8625   elif disk_template == constants.DT_BLOCK:
8626     result.append(constants.DISK_LD_DEFAULTS[constants.LD_BLOCKDEV])
8627
8628   elif disk_template == constants.DT_RBD:
8629     params = {
8630       constants.LDP_POOL: dt_params[constants.RBD_POOL]
8631       }
8632     params = \
8633       objects.FillDict(constants.DISK_LD_DEFAULTS[constants.LD_RBD],
8634                        params)
8635     result.append(params)
8636
8637   return result
8638
8639
8640 def _GenerateDRBD8Branch(lu, primary, secondary, size, vgnames, names,
8641                          iv_name, p_minor, s_minor, drbd_params, data_params,
8642                          meta_params):
8643   """Generate a drbd8 device complete with its children.
8644
8645   """
8646   assert len(vgnames) == len(names) == 2
8647   port = lu.cfg.AllocatePort()
8648   shared_secret = lu.cfg.GenerateDRBDSecret(lu.proc.GetECId())
8649
8650   dev_data = objects.Disk(dev_type=constants.LD_LV, size=size,
8651                           logical_id=(vgnames[0], names[0]),
8652                           params=data_params)
8653   dev_meta = objects.Disk(dev_type=constants.LD_LV, size=DRBD_META_SIZE,
8654                           logical_id=(vgnames[1], names[1]),
8655                           params=meta_params)
8656   drbd_dev = objects.Disk(dev_type=constants.LD_DRBD8, size=size,
8657                           logical_id=(primary, secondary, port,
8658                                       p_minor, s_minor,
8659                                       shared_secret),
8660                           children=[dev_data, dev_meta],
8661                           iv_name=iv_name, params=drbd_params)
8662   return drbd_dev
8663
8664
8665 def _GenerateDiskTemplate(lu, template_name,
8666                           instance_name, primary_node,
8667                           secondary_nodes, disk_info,
8668                           file_storage_dir, file_driver,
8669                           base_index, feedback_fn, disk_params):
8670   """Generate the entire disk layout for a given template type.
8671
8672   """
8673   #TODO: compute space requirements
8674
8675   vgname = lu.cfg.GetVGName()
8676   disk_count = len(disk_info)
8677   disks = []
8678   ld_params = _ComputeLDParams(template_name, disk_params)
8679   if template_name == constants.DT_DISKLESS:
8680     pass
8681   elif template_name == constants.DT_PLAIN:
8682     if secondary_nodes:
8683       raise errors.ProgrammerError("Wrong template configuration")
8684
8685     names = _GenerateUniqueNames(lu, [".disk%d" % (base_index + i)
8686                                       for i in range(disk_count)])
8687     for idx, disk in enumerate(disk_info):
8688       disk_index = idx + base_index
8689       vg = disk.get(constants.IDISK_VG, vgname)
8690       feedback_fn("* disk %i, vg %s, name %s" % (idx, vg, names[idx]))
8691       disk_dev = objects.Disk(dev_type=constants.LD_LV,
8692                               size=disk[constants.IDISK_SIZE],
8693                               logical_id=(vg, names[idx]),
8694                               iv_name="disk/%d" % disk_index,
8695                               mode=disk[constants.IDISK_MODE],
8696                               params=ld_params[0])
8697       disks.append(disk_dev)
8698   elif template_name == constants.DT_DRBD8:
8699     drbd_params, data_params, meta_params = ld_params
8700     if len(secondary_nodes) != 1:
8701       raise errors.ProgrammerError("Wrong template configuration")
8702     remote_node = secondary_nodes[0]
8703     minors = lu.cfg.AllocateDRBDMinor(
8704       [primary_node, remote_node] * len(disk_info), instance_name)
8705
8706     names = []
8707     for lv_prefix in _GenerateUniqueNames(lu, [".disk%d" % (base_index + i)
8708                                                for i in range(disk_count)]):
8709       names.append(lv_prefix + "_data")
8710       names.append(lv_prefix + "_meta")
8711     for idx, disk in enumerate(disk_info):
8712       disk_index = idx + base_index
8713       drbd_default_metavg = drbd_params[constants.LDP_DEFAULT_METAVG]
8714       data_vg = disk.get(constants.IDISK_VG, vgname)
8715       meta_vg = disk.get(constants.IDISK_METAVG, drbd_default_metavg)
8716       disk_dev = _GenerateDRBD8Branch(lu, primary_node, remote_node,
8717                                       disk[constants.IDISK_SIZE],
8718                                       [data_vg, meta_vg],
8719                                       names[idx * 2:idx * 2 + 2],
8720                                       "disk/%d" % disk_index,
8721                                       minors[idx * 2], minors[idx * 2 + 1],
8722                                       drbd_params, data_params, meta_params)
8723       disk_dev.mode = disk[constants.IDISK_MODE]
8724       disks.append(disk_dev)
8725   elif template_name == constants.DT_FILE:
8726     if secondary_nodes:
8727       raise errors.ProgrammerError("Wrong template configuration")
8728
8729     opcodes.RequireFileStorage()
8730
8731     for idx, disk in enumerate(disk_info):
8732       disk_index = idx + base_index
8733       disk_dev = objects.Disk(dev_type=constants.LD_FILE,
8734                               size=disk[constants.IDISK_SIZE],
8735                               iv_name="disk/%d" % disk_index,
8736                               logical_id=(file_driver,
8737                                           "%s/disk%d" % (file_storage_dir,
8738                                                          disk_index)),
8739                               mode=disk[constants.IDISK_MODE],
8740                               params=ld_params[0])
8741       disks.append(disk_dev)
8742   elif template_name == constants.DT_SHARED_FILE:
8743     if secondary_nodes:
8744       raise errors.ProgrammerError("Wrong template configuration")
8745
8746     opcodes.RequireSharedFileStorage()
8747
8748     for idx, disk in enumerate(disk_info):
8749       disk_index = idx + base_index
8750       disk_dev = objects.Disk(dev_type=constants.LD_FILE,
8751                               size=disk[constants.IDISK_SIZE],
8752                               iv_name="disk/%d" % disk_index,
8753                               logical_id=(file_driver,
8754                                           "%s/disk%d" % (file_storage_dir,
8755                                                          disk_index)),
8756                               mode=disk[constants.IDISK_MODE],
8757                               params=ld_params[0])
8758       disks.append(disk_dev)
8759   elif template_name == constants.DT_BLOCK:
8760     if secondary_nodes:
8761       raise errors.ProgrammerError("Wrong template configuration")
8762
8763     for idx, disk in enumerate(disk_info):
8764       disk_index = idx + base_index
8765       disk_dev = objects.Disk(dev_type=constants.LD_BLOCKDEV,
8766                               size=disk[constants.IDISK_SIZE],
8767                               logical_id=(constants.BLOCKDEV_DRIVER_MANUAL,
8768                                           disk[constants.IDISK_ADOPT]),
8769                               iv_name="disk/%d" % disk_index,
8770                               mode=disk[constants.IDISK_MODE],
8771                               params=ld_params[0])
8772       disks.append(disk_dev)
8773   elif template_name == constants.DT_RBD:
8774     if secondary_nodes:
8775       raise errors.ProgrammerError("Wrong template configuration")
8776
8777     names = _GenerateUniqueNames(lu, [".rbd.disk%d" % (base_index + i)
8778                                       for i in range(disk_count)])
8779
8780     for idx, disk in enumerate(disk_info):
8781       disk_index = idx + base_index
8782       disk_dev = objects.Disk(dev_type=constants.LD_RBD,
8783                               size=disk[constants.IDISK_SIZE],
8784                               logical_id=("rbd", names[idx]),
8785                               iv_name="disk/%d" % disk_index,
8786                               mode=disk[constants.IDISK_MODE],
8787                               params=ld_params[0])
8788       disks.append(disk_dev)
8789
8790   else:
8791     raise errors.ProgrammerError("Invalid disk template '%s'" % template_name)
8792   return disks
8793
8794
8795 def _GetInstanceInfoText(instance):
8796   """Compute that text that should be added to the disk's metadata.
8797
8798   """
8799   return "originstname+%s" % instance.name
8800
8801
8802 def _CalcEta(time_taken, written, total_size):
8803   """Calculates the ETA based on size written and total size.
8804
8805   @param time_taken: The time taken so far
8806   @param written: amount written so far
8807   @param total_size: The total size of data to be written
8808   @return: The remaining time in seconds
8809
8810   """
8811   avg_time = time_taken / float(written)
8812   return (total_size - written) * avg_time
8813
8814
8815 def _WipeDisks(lu, instance):
8816   """Wipes instance disks.
8817
8818   @type lu: L{LogicalUnit}
8819   @param lu: the logical unit on whose behalf we execute
8820   @type instance: L{objects.Instance}
8821   @param instance: the instance whose disks we should create
8822   @return: the success of the wipe
8823
8824   """
8825   node = instance.primary_node
8826
8827   for device in instance.disks:
8828     lu.cfg.SetDiskID(device, node)
8829
8830   logging.info("Pause sync of instance %s disks", instance.name)
8831   result = lu.rpc.call_blockdev_pause_resume_sync(node, instance.disks, True)
8832
8833   for idx, success in enumerate(result.payload):
8834     if not success:
8835       logging.warn("pause-sync of instance %s for disks %d failed",
8836                    instance.name, idx)
8837
8838   try:
8839     for idx, device in enumerate(instance.disks):
8840       # The wipe size is MIN_WIPE_CHUNK_PERCENT % of the instance disk but
8841       # MAX_WIPE_CHUNK at max
8842       wipe_chunk_size = min(constants.MAX_WIPE_CHUNK, device.size / 100.0 *
8843                             constants.MIN_WIPE_CHUNK_PERCENT)
8844       # we _must_ make this an int, otherwise rounding errors will
8845       # occur
8846       wipe_chunk_size = int(wipe_chunk_size)
8847
8848       lu.LogInfo("* Wiping disk %d", idx)
8849       logging.info("Wiping disk %d for instance %s, node %s using"
8850                    " chunk size %s", idx, instance.name, node, wipe_chunk_size)
8851
8852       offset = 0
8853       size = device.size
8854       last_output = 0
8855       start_time = time.time()
8856
8857       while offset < size:
8858         wipe_size = min(wipe_chunk_size, size - offset)
8859         logging.debug("Wiping disk %d, offset %s, chunk %s",
8860                       idx, offset, wipe_size)
8861         result = lu.rpc.call_blockdev_wipe(node, device, offset, wipe_size)
8862         result.Raise("Could not wipe disk %d at offset %d for size %d" %
8863                      (idx, offset, wipe_size))
8864         now = time.time()
8865         offset += wipe_size
8866         if now - last_output >= 60:
8867           eta = _CalcEta(now - start_time, offset, size)
8868           lu.LogInfo(" - done: %.1f%% ETA: %s" %
8869                      (offset / float(size) * 100, utils.FormatSeconds(eta)))
8870           last_output = now
8871   finally:
8872     logging.info("Resume sync of instance %s disks", instance.name)
8873
8874     result = lu.rpc.call_blockdev_pause_resume_sync(node, instance.disks, False)
8875
8876     for idx, success in enumerate(result.payload):
8877       if not success:
8878         lu.LogWarning("Resume sync of disk %d failed, please have a"
8879                       " look at the status and troubleshoot the issue", idx)
8880         logging.warn("resume-sync of instance %s for disks %d failed",
8881                      instance.name, idx)
8882
8883
8884 def _CreateDisks(lu, instance, to_skip=None, target_node=None):
8885   """Create all disks for an instance.
8886
8887   This abstracts away some work from AddInstance.
8888
8889   @type lu: L{LogicalUnit}
8890   @param lu: the logical unit on whose behalf we execute
8891   @type instance: L{objects.Instance}
8892   @param instance: the instance whose disks we should create
8893   @type to_skip: list
8894   @param to_skip: list of indices to skip
8895   @type target_node: string
8896   @param target_node: if passed, overrides the target node for creation
8897   @rtype: boolean
8898   @return: the success of the creation
8899
8900   """
8901   info = _GetInstanceInfoText(instance)
8902   if target_node is None:
8903     pnode = instance.primary_node
8904     all_nodes = instance.all_nodes
8905   else:
8906     pnode = target_node
8907     all_nodes = [pnode]
8908
8909   if instance.disk_template in constants.DTS_FILEBASED:
8910     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
8911     result = lu.rpc.call_file_storage_dir_create(pnode, file_storage_dir)
8912
8913     result.Raise("Failed to create directory '%s' on"
8914                  " node %s" % (file_storage_dir, pnode))
8915
8916   # Note: this needs to be kept in sync with adding of disks in
8917   # LUInstanceSetParams
8918   for idx, device in enumerate(instance.disks):
8919     if to_skip and idx in to_skip:
8920       continue
8921     logging.info("Creating volume %s for instance %s",
8922                  device.iv_name, instance.name)
8923     #HARDCODE
8924     for node in all_nodes:
8925       f_create = node == pnode
8926       _CreateBlockDev(lu, node, instance, device, f_create, info, f_create)
8927
8928
8929 def _RemoveDisks(lu, instance, target_node=None):
8930   """Remove all disks for an instance.
8931
8932   This abstracts away some work from `AddInstance()` and
8933   `RemoveInstance()`. Note that in case some of the devices couldn't
8934   be removed, the removal will continue with the other ones (compare
8935   with `_CreateDisks()`).
8936
8937   @type lu: L{LogicalUnit}
8938   @param lu: the logical unit on whose behalf we execute
8939   @type instance: L{objects.Instance}
8940   @param instance: the instance whose disks we should remove
8941   @type target_node: string
8942   @param target_node: used to override the node on which to remove the disks
8943   @rtype: boolean
8944   @return: the success of the removal
8945
8946   """
8947   logging.info("Removing block devices for instance %s", instance.name)
8948
8949   all_result = True
8950   for device in instance.disks:
8951     if target_node:
8952       edata = [(target_node, device)]
8953     else:
8954       edata = device.ComputeNodeTree(instance.primary_node)
8955     for node, disk in edata:
8956       lu.cfg.SetDiskID(disk, node)
8957       msg = lu.rpc.call_blockdev_remove(node, disk).fail_msg
8958       if msg:
8959         lu.LogWarning("Could not remove block device %s on node %s,"
8960                       " continuing anyway: %s", device.iv_name, node, msg)
8961         all_result = False
8962
8963     # if this is a DRBD disk, return its port to the pool
8964     if device.dev_type in constants.LDS_DRBD:
8965       tcp_port = device.logical_id[2]
8966       lu.cfg.AddTcpUdpPort(tcp_port)
8967
8968   if instance.disk_template == constants.DT_FILE:
8969     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
8970     if target_node:
8971       tgt = target_node
8972     else:
8973       tgt = instance.primary_node
8974     result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
8975     if result.fail_msg:
8976       lu.LogWarning("Could not remove directory '%s' on node %s: %s",
8977                     file_storage_dir, instance.primary_node, result.fail_msg)
8978       all_result = False
8979
8980   return all_result
8981
8982
8983 def _ComputeDiskSizePerVG(disk_template, disks):
8984   """Compute disk size requirements in the volume group
8985
8986   """
8987   def _compute(disks, payload):
8988     """Universal algorithm.
8989
8990     """
8991     vgs = {}
8992     for disk in disks:
8993       vgs[disk[constants.IDISK_VG]] = \
8994         vgs.get(constants.IDISK_VG, 0) + disk[constants.IDISK_SIZE] + payload
8995
8996     return vgs
8997
8998   # Required free disk space as a function of disk and swap space
8999   req_size_dict = {
9000     constants.DT_DISKLESS: {},
9001     constants.DT_PLAIN: _compute(disks, 0),
9002     # 128 MB are added for drbd metadata for each disk
9003     constants.DT_DRBD8: _compute(disks, DRBD_META_SIZE),
9004     constants.DT_FILE: {},
9005     constants.DT_SHARED_FILE: {},
9006   }
9007
9008   if disk_template not in req_size_dict:
9009     raise errors.ProgrammerError("Disk template '%s' size requirement"
9010                                  " is unknown" % disk_template)
9011
9012   return req_size_dict[disk_template]
9013
9014
9015 def _ComputeDiskSize(disk_template, disks):
9016   """Compute disk size requirements in the volume group
9017
9018   """
9019   # Required free disk space as a function of disk and swap space
9020   req_size_dict = {
9021     constants.DT_DISKLESS: None,
9022     constants.DT_PLAIN: sum(d[constants.IDISK_SIZE] for d in disks),
9023     # 128 MB are added for drbd metadata for each disk
9024     constants.DT_DRBD8:
9025       sum(d[constants.IDISK_SIZE] + DRBD_META_SIZE for d in disks),
9026     constants.DT_FILE: None,
9027     constants.DT_SHARED_FILE: 0,
9028     constants.DT_BLOCK: 0,
9029     constants.DT_RBD: 0,
9030   }
9031
9032   if disk_template not in req_size_dict:
9033     raise errors.ProgrammerError("Disk template '%s' size requirement"
9034                                  " is unknown" % disk_template)
9035
9036   return req_size_dict[disk_template]
9037
9038
9039 def _FilterVmNodes(lu, nodenames):
9040   """Filters out non-vm_capable nodes from a list.
9041
9042   @type lu: L{LogicalUnit}
9043   @param lu: the logical unit for which we check
9044   @type nodenames: list
9045   @param nodenames: the list of nodes on which we should check
9046   @rtype: list
9047   @return: the list of vm-capable nodes
9048
9049   """
9050   vm_nodes = frozenset(lu.cfg.GetNonVmCapableNodeList())
9051   return [name for name in nodenames if name not in vm_nodes]
9052
9053
9054 def _CheckHVParams(lu, nodenames, hvname, hvparams):
9055   """Hypervisor parameter validation.
9056
9057   This function abstract the hypervisor parameter validation to be
9058   used in both instance create and instance modify.
9059
9060   @type lu: L{LogicalUnit}
9061   @param lu: the logical unit for which we check
9062   @type nodenames: list
9063   @param nodenames: the list of nodes on which we should check
9064   @type hvname: string
9065   @param hvname: the name of the hypervisor we should use
9066   @type hvparams: dict
9067   @param hvparams: the parameters which we need to check
9068   @raise errors.OpPrereqError: if the parameters are not valid
9069
9070   """
9071   nodenames = _FilterVmNodes(lu, nodenames)
9072
9073   cluster = lu.cfg.GetClusterInfo()
9074   hvfull = objects.FillDict(cluster.hvparams.get(hvname, {}), hvparams)
9075
9076   hvinfo = lu.rpc.call_hypervisor_validate_params(nodenames, hvname, hvfull)
9077   for node in nodenames:
9078     info = hvinfo[node]
9079     if info.offline:
9080       continue
9081     info.Raise("Hypervisor parameter validation failed on node %s" % node)
9082
9083
9084 def _CheckOSParams(lu, required, nodenames, osname, osparams):
9085   """OS parameters validation.
9086
9087   @type lu: L{LogicalUnit}
9088   @param lu: the logical unit for which we check
9089   @type required: boolean
9090   @param required: whether the validation should fail if the OS is not
9091       found
9092   @type nodenames: list
9093   @param nodenames: the list of nodes on which we should check
9094   @type osname: string
9095   @param osname: the name of the hypervisor we should use
9096   @type osparams: dict
9097   @param osparams: the parameters which we need to check
9098   @raise errors.OpPrereqError: if the parameters are not valid
9099
9100   """
9101   nodenames = _FilterVmNodes(lu, nodenames)
9102   result = lu.rpc.call_os_validate(nodenames, required, osname,
9103                                    [constants.OS_VALIDATE_PARAMETERS],
9104                                    osparams)
9105   for node, nres in result.items():
9106     # we don't check for offline cases since this should be run only
9107     # against the master node and/or an instance's nodes
9108     nres.Raise("OS Parameters validation failed on node %s" % node)
9109     if not nres.payload:
9110       lu.LogInfo("OS %s not found on node %s, validation skipped",
9111                  osname, node)
9112
9113
9114 class LUInstanceCreate(LogicalUnit):
9115   """Create an instance.
9116
9117   """
9118   HPATH = "instance-add"
9119   HTYPE = constants.HTYPE_INSTANCE
9120   REQ_BGL = False
9121
9122   def CheckArguments(self):
9123     """Check arguments.
9124
9125     """
9126     # do not require name_check to ease forward/backward compatibility
9127     # for tools
9128     if self.op.no_install and self.op.start:
9129       self.LogInfo("No-installation mode selected, disabling startup")
9130       self.op.start = False
9131     # validate/normalize the instance name
9132     self.op.instance_name = \
9133       netutils.Hostname.GetNormalizedName(self.op.instance_name)
9134
9135     if self.op.ip_check and not self.op.name_check:
9136       # TODO: make the ip check more flexible and not depend on the name check
9137       raise errors.OpPrereqError("Cannot do IP address check without a name"
9138                                  " check", errors.ECODE_INVAL)
9139
9140     # check nics' parameter names
9141     for nic in self.op.nics:
9142       utils.ForceDictType(nic, constants.INIC_PARAMS_TYPES)
9143
9144     # check disks. parameter names and consistent adopt/no-adopt strategy
9145     has_adopt = has_no_adopt = False
9146     for disk in self.op.disks:
9147       utils.ForceDictType(disk, constants.IDISK_PARAMS_TYPES)
9148       if constants.IDISK_ADOPT in disk:
9149         has_adopt = True
9150       else:
9151         has_no_adopt = True
9152     if has_adopt and has_no_adopt:
9153       raise errors.OpPrereqError("Either all disks are adopted or none is",
9154                                  errors.ECODE_INVAL)
9155     if has_adopt:
9156       if self.op.disk_template not in constants.DTS_MAY_ADOPT:
9157         raise errors.OpPrereqError("Disk adoption is not supported for the"
9158                                    " '%s' disk template" %
9159                                    self.op.disk_template,
9160                                    errors.ECODE_INVAL)
9161       if self.op.iallocator is not None:
9162         raise errors.OpPrereqError("Disk adoption not allowed with an"
9163                                    " iallocator script", errors.ECODE_INVAL)
9164       if self.op.mode == constants.INSTANCE_IMPORT:
9165         raise errors.OpPrereqError("Disk adoption not allowed for"
9166                                    " instance import", errors.ECODE_INVAL)
9167     else:
9168       if self.op.disk_template in constants.DTS_MUST_ADOPT:
9169         raise errors.OpPrereqError("Disk template %s requires disk adoption,"
9170                                    " but no 'adopt' parameter given" %
9171                                    self.op.disk_template,
9172                                    errors.ECODE_INVAL)
9173
9174     self.adopt_disks = has_adopt
9175
9176     # instance name verification
9177     if self.op.name_check:
9178       self.hostname1 = netutils.GetHostname(name=self.op.instance_name)
9179       self.op.instance_name = self.hostname1.name
9180       # used in CheckPrereq for ip ping check
9181       self.check_ip = self.hostname1.ip
9182     else:
9183       self.check_ip = None
9184
9185     # file storage checks
9186     if (self.op.file_driver and
9187         not self.op.file_driver in constants.FILE_DRIVER):
9188       raise errors.OpPrereqError("Invalid file driver name '%s'" %
9189                                  self.op.file_driver, errors.ECODE_INVAL)
9190
9191     if self.op.disk_template == constants.DT_FILE:
9192       opcodes.RequireFileStorage()
9193     elif self.op.disk_template == constants.DT_SHARED_FILE:
9194       opcodes.RequireSharedFileStorage()
9195
9196     ### Node/iallocator related checks
9197     _CheckIAllocatorOrNode(self, "iallocator", "pnode")
9198
9199     if self.op.pnode is not None:
9200       if self.op.disk_template in constants.DTS_INT_MIRROR:
9201         if self.op.snode is None:
9202           raise errors.OpPrereqError("The networked disk templates need"
9203                                      " a mirror node", errors.ECODE_INVAL)
9204       elif self.op.snode:
9205         self.LogWarning("Secondary node will be ignored on non-mirrored disk"
9206                         " template")
9207         self.op.snode = None
9208
9209     self._cds = _GetClusterDomainSecret()
9210
9211     if self.op.mode == constants.INSTANCE_IMPORT:
9212       # On import force_variant must be True, because if we forced it at
9213       # initial install, our only chance when importing it back is that it
9214       # works again!
9215       self.op.force_variant = True
9216
9217       if self.op.no_install:
9218         self.LogInfo("No-installation mode has no effect during import")
9219
9220     elif self.op.mode == constants.INSTANCE_CREATE:
9221       if self.op.os_type is None:
9222         raise errors.OpPrereqError("No guest OS specified",
9223                                    errors.ECODE_INVAL)
9224       if self.op.os_type in self.cfg.GetClusterInfo().blacklisted_os:
9225         raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
9226                                    " installation" % self.op.os_type,
9227                                    errors.ECODE_STATE)
9228       if self.op.disk_template is None:
9229         raise errors.OpPrereqError("No disk template specified",
9230                                    errors.ECODE_INVAL)
9231
9232     elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
9233       # Check handshake to ensure both clusters have the same domain secret
9234       src_handshake = self.op.source_handshake
9235       if not src_handshake:
9236         raise errors.OpPrereqError("Missing source handshake",
9237                                    errors.ECODE_INVAL)
9238
9239       errmsg = masterd.instance.CheckRemoteExportHandshake(self._cds,
9240                                                            src_handshake)
9241       if errmsg:
9242         raise errors.OpPrereqError("Invalid handshake: %s" % errmsg,
9243                                    errors.ECODE_INVAL)
9244
9245       # Load and check source CA
9246       self.source_x509_ca_pem = self.op.source_x509_ca
9247       if not self.source_x509_ca_pem:
9248         raise errors.OpPrereqError("Missing source X509 CA",
9249                                    errors.ECODE_INVAL)
9250
9251       try:
9252         (cert, _) = utils.LoadSignedX509Certificate(self.source_x509_ca_pem,
9253                                                     self._cds)
9254       except OpenSSL.crypto.Error, err:
9255         raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
9256                                    (err, ), errors.ECODE_INVAL)
9257
9258       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
9259       if errcode is not None:
9260         raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
9261                                    errors.ECODE_INVAL)
9262
9263       self.source_x509_ca = cert
9264
9265       src_instance_name = self.op.source_instance_name
9266       if not src_instance_name:
9267         raise errors.OpPrereqError("Missing source instance name",
9268                                    errors.ECODE_INVAL)
9269
9270       self.source_instance_name = \
9271           netutils.GetHostname(name=src_instance_name).name
9272
9273     else:
9274       raise errors.OpPrereqError("Invalid instance creation mode %r" %
9275                                  self.op.mode, errors.ECODE_INVAL)
9276
9277   def ExpandNames(self):
9278     """ExpandNames for CreateInstance.
9279
9280     Figure out the right locks for instance creation.
9281
9282     """
9283     self.needed_locks = {}
9284
9285     instance_name = self.op.instance_name
9286     # this is just a preventive check, but someone might still add this
9287     # instance in the meantime, and creation will fail at lock-add time
9288     if instance_name in self.cfg.GetInstanceList():
9289       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
9290                                  instance_name, errors.ECODE_EXISTS)
9291
9292     self.add_locks[locking.LEVEL_INSTANCE] = instance_name
9293
9294     if self.op.iallocator:
9295       # TODO: Find a solution to not lock all nodes in the cluster, e.g. by
9296       # specifying a group on instance creation and then selecting nodes from
9297       # that group
9298       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9299       self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
9300     else:
9301       self.op.pnode = _ExpandNodeName(self.cfg, self.op.pnode)
9302       nodelist = [self.op.pnode]
9303       if self.op.snode is not None:
9304         self.op.snode = _ExpandNodeName(self.cfg, self.op.snode)
9305         nodelist.append(self.op.snode)
9306       self.needed_locks[locking.LEVEL_NODE] = nodelist
9307       # Lock resources of instance's primary and secondary nodes (copy to
9308       # prevent accidential modification)
9309       self.needed_locks[locking.LEVEL_NODE_RES] = list(nodelist)
9310
9311     # in case of import lock the source node too
9312     if self.op.mode == constants.INSTANCE_IMPORT:
9313       src_node = self.op.src_node
9314       src_path = self.op.src_path
9315
9316       if src_path is None:
9317         self.op.src_path = src_path = self.op.instance_name
9318
9319       if src_node is None:
9320         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9321         self.op.src_node = None
9322         if os.path.isabs(src_path):
9323           raise errors.OpPrereqError("Importing an instance from a path"
9324                                      " requires a source node option",
9325                                      errors.ECODE_INVAL)
9326       else:
9327         self.op.src_node = src_node = _ExpandNodeName(self.cfg, src_node)
9328         if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
9329           self.needed_locks[locking.LEVEL_NODE].append(src_node)
9330         if not os.path.isabs(src_path):
9331           self.op.src_path = src_path = \
9332             utils.PathJoin(constants.EXPORT_DIR, src_path)
9333
9334   def _RunAllocator(self):
9335     """Run the allocator based on input opcode.
9336
9337     """
9338     nics = [n.ToDict() for n in self.nics]
9339     ial = IAllocator(self.cfg, self.rpc,
9340                      mode=constants.IALLOCATOR_MODE_ALLOC,
9341                      name=self.op.instance_name,
9342                      disk_template=self.op.disk_template,
9343                      tags=self.op.tags,
9344                      os=self.op.os_type,
9345                      vcpus=self.be_full[constants.BE_VCPUS],
9346                      memory=self.be_full[constants.BE_MAXMEM],
9347                      disks=self.disks,
9348                      nics=nics,
9349                      hypervisor=self.op.hypervisor,
9350                      )
9351
9352     ial.Run(self.op.iallocator)
9353
9354     if not ial.success:
9355       raise errors.OpPrereqError("Can't compute nodes using"
9356                                  " iallocator '%s': %s" %
9357                                  (self.op.iallocator, ial.info),
9358                                  errors.ECODE_NORES)
9359     if len(ial.result) != ial.required_nodes:
9360       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
9361                                  " of nodes (%s), required %s" %
9362                                  (self.op.iallocator, len(ial.result),
9363                                   ial.required_nodes), errors.ECODE_FAULT)
9364     self.op.pnode = ial.result[0]
9365     self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
9366                  self.op.instance_name, self.op.iallocator,
9367                  utils.CommaJoin(ial.result))
9368     if ial.required_nodes == 2:
9369       self.op.snode = ial.result[1]
9370
9371   def BuildHooksEnv(self):
9372     """Build hooks env.
9373
9374     This runs on master, primary and secondary nodes of the instance.
9375
9376     """
9377     env = {
9378       "ADD_MODE": self.op.mode,
9379       }
9380     if self.op.mode == constants.INSTANCE_IMPORT:
9381       env["SRC_NODE"] = self.op.src_node
9382       env["SRC_PATH"] = self.op.src_path
9383       env["SRC_IMAGES"] = self.src_images
9384
9385     env.update(_BuildInstanceHookEnv(
9386       name=self.op.instance_name,
9387       primary_node=self.op.pnode,
9388       secondary_nodes=self.secondaries,
9389       status=self.op.start,
9390       os_type=self.op.os_type,
9391       minmem=self.be_full[constants.BE_MINMEM],
9392       maxmem=self.be_full[constants.BE_MAXMEM],
9393       vcpus=self.be_full[constants.BE_VCPUS],
9394       nics=_NICListToTuple(self, self.nics),
9395       disk_template=self.op.disk_template,
9396       disks=[(d[constants.IDISK_SIZE], d[constants.IDISK_MODE])
9397              for d in self.disks],
9398       bep=self.be_full,
9399       hvp=self.hv_full,
9400       hypervisor_name=self.op.hypervisor,
9401       tags=self.op.tags,
9402     ))
9403
9404     return env
9405
9406   def BuildHooksNodes(self):
9407     """Build hooks nodes.
9408
9409     """
9410     nl = [self.cfg.GetMasterNode(), self.op.pnode] + self.secondaries
9411     return nl, nl
9412
9413   def _ReadExportInfo(self):
9414     """Reads the export information from disk.
9415
9416     It will override the opcode source node and path with the actual
9417     information, if these two were not specified before.
9418
9419     @return: the export information
9420
9421     """
9422     assert self.op.mode == constants.INSTANCE_IMPORT
9423
9424     src_node = self.op.src_node
9425     src_path = self.op.src_path
9426
9427     if src_node is None:
9428       locked_nodes = self.owned_locks(locking.LEVEL_NODE)
9429       exp_list = self.rpc.call_export_list(locked_nodes)
9430       found = False
9431       for node in exp_list:
9432         if exp_list[node].fail_msg:
9433           continue
9434         if src_path in exp_list[node].payload:
9435           found = True
9436           self.op.src_node = src_node = node
9437           self.op.src_path = src_path = utils.PathJoin(constants.EXPORT_DIR,
9438                                                        src_path)
9439           break
9440       if not found:
9441         raise errors.OpPrereqError("No export found for relative path %s" %
9442                                     src_path, errors.ECODE_INVAL)
9443
9444     _CheckNodeOnline(self, src_node)
9445     result = self.rpc.call_export_info(src_node, src_path)
9446     result.Raise("No export or invalid export found in dir %s" % src_path)
9447
9448     export_info = objects.SerializableConfigParser.Loads(str(result.payload))
9449     if not export_info.has_section(constants.INISECT_EXP):
9450       raise errors.ProgrammerError("Corrupted export config",
9451                                    errors.ECODE_ENVIRON)
9452
9453     ei_version = export_info.get(constants.INISECT_EXP, "version")
9454     if (int(ei_version) != constants.EXPORT_VERSION):
9455       raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
9456                                  (ei_version, constants.EXPORT_VERSION),
9457                                  errors.ECODE_ENVIRON)
9458     return export_info
9459
9460   def _ReadExportParams(self, einfo):
9461     """Use export parameters as defaults.
9462
9463     In case the opcode doesn't specify (as in override) some instance
9464     parameters, then try to use them from the export information, if
9465     that declares them.
9466
9467     """
9468     self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
9469
9470     if self.op.disk_template is None:
9471       if einfo.has_option(constants.INISECT_INS, "disk_template"):
9472         self.op.disk_template = einfo.get(constants.INISECT_INS,
9473                                           "disk_template")
9474         if self.op.disk_template not in constants.DISK_TEMPLATES:
9475           raise errors.OpPrereqError("Disk template specified in configuration"
9476                                      " file is not one of the allowed values:"
9477                                      " %s" % " ".join(constants.DISK_TEMPLATES))
9478       else:
9479         raise errors.OpPrereqError("No disk template specified and the export"
9480                                    " is missing the disk_template information",
9481                                    errors.ECODE_INVAL)
9482
9483     if not self.op.disks:
9484       disks = []
9485       # TODO: import the disk iv_name too
9486       for idx in range(constants.MAX_DISKS):
9487         if einfo.has_option(constants.INISECT_INS, "disk%d_size" % idx):
9488           disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
9489           disks.append({constants.IDISK_SIZE: disk_sz})
9490       self.op.disks = disks
9491       if not disks and self.op.disk_template != constants.DT_DISKLESS:
9492         raise errors.OpPrereqError("No disk info specified and the export"
9493                                    " is missing the disk information",
9494                                    errors.ECODE_INVAL)
9495
9496     if not self.op.nics:
9497       nics = []
9498       for idx in range(constants.MAX_NICS):
9499         if einfo.has_option(constants.INISECT_INS, "nic%d_mac" % idx):
9500           ndict = {}
9501           for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
9502             v = einfo.get(constants.INISECT_INS, "nic%d_%s" % (idx, name))
9503             ndict[name] = v
9504           nics.append(ndict)
9505         else:
9506           break
9507       self.op.nics = nics
9508
9509     if not self.op.tags and einfo.has_option(constants.INISECT_INS, "tags"):
9510       self.op.tags = einfo.get(constants.INISECT_INS, "tags").split()
9511
9512     if (self.op.hypervisor is None and
9513         einfo.has_option(constants.INISECT_INS, "hypervisor")):
9514       self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
9515
9516     if einfo.has_section(constants.INISECT_HYP):
9517       # use the export parameters but do not override the ones
9518       # specified by the user
9519       for name, value in einfo.items(constants.INISECT_HYP):
9520         if name not in self.op.hvparams:
9521           self.op.hvparams[name] = value
9522
9523     if einfo.has_section(constants.INISECT_BEP):
9524       # use the parameters, without overriding
9525       for name, value in einfo.items(constants.INISECT_BEP):
9526         if name not in self.op.beparams:
9527           self.op.beparams[name] = value
9528         # Compatibility for the old "memory" be param
9529         if name == constants.BE_MEMORY:
9530           if constants.BE_MAXMEM not in self.op.beparams:
9531             self.op.beparams[constants.BE_MAXMEM] = value
9532           if constants.BE_MINMEM not in self.op.beparams:
9533             self.op.beparams[constants.BE_MINMEM] = value
9534     else:
9535       # try to read the parameters old style, from the main section
9536       for name in constants.BES_PARAMETERS:
9537         if (name not in self.op.beparams and
9538             einfo.has_option(constants.INISECT_INS, name)):
9539           self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
9540
9541     if einfo.has_section(constants.INISECT_OSP):
9542       # use the parameters, without overriding
9543       for name, value in einfo.items(constants.INISECT_OSP):
9544         if name not in self.op.osparams:
9545           self.op.osparams[name] = value
9546
9547   def _RevertToDefaults(self, cluster):
9548     """Revert the instance parameters to the default values.
9549
9550     """
9551     # hvparams
9552     hv_defs = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type, {})
9553     for name in self.op.hvparams.keys():
9554       if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
9555         del self.op.hvparams[name]
9556     # beparams
9557     be_defs = cluster.SimpleFillBE({})
9558     for name in self.op.beparams.keys():
9559       if name in be_defs and be_defs[name] == self.op.beparams[name]:
9560         del self.op.beparams[name]
9561     # nic params
9562     nic_defs = cluster.SimpleFillNIC({})
9563     for nic in self.op.nics:
9564       for name in constants.NICS_PARAMETERS:
9565         if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
9566           del nic[name]
9567     # osparams
9568     os_defs = cluster.SimpleFillOS(self.op.os_type, {})
9569     for name in self.op.osparams.keys():
9570       if name in os_defs and os_defs[name] == self.op.osparams[name]:
9571         del self.op.osparams[name]
9572
9573   def _CalculateFileStorageDir(self):
9574     """Calculate final instance file storage dir.
9575
9576     """
9577     # file storage dir calculation/check
9578     self.instance_file_storage_dir = None
9579     if self.op.disk_template in constants.DTS_FILEBASED:
9580       # build the full file storage dir path
9581       joinargs = []
9582
9583       if self.op.disk_template == constants.DT_SHARED_FILE:
9584         get_fsd_fn = self.cfg.GetSharedFileStorageDir
9585       else:
9586         get_fsd_fn = self.cfg.GetFileStorageDir
9587
9588       cfg_storagedir = get_fsd_fn()
9589       if not cfg_storagedir:
9590         raise errors.OpPrereqError("Cluster file storage dir not defined")
9591       joinargs.append(cfg_storagedir)
9592
9593       if self.op.file_storage_dir is not None:
9594         joinargs.append(self.op.file_storage_dir)
9595
9596       joinargs.append(self.op.instance_name)
9597
9598       # pylint: disable=W0142
9599       self.instance_file_storage_dir = utils.PathJoin(*joinargs)
9600
9601   def CheckPrereq(self): # pylint: disable=R0914
9602     """Check prerequisites.
9603
9604     """
9605     self._CalculateFileStorageDir()
9606
9607     if self.op.mode == constants.INSTANCE_IMPORT:
9608       export_info = self._ReadExportInfo()
9609       self._ReadExportParams(export_info)
9610
9611     if (not self.cfg.GetVGName() and
9612         self.op.disk_template not in constants.DTS_NOT_LVM):
9613       raise errors.OpPrereqError("Cluster does not support lvm-based"
9614                                  " instances", errors.ECODE_STATE)
9615
9616     if (self.op.hypervisor is None or
9617         self.op.hypervisor == constants.VALUE_AUTO):
9618       self.op.hypervisor = self.cfg.GetHypervisorType()
9619
9620     cluster = self.cfg.GetClusterInfo()
9621     enabled_hvs = cluster.enabled_hypervisors
9622     if self.op.hypervisor not in enabled_hvs:
9623       raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
9624                                  " cluster (%s)" % (self.op.hypervisor,
9625                                   ",".join(enabled_hvs)),
9626                                  errors.ECODE_STATE)
9627
9628     # Check tag validity
9629     for tag in self.op.tags:
9630       objects.TaggableObject.ValidateTag(tag)
9631
9632     # check hypervisor parameter syntax (locally)
9633     utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
9634     filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type,
9635                                       self.op.hvparams)
9636     hv_type = hypervisor.GetHypervisor(self.op.hypervisor)
9637     hv_type.CheckParameterSyntax(filled_hvp)
9638     self.hv_full = filled_hvp
9639     # check that we don't specify global parameters on an instance
9640     _CheckGlobalHvParams(self.op.hvparams)
9641
9642     # fill and remember the beparams dict
9643     default_beparams = cluster.beparams[constants.PP_DEFAULT]
9644     for param, value in self.op.beparams.iteritems():
9645       if value == constants.VALUE_AUTO:
9646         self.op.beparams[param] = default_beparams[param]
9647     objects.UpgradeBeParams(self.op.beparams)
9648     utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
9649     self.be_full = cluster.SimpleFillBE(self.op.beparams)
9650
9651     # build os parameters
9652     self.os_full = cluster.SimpleFillOS(self.op.os_type, self.op.osparams)
9653
9654     # now that hvp/bep are in final format, let's reset to defaults,
9655     # if told to do so
9656     if self.op.identify_defaults:
9657       self._RevertToDefaults(cluster)
9658
9659     # NIC buildup
9660     self.nics = []
9661     for idx, nic in enumerate(self.op.nics):
9662       nic_mode_req = nic.get(constants.INIC_MODE, None)
9663       nic_mode = nic_mode_req
9664       if nic_mode is None or nic_mode == constants.VALUE_AUTO:
9665         nic_mode = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_MODE]
9666
9667       # in routed mode, for the first nic, the default ip is 'auto'
9668       if nic_mode == constants.NIC_MODE_ROUTED and idx == 0:
9669         default_ip_mode = constants.VALUE_AUTO
9670       else:
9671         default_ip_mode = constants.VALUE_NONE
9672
9673       # ip validity checks
9674       ip = nic.get(constants.INIC_IP, default_ip_mode)
9675       if ip is None or ip.lower() == constants.VALUE_NONE:
9676         nic_ip = None
9677       elif ip.lower() == constants.VALUE_AUTO:
9678         if not self.op.name_check:
9679           raise errors.OpPrereqError("IP address set to auto but name checks"
9680                                      " have been skipped",
9681                                      errors.ECODE_INVAL)
9682         nic_ip = self.hostname1.ip
9683       else:
9684         if not netutils.IPAddress.IsValid(ip):
9685           raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
9686                                      errors.ECODE_INVAL)
9687         nic_ip = ip
9688
9689       # TODO: check the ip address for uniqueness
9690       if nic_mode == constants.NIC_MODE_ROUTED and not nic_ip:
9691         raise errors.OpPrereqError("Routed nic mode requires an ip address",
9692                                    errors.ECODE_INVAL)
9693
9694       # MAC address verification
9695       mac = nic.get(constants.INIC_MAC, constants.VALUE_AUTO)
9696       if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
9697         mac = utils.NormalizeAndValidateMac(mac)
9698
9699         try:
9700           self.cfg.ReserveMAC(mac, self.proc.GetECId())
9701         except errors.ReservationError:
9702           raise errors.OpPrereqError("MAC address %s already in use"
9703                                      " in cluster" % mac,
9704                                      errors.ECODE_NOTUNIQUE)
9705
9706       #  Build nic parameters
9707       link = nic.get(constants.INIC_LINK, None)
9708       if link == constants.VALUE_AUTO:
9709         link = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_LINK]
9710       nicparams = {}
9711       if nic_mode_req:
9712         nicparams[constants.NIC_MODE] = nic_mode
9713       if link:
9714         nicparams[constants.NIC_LINK] = link
9715
9716       check_params = cluster.SimpleFillNIC(nicparams)
9717       objects.NIC.CheckParameterSyntax(check_params)
9718       self.nics.append(objects.NIC(mac=mac, ip=nic_ip, nicparams=nicparams))
9719
9720     # disk checks/pre-build
9721     default_vg = self.cfg.GetVGName()
9722     self.disks = []
9723     for disk in self.op.disks:
9724       mode = disk.get(constants.IDISK_MODE, constants.DISK_RDWR)
9725       if mode not in constants.DISK_ACCESS_SET:
9726         raise errors.OpPrereqError("Invalid disk access mode '%s'" %
9727                                    mode, errors.ECODE_INVAL)
9728       size = disk.get(constants.IDISK_SIZE, None)
9729       if size is None:
9730         raise errors.OpPrereqError("Missing disk size", errors.ECODE_INVAL)
9731       try:
9732         size = int(size)
9733       except (TypeError, ValueError):
9734         raise errors.OpPrereqError("Invalid disk size '%s'" % size,
9735                                    errors.ECODE_INVAL)
9736
9737       data_vg = disk.get(constants.IDISK_VG, default_vg)
9738       new_disk = {
9739         constants.IDISK_SIZE: size,
9740         constants.IDISK_MODE: mode,
9741         constants.IDISK_VG: data_vg,
9742         }
9743       if constants.IDISK_METAVG in disk:
9744         new_disk[constants.IDISK_METAVG] = disk[constants.IDISK_METAVG]
9745       if constants.IDISK_ADOPT in disk:
9746         new_disk[constants.IDISK_ADOPT] = disk[constants.IDISK_ADOPT]
9747       self.disks.append(new_disk)
9748
9749     if self.op.mode == constants.INSTANCE_IMPORT:
9750       disk_images = []
9751       for idx in range(len(self.disks)):
9752         option = "disk%d_dump" % idx
9753         if export_info.has_option(constants.INISECT_INS, option):
9754           # FIXME: are the old os-es, disk sizes, etc. useful?
9755           export_name = export_info.get(constants.INISECT_INS, option)
9756           image = utils.PathJoin(self.op.src_path, export_name)
9757           disk_images.append(image)
9758         else:
9759           disk_images.append(False)
9760
9761       self.src_images = disk_images
9762
9763       old_name = export_info.get(constants.INISECT_INS, "name")
9764       if self.op.instance_name == old_name:
9765         for idx, nic in enumerate(self.nics):
9766           if nic.mac == constants.VALUE_AUTO:
9767             nic_mac_ini = "nic%d_mac" % idx
9768             nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
9769
9770     # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
9771
9772     # ip ping checks (we use the same ip that was resolved in ExpandNames)
9773     if self.op.ip_check:
9774       if netutils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
9775         raise errors.OpPrereqError("IP %s of instance %s already in use" %
9776                                    (self.check_ip, self.op.instance_name),
9777                                    errors.ECODE_NOTUNIQUE)
9778
9779     #### mac address generation
9780     # By generating here the mac address both the allocator and the hooks get
9781     # the real final mac address rather than the 'auto' or 'generate' value.
9782     # There is a race condition between the generation and the instance object
9783     # creation, which means that we know the mac is valid now, but we're not
9784     # sure it will be when we actually add the instance. If things go bad
9785     # adding the instance will abort because of a duplicate mac, and the
9786     # creation job will fail.
9787     for nic in self.nics:
9788       if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
9789         nic.mac = self.cfg.GenerateMAC(self.proc.GetECId())
9790
9791     #### allocator run
9792
9793     if self.op.iallocator is not None:
9794       self._RunAllocator()
9795
9796     # Release all unneeded node locks
9797     _ReleaseLocks(self, locking.LEVEL_NODE,
9798                   keep=filter(None, [self.op.pnode, self.op.snode,
9799                                      self.op.src_node]))
9800     _ReleaseLocks(self, locking.LEVEL_NODE_RES,
9801                   keep=filter(None, [self.op.pnode, self.op.snode,
9802                                      self.op.src_node]))
9803
9804     #### node related checks
9805
9806     # check primary node
9807     self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode)
9808     assert self.pnode is not None, \
9809       "Cannot retrieve locked node %s" % self.op.pnode
9810     if pnode.offline:
9811       raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
9812                                  pnode.name, errors.ECODE_STATE)
9813     if pnode.drained:
9814       raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
9815                                  pnode.name, errors.ECODE_STATE)
9816     if not pnode.vm_capable:
9817       raise errors.OpPrereqError("Cannot use non-vm_capable primary node"
9818                                  " '%s'" % pnode.name, errors.ECODE_STATE)
9819
9820     self.secondaries = []
9821
9822     # mirror node verification
9823     if self.op.disk_template in constants.DTS_INT_MIRROR:
9824       if self.op.snode == pnode.name:
9825         raise errors.OpPrereqError("The secondary node cannot be the"
9826                                    " primary node", errors.ECODE_INVAL)
9827       _CheckNodeOnline(self, self.op.snode)
9828       _CheckNodeNotDrained(self, self.op.snode)
9829       _CheckNodeVmCapable(self, self.op.snode)
9830       self.secondaries.append(self.op.snode)
9831
9832       snode = self.cfg.GetNodeInfo(self.op.snode)
9833       if pnode.group != snode.group:
9834         self.LogWarning("The primary and secondary nodes are in two"
9835                         " different node groups; the disk parameters"
9836                         " from the first disk's node group will be"
9837                         " used")
9838
9839     nodenames = [pnode.name] + self.secondaries
9840
9841     # Verify instance specs
9842     ispec = {
9843       constants.ISPEC_MEM_SIZE: self.be_full.get(constants.BE_MAXMEM, None),
9844       constants.ISPEC_CPU_COUNT: self.be_full.get(constants.BE_VCPUS, None),
9845       constants.ISPEC_DISK_COUNT: len(self.disks),
9846       constants.ISPEC_DISK_SIZE: [disk["size"] for disk in self.disks],
9847       constants.ISPEC_NIC_COUNT: len(self.nics),
9848       }
9849
9850     group_info = self.cfg.GetNodeGroup(pnode.group)
9851     ipolicy = _CalculateGroupIPolicy(cluster, group_info)
9852     res = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec)
9853     if not self.op.ignore_ipolicy and res:
9854       raise errors.OpPrereqError(("Instance allocation to group %s violates"
9855                                   " policy: %s") % (pnode.group,
9856                                                     utils.CommaJoin(res)),
9857                                   errors.ECODE_INVAL)
9858
9859     # disk parameters (not customizable at instance or node level)
9860     # just use the primary node parameters, ignoring the secondary.
9861     self.diskparams = group_info.diskparams
9862
9863     if not self.adopt_disks:
9864       if self.op.disk_template == constants.DT_RBD:
9865         # _CheckRADOSFreeSpace() is just a placeholder.
9866         # Any function that checks prerequisites can be placed here.
9867         # Check if there is enough space on the RADOS cluster.
9868         _CheckRADOSFreeSpace()
9869       else:
9870         # Check lv size requirements, if not adopting
9871         req_sizes = _ComputeDiskSizePerVG(self.op.disk_template, self.disks)
9872         _CheckNodesFreeDiskPerVG(self, nodenames, req_sizes)
9873
9874     elif self.op.disk_template == constants.DT_PLAIN: # Check the adoption data
9875       all_lvs = set(["%s/%s" % (disk[constants.IDISK_VG],
9876                                 disk[constants.IDISK_ADOPT])
9877                      for disk in self.disks])
9878       if len(all_lvs) != len(self.disks):
9879         raise errors.OpPrereqError("Duplicate volume names given for adoption",
9880                                    errors.ECODE_INVAL)
9881       for lv_name in all_lvs:
9882         try:
9883           # FIXME: lv_name here is "vg/lv" need to ensure that other calls
9884           # to ReserveLV uses the same syntax
9885           self.cfg.ReserveLV(lv_name, self.proc.GetECId())
9886         except errors.ReservationError:
9887           raise errors.OpPrereqError("LV named %s used by another instance" %
9888                                      lv_name, errors.ECODE_NOTUNIQUE)
9889
9890       vg_names = self.rpc.call_vg_list([pnode.name])[pnode.name]
9891       vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
9892
9893       node_lvs = self.rpc.call_lv_list([pnode.name],
9894                                        vg_names.payload.keys())[pnode.name]
9895       node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
9896       node_lvs = node_lvs.payload
9897
9898       delta = all_lvs.difference(node_lvs.keys())
9899       if delta:
9900         raise errors.OpPrereqError("Missing logical volume(s): %s" %
9901                                    utils.CommaJoin(delta),
9902                                    errors.ECODE_INVAL)
9903       online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
9904       if online_lvs:
9905         raise errors.OpPrereqError("Online logical volumes found, cannot"
9906                                    " adopt: %s" % utils.CommaJoin(online_lvs),
9907                                    errors.ECODE_STATE)
9908       # update the size of disk based on what is found
9909       for dsk in self.disks:
9910         dsk[constants.IDISK_SIZE] = \
9911           int(float(node_lvs["%s/%s" % (dsk[constants.IDISK_VG],
9912                                         dsk[constants.IDISK_ADOPT])][0]))
9913
9914     elif self.op.disk_template == constants.DT_BLOCK:
9915       # Normalize and de-duplicate device paths
9916       all_disks = set([os.path.abspath(disk[constants.IDISK_ADOPT])
9917                        for disk in self.disks])
9918       if len(all_disks) != len(self.disks):
9919         raise errors.OpPrereqError("Duplicate disk names given for adoption",
9920                                    errors.ECODE_INVAL)
9921       baddisks = [d for d in all_disks
9922                   if not d.startswith(constants.ADOPTABLE_BLOCKDEV_ROOT)]
9923       if baddisks:
9924         raise errors.OpPrereqError("Device node(s) %s lie outside %s and"
9925                                    " cannot be adopted" %
9926                                    (", ".join(baddisks),
9927                                     constants.ADOPTABLE_BLOCKDEV_ROOT),
9928                                    errors.ECODE_INVAL)
9929
9930       node_disks = self.rpc.call_bdev_sizes([pnode.name],
9931                                             list(all_disks))[pnode.name]
9932       node_disks.Raise("Cannot get block device information from node %s" %
9933                        pnode.name)
9934       node_disks = node_disks.payload
9935       delta = all_disks.difference(node_disks.keys())
9936       if delta:
9937         raise errors.OpPrereqError("Missing block device(s): %s" %
9938                                    utils.CommaJoin(delta),
9939                                    errors.ECODE_INVAL)
9940       for dsk in self.disks:
9941         dsk[constants.IDISK_SIZE] = \
9942           int(float(node_disks[dsk[constants.IDISK_ADOPT]]))
9943
9944     _CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
9945
9946     _CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant)
9947     # check OS parameters (remotely)
9948     _CheckOSParams(self, True, nodenames, self.op.os_type, self.os_full)
9949
9950     _CheckNicsBridgesExist(self, self.nics, self.pnode.name)
9951
9952     # memory check on primary node
9953     #TODO(dynmem): use MINMEM for checking
9954     if self.op.start:
9955       _CheckNodeFreeMemory(self, self.pnode.name,
9956                            "creating instance %s" % self.op.instance_name,
9957                            self.be_full[constants.BE_MAXMEM],
9958                            self.op.hypervisor)
9959
9960     self.dry_run_result = list(nodenames)
9961
9962   def Exec(self, feedback_fn):
9963     """Create and add the instance to the cluster.
9964
9965     """
9966     instance = self.op.instance_name
9967     pnode_name = self.pnode.name
9968
9969     assert not (self.owned_locks(locking.LEVEL_NODE_RES) -
9970                 self.owned_locks(locking.LEVEL_NODE)), \
9971       "Node locks differ from node resource locks"
9972
9973     ht_kind = self.op.hypervisor
9974     if ht_kind in constants.HTS_REQ_PORT:
9975       network_port = self.cfg.AllocatePort()
9976     else:
9977       network_port = None
9978
9979     disks = _GenerateDiskTemplate(self,
9980                                   self.op.disk_template,
9981                                   instance, pnode_name,
9982                                   self.secondaries,
9983                                   self.disks,
9984                                   self.instance_file_storage_dir,
9985                                   self.op.file_driver,
9986                                   0,
9987                                   feedback_fn,
9988                                   self.diskparams)
9989
9990     iobj = objects.Instance(name=instance, os=self.op.os_type,
9991                             primary_node=pnode_name,
9992                             nics=self.nics, disks=disks,
9993                             disk_template=self.op.disk_template,
9994                             admin_state=constants.ADMINST_DOWN,
9995                             network_port=network_port,
9996                             beparams=self.op.beparams,
9997                             hvparams=self.op.hvparams,
9998                             hypervisor=self.op.hypervisor,
9999                             osparams=self.op.osparams,
10000                             )
10001
10002     if self.op.tags:
10003       for tag in self.op.tags:
10004         iobj.AddTag(tag)
10005
10006     if self.adopt_disks:
10007       if self.op.disk_template == constants.DT_PLAIN:
10008         # rename LVs to the newly-generated names; we need to construct
10009         # 'fake' LV disks with the old data, plus the new unique_id
10010         tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
10011         rename_to = []
10012         for t_dsk, a_dsk in zip(tmp_disks, self.disks):
10013           rename_to.append(t_dsk.logical_id)
10014           t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
10015           self.cfg.SetDiskID(t_dsk, pnode_name)
10016         result = self.rpc.call_blockdev_rename(pnode_name,
10017                                                zip(tmp_disks, rename_to))
10018         result.Raise("Failed to rename adoped LVs")
10019     else:
10020       feedback_fn("* creating instance disks...")
10021       try:
10022         _CreateDisks(self, iobj)
10023       except errors.OpExecError:
10024         self.LogWarning("Device creation failed, reverting...")
10025         try:
10026           _RemoveDisks(self, iobj)
10027         finally:
10028           self.cfg.ReleaseDRBDMinors(instance)
10029           raise
10030
10031     feedback_fn("adding instance %s to cluster config" % instance)
10032
10033     self.cfg.AddInstance(iobj, self.proc.GetECId())
10034
10035     # Declare that we don't want to remove the instance lock anymore, as we've
10036     # added the instance to the config
10037     del self.remove_locks[locking.LEVEL_INSTANCE]
10038
10039     if self.op.mode == constants.INSTANCE_IMPORT:
10040       # Release unused nodes
10041       _ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node])
10042     else:
10043       # Release all nodes
10044       _ReleaseLocks(self, locking.LEVEL_NODE)
10045
10046     disk_abort = False
10047     if not self.adopt_disks and self.cfg.GetClusterInfo().prealloc_wipe_disks:
10048       feedback_fn("* wiping instance disks...")
10049       try:
10050         _WipeDisks(self, iobj)
10051       except errors.OpExecError, err:
10052         logging.exception("Wiping disks failed")
10053         self.LogWarning("Wiping instance disks failed (%s)", err)
10054         disk_abort = True
10055
10056     if disk_abort:
10057       # Something is already wrong with the disks, don't do anything else
10058       pass
10059     elif self.op.wait_for_sync:
10060       disk_abort = not _WaitForSync(self, iobj)
10061     elif iobj.disk_template in constants.DTS_INT_MIRROR:
10062       # make sure the disks are not degraded (still sync-ing is ok)
10063       feedback_fn("* checking mirrors status")
10064       disk_abort = not _WaitForSync(self, iobj, oneshot=True)
10065     else:
10066       disk_abort = False
10067
10068     if disk_abort:
10069       _RemoveDisks(self, iobj)
10070       self.cfg.RemoveInstance(iobj.name)
10071       # Make sure the instance lock gets removed
10072       self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
10073       raise errors.OpExecError("There are some degraded disks for"
10074                                " this instance")
10075
10076     # Release all node resource locks
10077     _ReleaseLocks(self, locking.LEVEL_NODE_RES)
10078
10079     if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
10080       if self.op.mode == constants.INSTANCE_CREATE:
10081         if not self.op.no_install:
10082           pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
10083                         not self.op.wait_for_sync)
10084           if pause_sync:
10085             feedback_fn("* pausing disk sync to install instance OS")
10086             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
10087                                                               iobj.disks, True)
10088             for idx, success in enumerate(result.payload):
10089               if not success:
10090                 logging.warn("pause-sync of instance %s for disk %d failed",
10091                              instance, idx)
10092
10093           feedback_fn("* running the instance OS create scripts...")
10094           # FIXME: pass debug option from opcode to backend
10095           os_add_result = \
10096             self.rpc.call_instance_os_add(pnode_name, (iobj, None), False,
10097                                           self.op.debug_level)
10098           if pause_sync:
10099             feedback_fn("* resuming disk sync")
10100             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
10101                                                               iobj.disks, False)
10102             for idx, success in enumerate(result.payload):
10103               if not success:
10104                 logging.warn("resume-sync of instance %s for disk %d failed",
10105                              instance, idx)
10106
10107           os_add_result.Raise("Could not add os for instance %s"
10108                               " on node %s" % (instance, pnode_name))
10109
10110       elif self.op.mode == constants.INSTANCE_IMPORT:
10111         feedback_fn("* running the instance OS import scripts...")
10112
10113         transfers = []
10114
10115         for idx, image in enumerate(self.src_images):
10116           if not image:
10117             continue
10118
10119           # FIXME: pass debug option from opcode to backend
10120           dt = masterd.instance.DiskTransfer("disk/%s" % idx,
10121                                              constants.IEIO_FILE, (image, ),
10122                                              constants.IEIO_SCRIPT,
10123                                              (iobj.disks[idx], idx),
10124                                              None)
10125           transfers.append(dt)
10126
10127         import_result = \
10128           masterd.instance.TransferInstanceData(self, feedback_fn,
10129                                                 self.op.src_node, pnode_name,
10130                                                 self.pnode.secondary_ip,
10131                                                 iobj, transfers)
10132         if not compat.all(import_result):
10133           self.LogWarning("Some disks for instance %s on node %s were not"
10134                           " imported successfully" % (instance, pnode_name))
10135
10136       elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
10137         feedback_fn("* preparing remote import...")
10138         # The source cluster will stop the instance before attempting to make a
10139         # connection. In some cases stopping an instance can take a long time,
10140         # hence the shutdown timeout is added to the connection timeout.
10141         connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
10142                            self.op.source_shutdown_timeout)
10143         timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
10144
10145         assert iobj.primary_node == self.pnode.name
10146         disk_results = \
10147           masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
10148                                         self.source_x509_ca,
10149                                         self._cds, timeouts)
10150         if not compat.all(disk_results):
10151           # TODO: Should the instance still be started, even if some disks
10152           # failed to import (valid for local imports, too)?
10153           self.LogWarning("Some disks for instance %s on node %s were not"
10154                           " imported successfully" % (instance, pnode_name))
10155
10156         # Run rename script on newly imported instance
10157         assert iobj.name == instance
10158         feedback_fn("Running rename script for %s" % instance)
10159         result = self.rpc.call_instance_run_rename(pnode_name, iobj,
10160                                                    self.source_instance_name,
10161                                                    self.op.debug_level)
10162         if result.fail_msg:
10163           self.LogWarning("Failed to run rename script for %s on node"
10164                           " %s: %s" % (instance, pnode_name, result.fail_msg))
10165
10166       else:
10167         # also checked in the prereq part
10168         raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
10169                                      % self.op.mode)
10170
10171     assert not self.owned_locks(locking.LEVEL_NODE_RES)
10172
10173     if self.op.start:
10174       iobj.admin_state = constants.ADMINST_UP
10175       self.cfg.Update(iobj, feedback_fn)
10176       logging.info("Starting instance %s on node %s", instance, pnode_name)
10177       feedback_fn("* starting instance...")
10178       result = self.rpc.call_instance_start(pnode_name, (iobj, None, None),
10179                                             False)
10180       result.Raise("Could not start instance")
10181
10182     return list(iobj.all_nodes)
10183
10184
10185 def _CheckRADOSFreeSpace():
10186   """Compute disk size requirements inside the RADOS cluster.
10187
10188   """
10189   # For the RADOS cluster we assume there is always enough space.
10190   pass
10191
10192
10193 class LUInstanceConsole(NoHooksLU):
10194   """Connect to an instance's console.
10195
10196   This is somewhat special in that it returns the command line that
10197   you need to run on the master node in order to connect to the
10198   console.
10199
10200   """
10201   REQ_BGL = False
10202
10203   def ExpandNames(self):
10204     self.share_locks = _ShareAll()
10205     self._ExpandAndLockInstance()
10206
10207   def CheckPrereq(self):
10208     """Check prerequisites.
10209
10210     This checks that the instance is in the cluster.
10211
10212     """
10213     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
10214     assert self.instance is not None, \
10215       "Cannot retrieve locked instance %s" % self.op.instance_name
10216     _CheckNodeOnline(self, self.instance.primary_node)
10217
10218   def Exec(self, feedback_fn):
10219     """Connect to the console of an instance
10220
10221     """
10222     instance = self.instance
10223     node = instance.primary_node
10224
10225     node_insts = self.rpc.call_instance_list([node],
10226                                              [instance.hypervisor])[node]
10227     node_insts.Raise("Can't get node information from %s" % node)
10228
10229     if instance.name not in node_insts.payload:
10230       if instance.admin_state == constants.ADMINST_UP:
10231         state = constants.INSTST_ERRORDOWN
10232       elif instance.admin_state == constants.ADMINST_DOWN:
10233         state = constants.INSTST_ADMINDOWN
10234       else:
10235         state = constants.INSTST_ADMINOFFLINE
10236       raise errors.OpExecError("Instance %s is not running (state %s)" %
10237                                (instance.name, state))
10238
10239     logging.debug("Connecting to console of %s on %s", instance.name, node)
10240
10241     return _GetInstanceConsole(self.cfg.GetClusterInfo(), instance)
10242
10243
10244 def _GetInstanceConsole(cluster, instance):
10245   """Returns console information for an instance.
10246
10247   @type cluster: L{objects.Cluster}
10248   @type instance: L{objects.Instance}
10249   @rtype: dict
10250
10251   """
10252   hyper = hypervisor.GetHypervisor(instance.hypervisor)
10253   # beparams and hvparams are passed separately, to avoid editing the
10254   # instance and then saving the defaults in the instance itself.
10255   hvparams = cluster.FillHV(instance)
10256   beparams = cluster.FillBE(instance)
10257   console = hyper.GetInstanceConsole(instance, hvparams, beparams)
10258
10259   assert console.instance == instance.name
10260   assert console.Validate()
10261
10262   return console.ToDict()
10263
10264
10265 class LUInstanceReplaceDisks(LogicalUnit):
10266   """Replace the disks of an instance.
10267
10268   """
10269   HPATH = "mirrors-replace"
10270   HTYPE = constants.HTYPE_INSTANCE
10271   REQ_BGL = False
10272
10273   def CheckArguments(self):
10274     TLReplaceDisks.CheckArguments(self.op.mode, self.op.remote_node,
10275                                   self.op.iallocator)
10276
10277   def ExpandNames(self):
10278     self._ExpandAndLockInstance()
10279
10280     assert locking.LEVEL_NODE not in self.needed_locks
10281     assert locking.LEVEL_NODE_RES not in self.needed_locks
10282     assert locking.LEVEL_NODEGROUP not in self.needed_locks
10283
10284     assert self.op.iallocator is None or self.op.remote_node is None, \
10285       "Conflicting options"
10286
10287     if self.op.remote_node is not None:
10288       self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
10289
10290       # Warning: do not remove the locking of the new secondary here
10291       # unless DRBD8.AddChildren is changed to work in parallel;
10292       # currently it doesn't since parallel invocations of
10293       # FindUnusedMinor will conflict
10294       self.needed_locks[locking.LEVEL_NODE] = [self.op.remote_node]
10295       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
10296     else:
10297       self.needed_locks[locking.LEVEL_NODE] = []
10298       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
10299
10300       if self.op.iallocator is not None:
10301         # iallocator will select a new node in the same group
10302         self.needed_locks[locking.LEVEL_NODEGROUP] = []
10303
10304     self.needed_locks[locking.LEVEL_NODE_RES] = []
10305
10306     self.replacer = TLReplaceDisks(self, self.op.instance_name, self.op.mode,
10307                                    self.op.iallocator, self.op.remote_node,
10308                                    self.op.disks, False, self.op.early_release,
10309                                    self.op.ignore_ipolicy)
10310
10311     self.tasklets = [self.replacer]
10312
10313   def DeclareLocks(self, level):
10314     if level == locking.LEVEL_NODEGROUP:
10315       assert self.op.remote_node is None
10316       assert self.op.iallocator is not None
10317       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
10318
10319       self.share_locks[locking.LEVEL_NODEGROUP] = 1
10320       # Lock all groups used by instance optimistically; this requires going
10321       # via the node before it's locked, requiring verification later on
10322       self.needed_locks[locking.LEVEL_NODEGROUP] = \
10323         self.cfg.GetInstanceNodeGroups(self.op.instance_name)
10324
10325     elif level == locking.LEVEL_NODE:
10326       if self.op.iallocator is not None:
10327         assert self.op.remote_node is None
10328         assert not self.needed_locks[locking.LEVEL_NODE]
10329
10330         # Lock member nodes of all locked groups
10331         self.needed_locks[locking.LEVEL_NODE] = [node_name
10332           for group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
10333           for node_name in self.cfg.GetNodeGroup(group_uuid).members]
10334       else:
10335         self._LockInstancesNodes()
10336     elif level == locking.LEVEL_NODE_RES:
10337       # Reuse node locks
10338       self.needed_locks[locking.LEVEL_NODE_RES] = \
10339         self.needed_locks[locking.LEVEL_NODE]
10340
10341   def BuildHooksEnv(self):
10342     """Build hooks env.
10343
10344     This runs on the master, the primary and all the secondaries.
10345
10346     """
10347     instance = self.replacer.instance
10348     env = {
10349       "MODE": self.op.mode,
10350       "NEW_SECONDARY": self.op.remote_node,
10351       "OLD_SECONDARY": instance.secondary_nodes[0],
10352       }
10353     env.update(_BuildInstanceHookEnvByObject(self, instance))
10354     return env
10355
10356   def BuildHooksNodes(self):
10357     """Build hooks nodes.
10358
10359     """
10360     instance = self.replacer.instance
10361     nl = [
10362       self.cfg.GetMasterNode(),
10363       instance.primary_node,
10364       ]
10365     if self.op.remote_node is not None:
10366       nl.append(self.op.remote_node)
10367     return nl, nl
10368
10369   def CheckPrereq(self):
10370     """Check prerequisites.
10371
10372     """
10373     assert (self.glm.is_owned(locking.LEVEL_NODEGROUP) or
10374             self.op.iallocator is None)
10375
10376     # Verify if node group locks are still correct
10377     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
10378     if owned_groups:
10379       _CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
10380
10381     return LogicalUnit.CheckPrereq(self)
10382
10383
10384 class TLReplaceDisks(Tasklet):
10385   """Replaces disks for an instance.
10386
10387   Note: Locking is not within the scope of this class.
10388
10389   """
10390   def __init__(self, lu, instance_name, mode, iallocator_name, remote_node,
10391                disks, delay_iallocator, early_release, ignore_ipolicy):
10392     """Initializes this class.
10393
10394     """
10395     Tasklet.__init__(self, lu)
10396
10397     # Parameters
10398     self.instance_name = instance_name
10399     self.mode = mode
10400     self.iallocator_name = iallocator_name
10401     self.remote_node = remote_node
10402     self.disks = disks
10403     self.delay_iallocator = delay_iallocator
10404     self.early_release = early_release
10405     self.ignore_ipolicy = ignore_ipolicy
10406
10407     # Runtime data
10408     self.instance = None
10409     self.new_node = None
10410     self.target_node = None
10411     self.other_node = None
10412     self.remote_node_info = None
10413     self.node_secondary_ip = None
10414
10415   @staticmethod
10416   def CheckArguments(mode, remote_node, iallocator):
10417     """Helper function for users of this class.
10418
10419     """
10420     # check for valid parameter combination
10421     if mode == constants.REPLACE_DISK_CHG:
10422       if remote_node is None and iallocator is None:
10423         raise errors.OpPrereqError("When changing the secondary either an"
10424                                    " iallocator script must be used or the"
10425                                    " new node given", errors.ECODE_INVAL)
10426
10427       if remote_node is not None and iallocator is not None:
10428         raise errors.OpPrereqError("Give either the iallocator or the new"
10429                                    " secondary, not both", errors.ECODE_INVAL)
10430
10431     elif remote_node is not None or iallocator is not None:
10432       # Not replacing the secondary
10433       raise errors.OpPrereqError("The iallocator and new node options can"
10434                                  " only be used when changing the"
10435                                  " secondary node", errors.ECODE_INVAL)
10436
10437   @staticmethod
10438   def _RunAllocator(lu, iallocator_name, instance_name, relocate_from):
10439     """Compute a new secondary node using an IAllocator.
10440
10441     """
10442     ial = IAllocator(lu.cfg, lu.rpc,
10443                      mode=constants.IALLOCATOR_MODE_RELOC,
10444                      name=instance_name,
10445                      relocate_from=list(relocate_from))
10446
10447     ial.Run(iallocator_name)
10448
10449     if not ial.success:
10450       raise errors.OpPrereqError("Can't compute nodes using iallocator '%s':"
10451                                  " %s" % (iallocator_name, ial.info),
10452                                  errors.ECODE_NORES)
10453
10454     if len(ial.result) != ial.required_nodes:
10455       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
10456                                  " of nodes (%s), required %s" %
10457                                  (iallocator_name,
10458                                   len(ial.result), ial.required_nodes),
10459                                  errors.ECODE_FAULT)
10460
10461     remote_node_name = ial.result[0]
10462
10463     lu.LogInfo("Selected new secondary for instance '%s': %s",
10464                instance_name, remote_node_name)
10465
10466     return remote_node_name
10467
10468   def _FindFaultyDisks(self, node_name):
10469     """Wrapper for L{_FindFaultyInstanceDisks}.
10470
10471     """
10472     return _FindFaultyInstanceDisks(self.cfg, self.rpc, self.instance,
10473                                     node_name, True)
10474
10475   def _CheckDisksActivated(self, instance):
10476     """Checks if the instance disks are activated.
10477
10478     @param instance: The instance to check disks
10479     @return: True if they are activated, False otherwise
10480
10481     """
10482     nodes = instance.all_nodes
10483
10484     for idx, dev in enumerate(instance.disks):
10485       for node in nodes:
10486         self.lu.LogInfo("Checking disk/%d on %s", idx, node)
10487         self.cfg.SetDiskID(dev, node)
10488
10489         result = self.rpc.call_blockdev_find(node, dev)
10490
10491         if result.offline:
10492           continue
10493         elif result.fail_msg or not result.payload:
10494           return False
10495
10496     return True
10497
10498   def CheckPrereq(self):
10499     """Check prerequisites.
10500
10501     This checks that the instance is in the cluster.
10502
10503     """
10504     self.instance = instance = self.cfg.GetInstanceInfo(self.instance_name)
10505     assert instance is not None, \
10506       "Cannot retrieve locked instance %s" % self.instance_name
10507
10508     if instance.disk_template != constants.DT_DRBD8:
10509       raise errors.OpPrereqError("Can only run replace disks for DRBD8-based"
10510                                  " instances", errors.ECODE_INVAL)
10511
10512     if len(instance.secondary_nodes) != 1:
10513       raise errors.OpPrereqError("The instance has a strange layout,"
10514                                  " expected one secondary but found %d" %
10515                                  len(instance.secondary_nodes),
10516                                  errors.ECODE_FAULT)
10517
10518     if not self.delay_iallocator:
10519       self._CheckPrereq2()
10520
10521   def _CheckPrereq2(self):
10522     """Check prerequisites, second part.
10523
10524     This function should always be part of CheckPrereq. It was separated and is
10525     now called from Exec because during node evacuation iallocator was only
10526     called with an unmodified cluster model, not taking planned changes into
10527     account.
10528
10529     """
10530     instance = self.instance
10531     secondary_node = instance.secondary_nodes[0]
10532
10533     if self.iallocator_name is None:
10534       remote_node = self.remote_node
10535     else:
10536       remote_node = self._RunAllocator(self.lu, self.iallocator_name,
10537                                        instance.name, instance.secondary_nodes)
10538
10539     if remote_node is None:
10540       self.remote_node_info = None
10541     else:
10542       assert remote_node in self.lu.owned_locks(locking.LEVEL_NODE), \
10543              "Remote node '%s' is not locked" % remote_node
10544
10545       self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
10546       assert self.remote_node_info is not None, \
10547         "Cannot retrieve locked node %s" % remote_node
10548
10549     if remote_node == self.instance.primary_node:
10550       raise errors.OpPrereqError("The specified node is the primary node of"
10551                                  " the instance", errors.ECODE_INVAL)
10552
10553     if remote_node == secondary_node:
10554       raise errors.OpPrereqError("The specified node is already the"
10555                                  " secondary node of the instance",
10556                                  errors.ECODE_INVAL)
10557
10558     if self.disks and self.mode in (constants.REPLACE_DISK_AUTO,
10559                                     constants.REPLACE_DISK_CHG):
10560       raise errors.OpPrereqError("Cannot specify disks to be replaced",
10561                                  errors.ECODE_INVAL)
10562
10563     if self.mode == constants.REPLACE_DISK_AUTO:
10564       if not self._CheckDisksActivated(instance):
10565         raise errors.OpPrereqError("Please run activate-disks on instance %s"
10566                                    " first" % self.instance_name,
10567                                    errors.ECODE_STATE)
10568       faulty_primary = self._FindFaultyDisks(instance.primary_node)
10569       faulty_secondary = self._FindFaultyDisks(secondary_node)
10570
10571       if faulty_primary and faulty_secondary:
10572         raise errors.OpPrereqError("Instance %s has faulty disks on more than"
10573                                    " one node and can not be repaired"
10574                                    " automatically" % self.instance_name,
10575                                    errors.ECODE_STATE)
10576
10577       if faulty_primary:
10578         self.disks = faulty_primary
10579         self.target_node = instance.primary_node
10580         self.other_node = secondary_node
10581         check_nodes = [self.target_node, self.other_node]
10582       elif faulty_secondary:
10583         self.disks = faulty_secondary
10584         self.target_node = secondary_node
10585         self.other_node = instance.primary_node
10586         check_nodes = [self.target_node, self.other_node]
10587       else:
10588         self.disks = []
10589         check_nodes = []
10590
10591     else:
10592       # Non-automatic modes
10593       if self.mode == constants.REPLACE_DISK_PRI:
10594         self.target_node = instance.primary_node
10595         self.other_node = secondary_node
10596         check_nodes = [self.target_node, self.other_node]
10597
10598       elif self.mode == constants.REPLACE_DISK_SEC:
10599         self.target_node = secondary_node
10600         self.other_node = instance.primary_node
10601         check_nodes = [self.target_node, self.other_node]
10602
10603       elif self.mode == constants.REPLACE_DISK_CHG:
10604         self.new_node = remote_node
10605         self.other_node = instance.primary_node
10606         self.target_node = secondary_node
10607         check_nodes = [self.new_node, self.other_node]
10608
10609         _CheckNodeNotDrained(self.lu, remote_node)
10610         _CheckNodeVmCapable(self.lu, remote_node)
10611
10612         old_node_info = self.cfg.GetNodeInfo(secondary_node)
10613         assert old_node_info is not None
10614         if old_node_info.offline and not self.early_release:
10615           # doesn't make sense to delay the release
10616           self.early_release = True
10617           self.lu.LogInfo("Old secondary %s is offline, automatically enabling"
10618                           " early-release mode", secondary_node)
10619
10620       else:
10621         raise errors.ProgrammerError("Unhandled disk replace mode (%s)" %
10622                                      self.mode)
10623
10624       # If not specified all disks should be replaced
10625       if not self.disks:
10626         self.disks = range(len(self.instance.disks))
10627
10628     # TODO: This is ugly, but right now we can't distinguish between internal
10629     # submitted opcode and external one. We should fix that.
10630     if self.remote_node_info:
10631       # We change the node, lets verify it still meets instance policy
10632       new_group_info = self.cfg.GetNodeGroup(self.remote_node_info.group)
10633       ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(),
10634                                        new_group_info)
10635       _CheckTargetNodeIPolicy(self, ipolicy, instance, self.remote_node_info,
10636                               ignore=self.ignore_ipolicy)
10637
10638     # TODO: compute disk parameters
10639     primary_node_info = self.cfg.GetNodeInfo(instance.primary_node)
10640     secondary_node_info = self.cfg.GetNodeInfo(secondary_node)
10641     if primary_node_info.group != secondary_node_info.group:
10642       self.lu.LogInfo("The instance primary and secondary nodes are in two"
10643                       " different node groups; the disk parameters of the"
10644                       " primary node's group will be applied.")
10645
10646     self.diskparams = self.cfg.GetNodeGroup(primary_node_info.group).diskparams
10647
10648     for node in check_nodes:
10649       _CheckNodeOnline(self.lu, node)
10650
10651     touched_nodes = frozenset(node_name for node_name in [self.new_node,
10652                                                           self.other_node,
10653                                                           self.target_node]
10654                               if node_name is not None)
10655
10656     # Release unneeded node and node resource locks
10657     _ReleaseLocks(self.lu, locking.LEVEL_NODE, keep=touched_nodes)
10658     _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES, keep=touched_nodes)
10659
10660     # Release any owned node group
10661     if self.lu.glm.is_owned(locking.LEVEL_NODEGROUP):
10662       _ReleaseLocks(self.lu, locking.LEVEL_NODEGROUP)
10663
10664     # Check whether disks are valid
10665     for disk_idx in self.disks:
10666       instance.FindDisk(disk_idx)
10667
10668     # Get secondary node IP addresses
10669     self.node_secondary_ip = dict((name, node.secondary_ip) for (name, node)
10670                                   in self.cfg.GetMultiNodeInfo(touched_nodes))
10671
10672   def Exec(self, feedback_fn):
10673     """Execute disk replacement.
10674
10675     This dispatches the disk replacement to the appropriate handler.
10676
10677     """
10678     if self.delay_iallocator:
10679       self._CheckPrereq2()
10680
10681     if __debug__:
10682       # Verify owned locks before starting operation
10683       owned_nodes = self.lu.owned_locks(locking.LEVEL_NODE)
10684       assert set(owned_nodes) == set(self.node_secondary_ip), \
10685           ("Incorrect node locks, owning %s, expected %s" %
10686            (owned_nodes, self.node_secondary_ip.keys()))
10687       assert (self.lu.owned_locks(locking.LEVEL_NODE) ==
10688               self.lu.owned_locks(locking.LEVEL_NODE_RES))
10689
10690       owned_instances = self.lu.owned_locks(locking.LEVEL_INSTANCE)
10691       assert list(owned_instances) == [self.instance_name], \
10692           "Instance '%s' not locked" % self.instance_name
10693
10694       assert not self.lu.glm.is_owned(locking.LEVEL_NODEGROUP), \
10695           "Should not own any node group lock at this point"
10696
10697     if not self.disks:
10698       feedback_fn("No disks need replacement")
10699       return
10700
10701     feedback_fn("Replacing disk(s) %s for %s" %
10702                 (utils.CommaJoin(self.disks), self.instance.name))
10703
10704     activate_disks = (self.instance.admin_state != constants.ADMINST_UP)
10705
10706     # Activate the instance disks if we're replacing them on a down instance
10707     if activate_disks:
10708       _StartInstanceDisks(self.lu, self.instance, True)
10709
10710     try:
10711       # Should we replace the secondary node?
10712       if self.new_node is not None:
10713         fn = self._ExecDrbd8Secondary
10714       else:
10715         fn = self._ExecDrbd8DiskOnly
10716
10717       result = fn(feedback_fn)
10718     finally:
10719       # Deactivate the instance disks if we're replacing them on a
10720       # down instance
10721       if activate_disks:
10722         _SafeShutdownInstanceDisks(self.lu, self.instance)
10723
10724     assert not self.lu.owned_locks(locking.LEVEL_NODE)
10725
10726     if __debug__:
10727       # Verify owned locks
10728       owned_nodes = self.lu.owned_locks(locking.LEVEL_NODE_RES)
10729       nodes = frozenset(self.node_secondary_ip)
10730       assert ((self.early_release and not owned_nodes) or
10731               (not self.early_release and not (set(owned_nodes) - nodes))), \
10732         ("Not owning the correct locks, early_release=%s, owned=%r,"
10733          " nodes=%r" % (self.early_release, owned_nodes, nodes))
10734
10735     return result
10736
10737   def _CheckVolumeGroup(self, nodes):
10738     self.lu.LogInfo("Checking volume groups")
10739
10740     vgname = self.cfg.GetVGName()
10741
10742     # Make sure volume group exists on all involved nodes
10743     results = self.rpc.call_vg_list(nodes)
10744     if not results:
10745       raise errors.OpExecError("Can't list volume groups on the nodes")
10746
10747     for node in nodes:
10748       res = results[node]
10749       res.Raise("Error checking node %s" % node)
10750       if vgname not in res.payload:
10751         raise errors.OpExecError("Volume group '%s' not found on node %s" %
10752                                  (vgname, node))
10753
10754   def _CheckDisksExistence(self, nodes):
10755     # Check disk existence
10756     for idx, dev in enumerate(self.instance.disks):
10757       if idx not in self.disks:
10758         continue
10759
10760       for node in nodes:
10761         self.lu.LogInfo("Checking disk/%d on %s" % (idx, node))
10762         self.cfg.SetDiskID(dev, node)
10763
10764         result = self.rpc.call_blockdev_find(node, dev)
10765
10766         msg = result.fail_msg
10767         if msg or not result.payload:
10768           if not msg:
10769             msg = "disk not found"
10770           raise errors.OpExecError("Can't find disk/%d on node %s: %s" %
10771                                    (idx, node, msg))
10772
10773   def _CheckDisksConsistency(self, node_name, on_primary, ldisk):
10774     for idx, dev in enumerate(self.instance.disks):
10775       if idx not in self.disks:
10776         continue
10777
10778       self.lu.LogInfo("Checking disk/%d consistency on node %s" %
10779                       (idx, node_name))
10780
10781       if not _CheckDiskConsistency(self.lu, dev, node_name, on_primary,
10782                                    ldisk=ldisk):
10783         raise errors.OpExecError("Node %s has degraded storage, unsafe to"
10784                                  " replace disks for instance %s" %
10785                                  (node_name, self.instance.name))
10786
10787   def _CreateNewStorage(self, node_name):
10788     """Create new storage on the primary or secondary node.
10789
10790     This is only used for same-node replaces, not for changing the
10791     secondary node, hence we don't want to modify the existing disk.
10792
10793     """
10794     iv_names = {}
10795
10796     for idx, dev in enumerate(self.instance.disks):
10797       if idx not in self.disks:
10798         continue
10799
10800       self.lu.LogInfo("Adding storage on %s for disk/%d" % (node_name, idx))
10801
10802       self.cfg.SetDiskID(dev, node_name)
10803
10804       lv_names = [".disk%d_%s" % (idx, suffix) for suffix in ["data", "meta"]]
10805       names = _GenerateUniqueNames(self.lu, lv_names)
10806
10807       _, data_p, meta_p = _ComputeLDParams(constants.DT_DRBD8, self.diskparams)
10808
10809       vg_data = dev.children[0].logical_id[0]
10810       lv_data = objects.Disk(dev_type=constants.LD_LV, size=dev.size,
10811                              logical_id=(vg_data, names[0]), params=data_p)
10812       vg_meta = dev.children[1].logical_id[0]
10813       lv_meta = objects.Disk(dev_type=constants.LD_LV, size=DRBD_META_SIZE,
10814                              logical_id=(vg_meta, names[1]), params=meta_p)
10815
10816       new_lvs = [lv_data, lv_meta]
10817       old_lvs = [child.Copy() for child in dev.children]
10818       iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
10819
10820       # we pass force_create=True to force the LVM creation
10821       for new_lv in new_lvs:
10822         _CreateBlockDev(self.lu, node_name, self.instance, new_lv, True,
10823                         _GetInstanceInfoText(self.instance), False)
10824
10825     return iv_names
10826
10827   def _CheckDevices(self, node_name, iv_names):
10828     for name, (dev, _, _) in iv_names.iteritems():
10829       self.cfg.SetDiskID(dev, node_name)
10830
10831       result = self.rpc.call_blockdev_find(node_name, dev)
10832
10833       msg = result.fail_msg
10834       if msg or not result.payload:
10835         if not msg:
10836           msg = "disk not found"
10837         raise errors.OpExecError("Can't find DRBD device %s: %s" %
10838                                  (name, msg))
10839
10840       if result.payload.is_degraded:
10841         raise errors.OpExecError("DRBD device %s is degraded!" % name)
10842
10843   def _RemoveOldStorage(self, node_name, iv_names):
10844     for name, (_, old_lvs, _) in iv_names.iteritems():
10845       self.lu.LogInfo("Remove logical volumes for %s" % name)
10846
10847       for lv in old_lvs:
10848         self.cfg.SetDiskID(lv, node_name)
10849
10850         msg = self.rpc.call_blockdev_remove(node_name, lv).fail_msg
10851         if msg:
10852           self.lu.LogWarning("Can't remove old LV: %s" % msg,
10853                              hint="remove unused LVs manually")
10854
10855   def _ExecDrbd8DiskOnly(self, feedback_fn): # pylint: disable=W0613
10856     """Replace a disk on the primary or secondary for DRBD 8.
10857
10858     The algorithm for replace is quite complicated:
10859
10860       1. for each disk to be replaced:
10861
10862         1. create new LVs on the target node with unique names
10863         1. detach old LVs from the drbd device
10864         1. rename old LVs to name_replaced.<time_t>
10865         1. rename new LVs to old LVs
10866         1. attach the new LVs (with the old names now) to the drbd device
10867
10868       1. wait for sync across all devices
10869
10870       1. for each modified disk:
10871
10872         1. remove old LVs (which have the name name_replaces.<time_t>)
10873
10874     Failures are not very well handled.
10875
10876     """
10877     steps_total = 6
10878
10879     # Step: check device activation
10880     self.lu.LogStep(1, steps_total, "Check device existence")
10881     self._CheckDisksExistence([self.other_node, self.target_node])
10882     self._CheckVolumeGroup([self.target_node, self.other_node])
10883
10884     # Step: check other node consistency
10885     self.lu.LogStep(2, steps_total, "Check peer consistency")
10886     self._CheckDisksConsistency(self.other_node,
10887                                 self.other_node == self.instance.primary_node,
10888                                 False)
10889
10890     # Step: create new storage
10891     self.lu.LogStep(3, steps_total, "Allocate new storage")
10892     iv_names = self._CreateNewStorage(self.target_node)
10893
10894     # Step: for each lv, detach+rename*2+attach
10895     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
10896     for dev, old_lvs, new_lvs in iv_names.itervalues():
10897       self.lu.LogInfo("Detaching %s drbd from local storage" % dev.iv_name)
10898
10899       result = self.rpc.call_blockdev_removechildren(self.target_node, dev,
10900                                                      old_lvs)
10901       result.Raise("Can't detach drbd from local storage on node"
10902                    " %s for device %s" % (self.target_node, dev.iv_name))
10903       #dev.children = []
10904       #cfg.Update(instance)
10905
10906       # ok, we created the new LVs, so now we know we have the needed
10907       # storage; as such, we proceed on the target node to rename
10908       # old_lv to _old, and new_lv to old_lv; note that we rename LVs
10909       # using the assumption that logical_id == physical_id (which in
10910       # turn is the unique_id on that node)
10911
10912       # FIXME(iustin): use a better name for the replaced LVs
10913       temp_suffix = int(time.time())
10914       ren_fn = lambda d, suff: (d.physical_id[0],
10915                                 d.physical_id[1] + "_replaced-%s" % suff)
10916
10917       # Build the rename list based on what LVs exist on the node
10918       rename_old_to_new = []
10919       for to_ren in old_lvs:
10920         result = self.rpc.call_blockdev_find(self.target_node, to_ren)
10921         if not result.fail_msg and result.payload:
10922           # device exists
10923           rename_old_to_new.append((to_ren, ren_fn(to_ren, temp_suffix)))
10924
10925       self.lu.LogInfo("Renaming the old LVs on the target node")
10926       result = self.rpc.call_blockdev_rename(self.target_node,
10927                                              rename_old_to_new)
10928       result.Raise("Can't rename old LVs on node %s" % self.target_node)
10929
10930       # Now we rename the new LVs to the old LVs
10931       self.lu.LogInfo("Renaming the new LVs on the target node")
10932       rename_new_to_old = [(new, old.physical_id)
10933                            for old, new in zip(old_lvs, new_lvs)]
10934       result = self.rpc.call_blockdev_rename(self.target_node,
10935                                              rename_new_to_old)
10936       result.Raise("Can't rename new LVs on node %s" % self.target_node)
10937
10938       # Intermediate steps of in memory modifications
10939       for old, new in zip(old_lvs, new_lvs):
10940         new.logical_id = old.logical_id
10941         self.cfg.SetDiskID(new, self.target_node)
10942
10943       # We need to modify old_lvs so that removal later removes the
10944       # right LVs, not the newly added ones; note that old_lvs is a
10945       # copy here
10946       for disk in old_lvs:
10947         disk.logical_id = ren_fn(disk, temp_suffix)
10948         self.cfg.SetDiskID(disk, self.target_node)
10949
10950       # Now that the new lvs have the old name, we can add them to the device
10951       self.lu.LogInfo("Adding new mirror component on %s" % self.target_node)
10952       result = self.rpc.call_blockdev_addchildren(self.target_node, dev,
10953                                                   new_lvs)
10954       msg = result.fail_msg
10955       if msg:
10956         for new_lv in new_lvs:
10957           msg2 = self.rpc.call_blockdev_remove(self.target_node,
10958                                                new_lv).fail_msg
10959           if msg2:
10960             self.lu.LogWarning("Can't rollback device %s: %s", dev, msg2,
10961                                hint=("cleanup manually the unused logical"
10962                                      "volumes"))
10963         raise errors.OpExecError("Can't add local storage to drbd: %s" % msg)
10964
10965     cstep = itertools.count(5)
10966
10967     if self.early_release:
10968       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
10969       self._RemoveOldStorage(self.target_node, iv_names)
10970       # TODO: Check if releasing locks early still makes sense
10971       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
10972     else:
10973       # Release all resource locks except those used by the instance
10974       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES,
10975                     keep=self.node_secondary_ip.keys())
10976
10977     # Release all node locks while waiting for sync
10978     _ReleaseLocks(self.lu, locking.LEVEL_NODE)
10979
10980     # TODO: Can the instance lock be downgraded here? Take the optional disk
10981     # shutdown in the caller into consideration.
10982
10983     # Wait for sync
10984     # This can fail as the old devices are degraded and _WaitForSync
10985     # does a combined result over all disks, so we don't check its return value
10986     self.lu.LogStep(cstep.next(), steps_total, "Sync devices")
10987     _WaitForSync(self.lu, self.instance)
10988
10989     # Check all devices manually
10990     self._CheckDevices(self.instance.primary_node, iv_names)
10991
10992     # Step: remove old storage
10993     if not self.early_release:
10994       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
10995       self._RemoveOldStorage(self.target_node, iv_names)
10996
10997   def _ExecDrbd8Secondary(self, feedback_fn):
10998     """Replace the secondary node for DRBD 8.
10999
11000     The algorithm for replace is quite complicated:
11001       - for all disks of the instance:
11002         - create new LVs on the new node with same names
11003         - shutdown the drbd device on the old secondary
11004         - disconnect the drbd network on the primary
11005         - create the drbd device on the new secondary
11006         - network attach the drbd on the primary, using an artifice:
11007           the drbd code for Attach() will connect to the network if it
11008           finds a device which is connected to the good local disks but
11009           not network enabled
11010       - wait for sync across all devices
11011       - remove all disks from the old secondary
11012
11013     Failures are not very well handled.
11014
11015     """
11016     steps_total = 6
11017
11018     pnode = self.instance.primary_node
11019
11020     # Step: check device activation
11021     self.lu.LogStep(1, steps_total, "Check device existence")
11022     self._CheckDisksExistence([self.instance.primary_node])
11023     self._CheckVolumeGroup([self.instance.primary_node])
11024
11025     # Step: check other node consistency
11026     self.lu.LogStep(2, steps_total, "Check peer consistency")
11027     self._CheckDisksConsistency(self.instance.primary_node, True, True)
11028
11029     # Step: create new storage
11030     self.lu.LogStep(3, steps_total, "Allocate new storage")
11031     for idx, dev in enumerate(self.instance.disks):
11032       self.lu.LogInfo("Adding new local storage on %s for disk/%d" %
11033                       (self.new_node, idx))
11034       # we pass force_create=True to force LVM creation
11035       for new_lv in dev.children:
11036         _CreateBlockDev(self.lu, self.new_node, self.instance, new_lv, True,
11037                         _GetInstanceInfoText(self.instance), False)
11038
11039     # Step 4: dbrd minors and drbd setups changes
11040     # after this, we must manually remove the drbd minors on both the
11041     # error and the success paths
11042     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
11043     minors = self.cfg.AllocateDRBDMinor([self.new_node
11044                                          for dev in self.instance.disks],
11045                                         self.instance.name)
11046     logging.debug("Allocated minors %r", minors)
11047
11048     iv_names = {}
11049     for idx, (dev, new_minor) in enumerate(zip(self.instance.disks, minors)):
11050       self.lu.LogInfo("activating a new drbd on %s for disk/%d" %
11051                       (self.new_node, idx))
11052       # create new devices on new_node; note that we create two IDs:
11053       # one without port, so the drbd will be activated without
11054       # networking information on the new node at this stage, and one
11055       # with network, for the latter activation in step 4
11056       (o_node1, o_node2, o_port, o_minor1, o_minor2, o_secret) = dev.logical_id
11057       if self.instance.primary_node == o_node1:
11058         p_minor = o_minor1
11059       else:
11060         assert self.instance.primary_node == o_node2, "Three-node instance?"
11061         p_minor = o_minor2
11062
11063       new_alone_id = (self.instance.primary_node, self.new_node, None,
11064                       p_minor, new_minor, o_secret)
11065       new_net_id = (self.instance.primary_node, self.new_node, o_port,
11066                     p_minor, new_minor, o_secret)
11067
11068       iv_names[idx] = (dev, dev.children, new_net_id)
11069       logging.debug("Allocated new_minor: %s, new_logical_id: %s", new_minor,
11070                     new_net_id)
11071       drbd_params, _, _ = _ComputeLDParams(constants.DT_DRBD8, self.diskparams)
11072       new_drbd = objects.Disk(dev_type=constants.LD_DRBD8,
11073                               logical_id=new_alone_id,
11074                               children=dev.children,
11075                               size=dev.size,
11076                               params=drbd_params)
11077       try:
11078         _CreateSingleBlockDev(self.lu, self.new_node, self.instance, new_drbd,
11079                               _GetInstanceInfoText(self.instance), False)
11080       except errors.GenericError:
11081         self.cfg.ReleaseDRBDMinors(self.instance.name)
11082         raise
11083
11084     # We have new devices, shutdown the drbd on the old secondary
11085     for idx, dev in enumerate(self.instance.disks):
11086       self.lu.LogInfo("Shutting down drbd for disk/%d on old node" % idx)
11087       self.cfg.SetDiskID(dev, self.target_node)
11088       msg = self.rpc.call_blockdev_shutdown(self.target_node, dev).fail_msg
11089       if msg:
11090         self.lu.LogWarning("Failed to shutdown drbd for disk/%d on old"
11091                            "node: %s" % (idx, msg),
11092                            hint=("Please cleanup this device manually as"
11093                                  " soon as possible"))
11094
11095     self.lu.LogInfo("Detaching primary drbds from the network (=> standalone)")
11096     result = self.rpc.call_drbd_disconnect_net([pnode], self.node_secondary_ip,
11097                                                self.instance.disks)[pnode]
11098
11099     msg = result.fail_msg
11100     if msg:
11101       # detaches didn't succeed (unlikely)
11102       self.cfg.ReleaseDRBDMinors(self.instance.name)
11103       raise errors.OpExecError("Can't detach the disks from the network on"
11104                                " old node: %s" % (msg,))
11105
11106     # if we managed to detach at least one, we update all the disks of
11107     # the instance to point to the new secondary
11108     self.lu.LogInfo("Updating instance configuration")
11109     for dev, _, new_logical_id in iv_names.itervalues():
11110       dev.logical_id = new_logical_id
11111       self.cfg.SetDiskID(dev, self.instance.primary_node)
11112
11113     self.cfg.Update(self.instance, feedback_fn)
11114
11115     # Release all node locks (the configuration has been updated)
11116     _ReleaseLocks(self.lu, locking.LEVEL_NODE)
11117
11118     # and now perform the drbd attach
11119     self.lu.LogInfo("Attaching primary drbds to new secondary"
11120                     " (standalone => connected)")
11121     result = self.rpc.call_drbd_attach_net([self.instance.primary_node,
11122                                             self.new_node],
11123                                            self.node_secondary_ip,
11124                                            self.instance.disks,
11125                                            self.instance.name,
11126                                            False)
11127     for to_node, to_result in result.items():
11128       msg = to_result.fail_msg
11129       if msg:
11130         self.lu.LogWarning("Can't attach drbd disks on node %s: %s",
11131                            to_node, msg,
11132                            hint=("please do a gnt-instance info to see the"
11133                                  " status of disks"))
11134
11135     cstep = itertools.count(5)
11136
11137     if self.early_release:
11138       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11139       self._RemoveOldStorage(self.target_node, iv_names)
11140       # TODO: Check if releasing locks early still makes sense
11141       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
11142     else:
11143       # Release all resource locks except those used by the instance
11144       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES,
11145                     keep=self.node_secondary_ip.keys())
11146
11147     # TODO: Can the instance lock be downgraded here? Take the optional disk
11148     # shutdown in the caller into consideration.
11149
11150     # Wait for sync
11151     # This can fail as the old devices are degraded and _WaitForSync
11152     # does a combined result over all disks, so we don't check its return value
11153     self.lu.LogStep(cstep.next(), steps_total, "Sync devices")
11154     _WaitForSync(self.lu, self.instance)
11155
11156     # Check all devices manually
11157     self._CheckDevices(self.instance.primary_node, iv_names)
11158
11159     # Step: remove old storage
11160     if not self.early_release:
11161       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11162       self._RemoveOldStorage(self.target_node, iv_names)
11163
11164
11165 class LURepairNodeStorage(NoHooksLU):
11166   """Repairs the volume group on a node.
11167
11168   """
11169   REQ_BGL = False
11170
11171   def CheckArguments(self):
11172     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
11173
11174     storage_type = self.op.storage_type
11175
11176     if (constants.SO_FIX_CONSISTENCY not in
11177         constants.VALID_STORAGE_OPERATIONS.get(storage_type, [])):
11178       raise errors.OpPrereqError("Storage units of type '%s' can not be"
11179                                  " repaired" % storage_type,
11180                                  errors.ECODE_INVAL)
11181
11182   def ExpandNames(self):
11183     self.needed_locks = {
11184       locking.LEVEL_NODE: [self.op.node_name],
11185       }
11186
11187   def _CheckFaultyDisks(self, instance, node_name):
11188     """Ensure faulty disks abort the opcode or at least warn."""
11189     try:
11190       if _FindFaultyInstanceDisks(self.cfg, self.rpc, instance,
11191                                   node_name, True):
11192         raise errors.OpPrereqError("Instance '%s' has faulty disks on"
11193                                    " node '%s'" % (instance.name, node_name),
11194                                    errors.ECODE_STATE)
11195     except errors.OpPrereqError, err:
11196       if self.op.ignore_consistency:
11197         self.proc.LogWarning(str(err.args[0]))
11198       else:
11199         raise
11200
11201   def CheckPrereq(self):
11202     """Check prerequisites.
11203
11204     """
11205     # Check whether any instance on this node has faulty disks
11206     for inst in _GetNodeInstances(self.cfg, self.op.node_name):
11207       if inst.admin_state != constants.ADMINST_UP:
11208         continue
11209       check_nodes = set(inst.all_nodes)
11210       check_nodes.discard(self.op.node_name)
11211       for inst_node_name in check_nodes:
11212         self._CheckFaultyDisks(inst, inst_node_name)
11213
11214   def Exec(self, feedback_fn):
11215     feedback_fn("Repairing storage unit '%s' on %s ..." %
11216                 (self.op.name, self.op.node_name))
11217
11218     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
11219     result = self.rpc.call_storage_execute(self.op.node_name,
11220                                            self.op.storage_type, st_args,
11221                                            self.op.name,
11222                                            constants.SO_FIX_CONSISTENCY)
11223     result.Raise("Failed to repair storage unit '%s' on %s" %
11224                  (self.op.name, self.op.node_name))
11225
11226
11227 class LUNodeEvacuate(NoHooksLU):
11228   """Evacuates instances off a list of nodes.
11229
11230   """
11231   REQ_BGL = False
11232
11233   _MODE2IALLOCATOR = {
11234     constants.NODE_EVAC_PRI: constants.IALLOCATOR_NEVAC_PRI,
11235     constants.NODE_EVAC_SEC: constants.IALLOCATOR_NEVAC_SEC,
11236     constants.NODE_EVAC_ALL: constants.IALLOCATOR_NEVAC_ALL,
11237     }
11238   assert frozenset(_MODE2IALLOCATOR.keys()) == constants.NODE_EVAC_MODES
11239   assert (frozenset(_MODE2IALLOCATOR.values()) ==
11240           constants.IALLOCATOR_NEVAC_MODES)
11241
11242   def CheckArguments(self):
11243     _CheckIAllocatorOrNode(self, "iallocator", "remote_node")
11244
11245   def ExpandNames(self):
11246     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
11247
11248     if self.op.remote_node is not None:
11249       self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
11250       assert self.op.remote_node
11251
11252       if self.op.remote_node == self.op.node_name:
11253         raise errors.OpPrereqError("Can not use evacuated node as a new"
11254                                    " secondary node", errors.ECODE_INVAL)
11255
11256       if self.op.mode != constants.NODE_EVAC_SEC:
11257         raise errors.OpPrereqError("Without the use of an iallocator only"
11258                                    " secondary instances can be evacuated",
11259                                    errors.ECODE_INVAL)
11260
11261     # Declare locks
11262     self.share_locks = _ShareAll()
11263     self.needed_locks = {
11264       locking.LEVEL_INSTANCE: [],
11265       locking.LEVEL_NODEGROUP: [],
11266       locking.LEVEL_NODE: [],
11267       }
11268
11269     # Determine nodes (via group) optimistically, needs verification once locks
11270     # have been acquired
11271     self.lock_nodes = self._DetermineNodes()
11272
11273   def _DetermineNodes(self):
11274     """Gets the list of nodes to operate on.
11275
11276     """
11277     if self.op.remote_node is None:
11278       # Iallocator will choose any node(s) in the same group
11279       group_nodes = self.cfg.GetNodeGroupMembersByNodes([self.op.node_name])
11280     else:
11281       group_nodes = frozenset([self.op.remote_node])
11282
11283     # Determine nodes to be locked
11284     return set([self.op.node_name]) | group_nodes
11285
11286   def _DetermineInstances(self):
11287     """Builds list of instances to operate on.
11288
11289     """
11290     assert self.op.mode in constants.NODE_EVAC_MODES
11291
11292     if self.op.mode == constants.NODE_EVAC_PRI:
11293       # Primary instances only
11294       inst_fn = _GetNodePrimaryInstances
11295       assert self.op.remote_node is None, \
11296         "Evacuating primary instances requires iallocator"
11297     elif self.op.mode == constants.NODE_EVAC_SEC:
11298       # Secondary instances only
11299       inst_fn = _GetNodeSecondaryInstances
11300     else:
11301       # All instances
11302       assert self.op.mode == constants.NODE_EVAC_ALL
11303       inst_fn = _GetNodeInstances
11304       # TODO: In 2.6, change the iallocator interface to take an evacuation mode
11305       # per instance
11306       raise errors.OpPrereqError("Due to an issue with the iallocator"
11307                                  " interface it is not possible to evacuate"
11308                                  " all instances at once; specify explicitly"
11309                                  " whether to evacuate primary or secondary"
11310                                  " instances",
11311                                  errors.ECODE_INVAL)
11312
11313     return inst_fn(self.cfg, self.op.node_name)
11314
11315   def DeclareLocks(self, level):
11316     if level == locking.LEVEL_INSTANCE:
11317       # Lock instances optimistically, needs verification once node and group
11318       # locks have been acquired
11319       self.needed_locks[locking.LEVEL_INSTANCE] = \
11320         set(i.name for i in self._DetermineInstances())
11321
11322     elif level == locking.LEVEL_NODEGROUP:
11323       # Lock node groups for all potential target nodes optimistically, needs
11324       # verification once nodes have been acquired
11325       self.needed_locks[locking.LEVEL_NODEGROUP] = \
11326         self.cfg.GetNodeGroupsFromNodes(self.lock_nodes)
11327
11328     elif level == locking.LEVEL_NODE:
11329       self.needed_locks[locking.LEVEL_NODE] = self.lock_nodes
11330
11331   def CheckPrereq(self):
11332     # Verify locks
11333     owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
11334     owned_nodes = self.owned_locks(locking.LEVEL_NODE)
11335     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
11336
11337     need_nodes = self._DetermineNodes()
11338
11339     if not owned_nodes.issuperset(need_nodes):
11340       raise errors.OpPrereqError("Nodes in same group as '%s' changed since"
11341                                  " locks were acquired, current nodes are"
11342                                  " are '%s', used to be '%s'; retry the"
11343                                  " operation" %
11344                                  (self.op.node_name,
11345                                   utils.CommaJoin(need_nodes),
11346                                   utils.CommaJoin(owned_nodes)),
11347                                  errors.ECODE_STATE)
11348
11349     wanted_groups = self.cfg.GetNodeGroupsFromNodes(owned_nodes)
11350     if owned_groups != wanted_groups:
11351       raise errors.OpExecError("Node groups changed since locks were acquired,"
11352                                " current groups are '%s', used to be '%s';"
11353                                " retry the operation" %
11354                                (utils.CommaJoin(wanted_groups),
11355                                 utils.CommaJoin(owned_groups)))
11356
11357     # Determine affected instances
11358     self.instances = self._DetermineInstances()
11359     self.instance_names = [i.name for i in self.instances]
11360
11361     if set(self.instance_names) != owned_instances:
11362       raise errors.OpExecError("Instances on node '%s' changed since locks"
11363                                " were acquired, current instances are '%s',"
11364                                " used to be '%s'; retry the operation" %
11365                                (self.op.node_name,
11366                                 utils.CommaJoin(self.instance_names),
11367                                 utils.CommaJoin(owned_instances)))
11368
11369     if self.instance_names:
11370       self.LogInfo("Evacuating instances from node '%s': %s",
11371                    self.op.node_name,
11372                    utils.CommaJoin(utils.NiceSort(self.instance_names)))
11373     else:
11374       self.LogInfo("No instances to evacuate from node '%s'",
11375                    self.op.node_name)
11376
11377     if self.op.remote_node is not None:
11378       for i in self.instances:
11379         if i.primary_node == self.op.remote_node:
11380           raise errors.OpPrereqError("Node %s is the primary node of"
11381                                      " instance %s, cannot use it as"
11382                                      " secondary" %
11383                                      (self.op.remote_node, i.name),
11384                                      errors.ECODE_INVAL)
11385
11386   def Exec(self, feedback_fn):
11387     assert (self.op.iallocator is not None) ^ (self.op.remote_node is not None)
11388
11389     if not self.instance_names:
11390       # No instances to evacuate
11391       jobs = []
11392
11393     elif self.op.iallocator is not None:
11394       # TODO: Implement relocation to other group
11395       ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_NODE_EVAC,
11396                        evac_mode=self._MODE2IALLOCATOR[self.op.mode],
11397                        instances=list(self.instance_names))
11398
11399       ial.Run(self.op.iallocator)
11400
11401       if not ial.success:
11402         raise errors.OpPrereqError("Can't compute node evacuation using"
11403                                    " iallocator '%s': %s" %
11404                                    (self.op.iallocator, ial.info),
11405                                    errors.ECODE_NORES)
11406
11407       jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, True)
11408
11409     elif self.op.remote_node is not None:
11410       assert self.op.mode == constants.NODE_EVAC_SEC
11411       jobs = [
11412         [opcodes.OpInstanceReplaceDisks(instance_name=instance_name,
11413                                         remote_node=self.op.remote_node,
11414                                         disks=[],
11415                                         mode=constants.REPLACE_DISK_CHG,
11416                                         early_release=self.op.early_release)]
11417         for instance_name in self.instance_names
11418         ]
11419
11420     else:
11421       raise errors.ProgrammerError("No iallocator or remote node")
11422
11423     return ResultWithJobs(jobs)
11424
11425
11426 def _SetOpEarlyRelease(early_release, op):
11427   """Sets C{early_release} flag on opcodes if available.
11428
11429   """
11430   try:
11431     op.early_release = early_release
11432   except AttributeError:
11433     assert not isinstance(op, opcodes.OpInstanceReplaceDisks)
11434
11435   return op
11436
11437
11438 def _NodeEvacDest(use_nodes, group, nodes):
11439   """Returns group or nodes depending on caller's choice.
11440
11441   """
11442   if use_nodes:
11443     return utils.CommaJoin(nodes)
11444   else:
11445     return group
11446
11447
11448 def _LoadNodeEvacResult(lu, alloc_result, early_release, use_nodes):
11449   """Unpacks the result of change-group and node-evacuate iallocator requests.
11450
11451   Iallocator modes L{constants.IALLOCATOR_MODE_NODE_EVAC} and
11452   L{constants.IALLOCATOR_MODE_CHG_GROUP}.
11453
11454   @type lu: L{LogicalUnit}
11455   @param lu: Logical unit instance
11456   @type alloc_result: tuple/list
11457   @param alloc_result: Result from iallocator
11458   @type early_release: bool
11459   @param early_release: Whether to release locks early if possible
11460   @type use_nodes: bool
11461   @param use_nodes: Whether to display node names instead of groups
11462
11463   """
11464   (moved, failed, jobs) = alloc_result
11465
11466   if failed:
11467     failreason = utils.CommaJoin("%s (%s)" % (name, reason)
11468                                  for (name, reason) in failed)
11469     lu.LogWarning("Unable to evacuate instances %s", failreason)
11470     raise errors.OpExecError("Unable to evacuate instances %s" % failreason)
11471
11472   if moved:
11473     lu.LogInfo("Instances to be moved: %s",
11474                utils.CommaJoin("%s (to %s)" %
11475                                (name, _NodeEvacDest(use_nodes, group, nodes))
11476                                for (name, group, nodes) in moved))
11477
11478   return [map(compat.partial(_SetOpEarlyRelease, early_release),
11479               map(opcodes.OpCode.LoadOpCode, ops))
11480           for ops in jobs]
11481
11482
11483 class LUInstanceGrowDisk(LogicalUnit):
11484   """Grow a disk of an instance.
11485
11486   """
11487   HPATH = "disk-grow"
11488   HTYPE = constants.HTYPE_INSTANCE
11489   REQ_BGL = False
11490
11491   def ExpandNames(self):
11492     self._ExpandAndLockInstance()
11493     self.needed_locks[locking.LEVEL_NODE] = []
11494     self.needed_locks[locking.LEVEL_NODE_RES] = []
11495     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
11496     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
11497
11498   def DeclareLocks(self, level):
11499     if level == locking.LEVEL_NODE:
11500       self._LockInstancesNodes()
11501     elif level == locking.LEVEL_NODE_RES:
11502       # Copy node locks
11503       self.needed_locks[locking.LEVEL_NODE_RES] = \
11504         self.needed_locks[locking.LEVEL_NODE][:]
11505
11506   def BuildHooksEnv(self):
11507     """Build hooks env.
11508
11509     This runs on the master, the primary and all the secondaries.
11510
11511     """
11512     env = {
11513       "DISK": self.op.disk,
11514       "AMOUNT": self.op.amount,
11515       }
11516     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
11517     return env
11518
11519   def BuildHooksNodes(self):
11520     """Build hooks nodes.
11521
11522     """
11523     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
11524     return (nl, nl)
11525
11526   def CheckPrereq(self):
11527     """Check prerequisites.
11528
11529     This checks that the instance is in the cluster.
11530
11531     """
11532     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
11533     assert instance is not None, \
11534       "Cannot retrieve locked instance %s" % self.op.instance_name
11535     nodenames = list(instance.all_nodes)
11536     for node in nodenames:
11537       _CheckNodeOnline(self, node)
11538
11539     self.instance = instance
11540
11541     if instance.disk_template not in constants.DTS_GROWABLE:
11542       raise errors.OpPrereqError("Instance's disk layout does not support"
11543                                  " growing", errors.ECODE_INVAL)
11544
11545     self.disk = instance.FindDisk(self.op.disk)
11546
11547     if instance.disk_template not in (constants.DT_FILE,
11548                                       constants.DT_SHARED_FILE,
11549                                       constants.DT_RBD):
11550       # TODO: check the free disk space for file, when that feature will be
11551       # supported
11552       _CheckNodesFreeDiskPerVG(self, nodenames,
11553                                self.disk.ComputeGrowth(self.op.amount))
11554
11555   def Exec(self, feedback_fn):
11556     """Execute disk grow.
11557
11558     """
11559     instance = self.instance
11560     disk = self.disk
11561
11562     assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
11563     assert (self.owned_locks(locking.LEVEL_NODE) ==
11564             self.owned_locks(locking.LEVEL_NODE_RES))
11565
11566     disks_ok, _ = _AssembleInstanceDisks(self, self.instance, disks=[disk])
11567     if not disks_ok:
11568       raise errors.OpExecError("Cannot activate block device to grow")
11569
11570     feedback_fn("Growing disk %s of instance '%s' by %s" %
11571                 (self.op.disk, instance.name,
11572                  utils.FormatUnit(self.op.amount, "h")))
11573
11574     # First run all grow ops in dry-run mode
11575     for node in instance.all_nodes:
11576       self.cfg.SetDiskID(disk, node)
11577       result = self.rpc.call_blockdev_grow(node, disk, self.op.amount, True)
11578       result.Raise("Grow request failed to node %s" % node)
11579
11580     # We know that (as far as we can test) operations across different
11581     # nodes will succeed, time to run it for real
11582     for node in instance.all_nodes:
11583       self.cfg.SetDiskID(disk, node)
11584       result = self.rpc.call_blockdev_grow(node, disk, self.op.amount, False)
11585       result.Raise("Grow request failed to node %s" % node)
11586
11587       # TODO: Rewrite code to work properly
11588       # DRBD goes into sync mode for a short amount of time after executing the
11589       # "resize" command. DRBD 8.x below version 8.0.13 contains a bug whereby
11590       # calling "resize" in sync mode fails. Sleeping for a short amount of
11591       # time is a work-around.
11592       time.sleep(5)
11593
11594     disk.RecordGrow(self.op.amount)
11595     self.cfg.Update(instance, feedback_fn)
11596
11597     # Changes have been recorded, release node lock
11598     _ReleaseLocks(self, locking.LEVEL_NODE)
11599
11600     # Downgrade lock while waiting for sync
11601     self.glm.downgrade(locking.LEVEL_INSTANCE)
11602
11603     if self.op.wait_for_sync:
11604       disk_abort = not _WaitForSync(self, instance, disks=[disk])
11605       if disk_abort:
11606         self.proc.LogWarning("Disk sync-ing has not returned a good"
11607                              " status; please check the instance")
11608       if instance.admin_state != constants.ADMINST_UP:
11609         _SafeShutdownInstanceDisks(self, instance, disks=[disk])
11610     elif instance.admin_state != constants.ADMINST_UP:
11611       self.proc.LogWarning("Not shutting down the disk even if the instance is"
11612                            " not supposed to be running because no wait for"
11613                            " sync mode was requested")
11614
11615     assert self.owned_locks(locking.LEVEL_NODE_RES)
11616     assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
11617
11618
11619 class LUInstanceQueryData(NoHooksLU):
11620   """Query runtime instance data.
11621
11622   """
11623   REQ_BGL = False
11624
11625   def ExpandNames(self):
11626     self.needed_locks = {}
11627
11628     # Use locking if requested or when non-static information is wanted
11629     if not (self.op.static or self.op.use_locking):
11630       self.LogWarning("Non-static data requested, locks need to be acquired")
11631       self.op.use_locking = True
11632
11633     if self.op.instances or not self.op.use_locking:
11634       # Expand instance names right here
11635       self.wanted_names = _GetWantedInstances(self, self.op.instances)
11636     else:
11637       # Will use acquired locks
11638       self.wanted_names = None
11639
11640     if self.op.use_locking:
11641       self.share_locks = _ShareAll()
11642
11643       if self.wanted_names is None:
11644         self.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET
11645       else:
11646         self.needed_locks[locking.LEVEL_INSTANCE] = self.wanted_names
11647
11648       self.needed_locks[locking.LEVEL_NODE] = []
11649       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
11650
11651   def DeclareLocks(self, level):
11652     if self.op.use_locking and level == locking.LEVEL_NODE:
11653       self._LockInstancesNodes()
11654
11655   def CheckPrereq(self):
11656     """Check prerequisites.
11657
11658     This only checks the optional instance list against the existing names.
11659
11660     """
11661     if self.wanted_names is None:
11662       assert self.op.use_locking, "Locking was not used"
11663       self.wanted_names = self.owned_locks(locking.LEVEL_INSTANCE)
11664
11665     self.wanted_instances = \
11666         map(compat.snd, self.cfg.GetMultiInstanceInfo(self.wanted_names))
11667
11668   def _ComputeBlockdevStatus(self, node, instance_name, dev):
11669     """Returns the status of a block device
11670
11671     """
11672     if self.op.static or not node:
11673       return None
11674
11675     self.cfg.SetDiskID(dev, node)
11676
11677     result = self.rpc.call_blockdev_find(node, dev)
11678     if result.offline:
11679       return None
11680
11681     result.Raise("Can't compute disk status for %s" % instance_name)
11682
11683     status = result.payload
11684     if status is None:
11685       return None
11686
11687     return (status.dev_path, status.major, status.minor,
11688             status.sync_percent, status.estimated_time,
11689             status.is_degraded, status.ldisk_status)
11690
11691   def _ComputeDiskStatus(self, instance, snode, dev):
11692     """Compute block device status.
11693
11694     """
11695     if dev.dev_type in constants.LDS_DRBD:
11696       # we change the snode then (otherwise we use the one passed in)
11697       if dev.logical_id[0] == instance.primary_node:
11698         snode = dev.logical_id[1]
11699       else:
11700         snode = dev.logical_id[0]
11701
11702     dev_pstatus = self._ComputeBlockdevStatus(instance.primary_node,
11703                                               instance.name, dev)
11704     dev_sstatus = self._ComputeBlockdevStatus(snode, instance.name, dev)
11705
11706     if dev.children:
11707       dev_children = map(compat.partial(self._ComputeDiskStatus,
11708                                         instance, snode),
11709                          dev.children)
11710     else:
11711       dev_children = []
11712
11713     return {
11714       "iv_name": dev.iv_name,
11715       "dev_type": dev.dev_type,
11716       "logical_id": dev.logical_id,
11717       "physical_id": dev.physical_id,
11718       "pstatus": dev_pstatus,
11719       "sstatus": dev_sstatus,
11720       "children": dev_children,
11721       "mode": dev.mode,
11722       "size": dev.size,
11723       }
11724
11725   def Exec(self, feedback_fn):
11726     """Gather and return data"""
11727     result = {}
11728
11729     cluster = self.cfg.GetClusterInfo()
11730
11731     pri_nodes = self.cfg.GetMultiNodeInfo(i.primary_node
11732                                           for i in self.wanted_instances)
11733     for instance, (_, pnode) in zip(self.wanted_instances, pri_nodes):
11734       if self.op.static or pnode.offline:
11735         remote_state = None
11736         if pnode.offline:
11737           self.LogWarning("Primary node %s is marked offline, returning static"
11738                           " information only for instance %s" %
11739                           (pnode.name, instance.name))
11740       else:
11741         remote_info = self.rpc.call_instance_info(instance.primary_node,
11742                                                   instance.name,
11743                                                   instance.hypervisor)
11744         remote_info.Raise("Error checking node %s" % instance.primary_node)
11745         remote_info = remote_info.payload
11746         if remote_info and "state" in remote_info:
11747           remote_state = "up"
11748         else:
11749           if instance.admin_state == constants.ADMINST_UP:
11750             remote_state = "down"
11751           else:
11752             remote_state = instance.admin_state
11753
11754       disks = map(compat.partial(self._ComputeDiskStatus, instance, None),
11755                   instance.disks)
11756
11757       result[instance.name] = {
11758         "name": instance.name,
11759         "config_state": instance.admin_state,
11760         "run_state": remote_state,
11761         "pnode": instance.primary_node,
11762         "snodes": instance.secondary_nodes,
11763         "os": instance.os,
11764         # this happens to be the same format used for hooks
11765         "nics": _NICListToTuple(self, instance.nics),
11766         "disk_template": instance.disk_template,
11767         "disks": disks,
11768         "hypervisor": instance.hypervisor,
11769         "network_port": instance.network_port,
11770         "hv_instance": instance.hvparams,
11771         "hv_actual": cluster.FillHV(instance, skip_globals=True),
11772         "be_instance": instance.beparams,
11773         "be_actual": cluster.FillBE(instance),
11774         "os_instance": instance.osparams,
11775         "os_actual": cluster.SimpleFillOS(instance.os, instance.osparams),
11776         "serial_no": instance.serial_no,
11777         "mtime": instance.mtime,
11778         "ctime": instance.ctime,
11779         "uuid": instance.uuid,
11780         }
11781
11782     return result
11783
11784
11785 class LUInstanceSetParams(LogicalUnit):
11786   """Modifies an instances's parameters.
11787
11788   """
11789   HPATH = "instance-modify"
11790   HTYPE = constants.HTYPE_INSTANCE
11791   REQ_BGL = False
11792
11793   def CheckArguments(self):
11794     if not (self.op.nics or self.op.disks or self.op.disk_template or
11795             self.op.hvparams or self.op.beparams or self.op.os_name or
11796             self.op.online_inst or self.op.offline_inst or
11797             self.op.runtime_mem):
11798       raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
11799
11800     if self.op.hvparams:
11801       _CheckGlobalHvParams(self.op.hvparams)
11802
11803     # Disk validation
11804     disk_addremove = 0
11805     for disk_op, disk_dict in self.op.disks:
11806       utils.ForceDictType(disk_dict, constants.IDISK_PARAMS_TYPES)
11807       if disk_op == constants.DDM_REMOVE:
11808         disk_addremove += 1
11809         continue
11810       elif disk_op == constants.DDM_ADD:
11811         disk_addremove += 1
11812       else:
11813         if not isinstance(disk_op, int):
11814           raise errors.OpPrereqError("Invalid disk index", errors.ECODE_INVAL)
11815         if not isinstance(disk_dict, dict):
11816           msg = "Invalid disk value: expected dict, got '%s'" % disk_dict
11817           raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
11818
11819       if disk_op == constants.DDM_ADD:
11820         mode = disk_dict.setdefault(constants.IDISK_MODE, constants.DISK_RDWR)
11821         if mode not in constants.DISK_ACCESS_SET:
11822           raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
11823                                      errors.ECODE_INVAL)
11824         size = disk_dict.get(constants.IDISK_SIZE, None)
11825         if size is None:
11826           raise errors.OpPrereqError("Required disk parameter size missing",
11827                                      errors.ECODE_INVAL)
11828         try:
11829           size = int(size)
11830         except (TypeError, ValueError), err:
11831           raise errors.OpPrereqError("Invalid disk size parameter: %s" %
11832                                      str(err), errors.ECODE_INVAL)
11833         disk_dict[constants.IDISK_SIZE] = size
11834       else:
11835         # modification of disk
11836         if constants.IDISK_SIZE in disk_dict:
11837           raise errors.OpPrereqError("Disk size change not possible, use"
11838                                      " grow-disk", errors.ECODE_INVAL)
11839
11840     if disk_addremove > 1:
11841       raise errors.OpPrereqError("Only one disk add or remove operation"
11842                                  " supported at a time", errors.ECODE_INVAL)
11843
11844     if self.op.disks and self.op.disk_template is not None:
11845       raise errors.OpPrereqError("Disk template conversion and other disk"
11846                                  " changes not supported at the same time",
11847                                  errors.ECODE_INVAL)
11848
11849     if (self.op.disk_template and
11850         self.op.disk_template in constants.DTS_INT_MIRROR and
11851         self.op.remote_node is None):
11852       raise errors.OpPrereqError("Changing the disk template to a mirrored"
11853                                  " one requires specifying a secondary node",
11854                                  errors.ECODE_INVAL)
11855
11856     # NIC validation
11857     nic_addremove = 0
11858     for nic_op, nic_dict in self.op.nics:
11859       utils.ForceDictType(nic_dict, constants.INIC_PARAMS_TYPES)
11860       if nic_op == constants.DDM_REMOVE:
11861         nic_addremove += 1
11862         continue
11863       elif nic_op == constants.DDM_ADD:
11864         nic_addremove += 1
11865       else:
11866         if not isinstance(nic_op, int):
11867           raise errors.OpPrereqError("Invalid nic index", errors.ECODE_INVAL)
11868         if not isinstance(nic_dict, dict):
11869           msg = "Invalid nic value: expected dict, got '%s'" % nic_dict
11870           raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
11871
11872       # nic_dict should be a dict
11873       nic_ip = nic_dict.get(constants.INIC_IP, None)
11874       if nic_ip is not None:
11875         if nic_ip.lower() == constants.VALUE_NONE:
11876           nic_dict[constants.INIC_IP] = None
11877         else:
11878           if not netutils.IPAddress.IsValid(nic_ip):
11879             raise errors.OpPrereqError("Invalid IP address '%s'" % nic_ip,
11880                                        errors.ECODE_INVAL)
11881
11882       nic_bridge = nic_dict.get("bridge", None)
11883       nic_link = nic_dict.get(constants.INIC_LINK, None)
11884       if nic_bridge and nic_link:
11885         raise errors.OpPrereqError("Cannot pass 'bridge' and 'link'"
11886                                    " at the same time", errors.ECODE_INVAL)
11887       elif nic_bridge and nic_bridge.lower() == constants.VALUE_NONE:
11888         nic_dict["bridge"] = None
11889       elif nic_link and nic_link.lower() == constants.VALUE_NONE:
11890         nic_dict[constants.INIC_LINK] = None
11891
11892       if nic_op == constants.DDM_ADD:
11893         nic_mac = nic_dict.get(constants.INIC_MAC, None)
11894         if nic_mac is None:
11895           nic_dict[constants.INIC_MAC] = constants.VALUE_AUTO
11896
11897       if constants.INIC_MAC in nic_dict:
11898         nic_mac = nic_dict[constants.INIC_MAC]
11899         if nic_mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
11900           nic_mac = utils.NormalizeAndValidateMac(nic_mac)
11901
11902         if nic_op != constants.DDM_ADD and nic_mac == constants.VALUE_AUTO:
11903           raise errors.OpPrereqError("'auto' is not a valid MAC address when"
11904                                      " modifying an existing nic",
11905                                      errors.ECODE_INVAL)
11906
11907     if nic_addremove > 1:
11908       raise errors.OpPrereqError("Only one NIC add or remove operation"
11909                                  " supported at a time", errors.ECODE_INVAL)
11910
11911   def ExpandNames(self):
11912     self._ExpandAndLockInstance()
11913     # Can't even acquire node locks in shared mode as upcoming changes in
11914     # Ganeti 2.6 will start to modify the node object on disk conversion
11915     self.needed_locks[locking.LEVEL_NODE] = []
11916     self.needed_locks[locking.LEVEL_NODE_RES] = []
11917     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
11918
11919   def DeclareLocks(self, level):
11920     if level == locking.LEVEL_NODE:
11921       self._LockInstancesNodes()
11922       if self.op.disk_template and self.op.remote_node:
11923         self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
11924         self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
11925     elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
11926       # Copy node locks
11927       self.needed_locks[locking.LEVEL_NODE_RES] = \
11928         self.needed_locks[locking.LEVEL_NODE][:]
11929
11930   def BuildHooksEnv(self):
11931     """Build hooks env.
11932
11933     This runs on the master, primary and secondaries.
11934
11935     """
11936     args = dict()
11937     if constants.BE_MINMEM in self.be_new:
11938       args["minmem"] = self.be_new[constants.BE_MINMEM]
11939     if constants.BE_MAXMEM in self.be_new:
11940       args["maxmem"] = self.be_new[constants.BE_MAXMEM]
11941     if constants.BE_VCPUS in self.be_new:
11942       args["vcpus"] = self.be_new[constants.BE_VCPUS]
11943     # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
11944     # information at all.
11945     if self.op.nics:
11946       args["nics"] = []
11947       nic_override = dict(self.op.nics)
11948       for idx, nic in enumerate(self.instance.nics):
11949         if idx in nic_override:
11950           this_nic_override = nic_override[idx]
11951         else:
11952           this_nic_override = {}
11953         if constants.INIC_IP in this_nic_override:
11954           ip = this_nic_override[constants.INIC_IP]
11955         else:
11956           ip = nic.ip
11957         if constants.INIC_MAC in this_nic_override:
11958           mac = this_nic_override[constants.INIC_MAC]
11959         else:
11960           mac = nic.mac
11961         if idx in self.nic_pnew:
11962           nicparams = self.nic_pnew[idx]
11963         else:
11964           nicparams = self.cluster.SimpleFillNIC(nic.nicparams)
11965         mode = nicparams[constants.NIC_MODE]
11966         link = nicparams[constants.NIC_LINK]
11967         args["nics"].append((ip, mac, mode, link))
11968       if constants.DDM_ADD in nic_override:
11969         ip = nic_override[constants.DDM_ADD].get(constants.INIC_IP, None)
11970         mac = nic_override[constants.DDM_ADD][constants.INIC_MAC]
11971         nicparams = self.nic_pnew[constants.DDM_ADD]
11972         mode = nicparams[constants.NIC_MODE]
11973         link = nicparams[constants.NIC_LINK]
11974         args["nics"].append((ip, mac, mode, link))
11975       elif constants.DDM_REMOVE in nic_override:
11976         del args["nics"][-1]
11977
11978     env = _BuildInstanceHookEnvByObject(self, self.instance, override=args)
11979     if self.op.disk_template:
11980       env["NEW_DISK_TEMPLATE"] = self.op.disk_template
11981     if self.op.runtime_mem:
11982       env["RUNTIME_MEMORY"] = self.op.runtime_mem
11983
11984     return env
11985
11986   def BuildHooksNodes(self):
11987     """Build hooks nodes.
11988
11989     """
11990     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
11991     return (nl, nl)
11992
11993   def CheckPrereq(self):
11994     """Check prerequisites.
11995
11996     This only checks the instance list against the existing names.
11997
11998     """
11999     # checking the new params on the primary/secondary nodes
12000
12001     instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
12002     cluster = self.cluster = self.cfg.GetClusterInfo()
12003     assert self.instance is not None, \
12004       "Cannot retrieve locked instance %s" % self.op.instance_name
12005     pnode = instance.primary_node
12006     nodelist = list(instance.all_nodes)
12007     pnode_info = self.cfg.GetNodeInfo(pnode)
12008     self.diskparams = self.cfg.GetNodeGroup(pnode_info.group).diskparams
12009
12010     # OS change
12011     if self.op.os_name and not self.op.force:
12012       _CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
12013                       self.op.force_variant)
12014       instance_os = self.op.os_name
12015     else:
12016       instance_os = instance.os
12017
12018     if self.op.disk_template:
12019       if instance.disk_template == self.op.disk_template:
12020         raise errors.OpPrereqError("Instance already has disk template %s" %
12021                                    instance.disk_template, errors.ECODE_INVAL)
12022
12023       if (instance.disk_template,
12024           self.op.disk_template) not in self._DISK_CONVERSIONS:
12025         raise errors.OpPrereqError("Unsupported disk template conversion from"
12026                                    " %s to %s" % (instance.disk_template,
12027                                                   self.op.disk_template),
12028                                    errors.ECODE_INVAL)
12029       _CheckInstanceState(self, instance, INSTANCE_DOWN,
12030                           msg="cannot change disk template")
12031       if self.op.disk_template in constants.DTS_INT_MIRROR:
12032         if self.op.remote_node == pnode:
12033           raise errors.OpPrereqError("Given new secondary node %s is the same"
12034                                      " as the primary node of the instance" %
12035                                      self.op.remote_node, errors.ECODE_STATE)
12036         _CheckNodeOnline(self, self.op.remote_node)
12037         _CheckNodeNotDrained(self, self.op.remote_node)
12038         # FIXME: here we assume that the old instance type is DT_PLAIN
12039         assert instance.disk_template == constants.DT_PLAIN
12040         disks = [{constants.IDISK_SIZE: d.size,
12041                   constants.IDISK_VG: d.logical_id[0]}
12042                  for d in instance.disks]
12043         required = _ComputeDiskSizePerVG(self.op.disk_template, disks)
12044         _CheckNodesFreeDiskPerVG(self, [self.op.remote_node], required)
12045
12046         snode_info = self.cfg.GetNodeInfo(self.op.remote_node)
12047         snode_group = self.cfg.GetNodeGroup(snode_info.group)
12048         ipolicy = _CalculateGroupIPolicy(cluster, snode_group)
12049         _CheckTargetNodeIPolicy(self, ipolicy, instance, snode_info,
12050                                 ignore=self.op.ignore_ipolicy)
12051         if pnode_info.group != snode_info.group:
12052           self.LogWarning("The primary and secondary nodes are in two"
12053                           " different node groups; the disk parameters"
12054                           " from the first disk's node group will be"
12055                           " used")
12056
12057     # hvparams processing
12058     if self.op.hvparams:
12059       hv_type = instance.hypervisor
12060       i_hvdict = _GetUpdatedParams(instance.hvparams, self.op.hvparams)
12061       utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
12062       hv_new = cluster.SimpleFillHV(hv_type, instance.os, i_hvdict)
12063
12064       # local check
12065       hypervisor.GetHypervisor(hv_type).CheckParameterSyntax(hv_new)
12066       _CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
12067       self.hv_proposed = self.hv_new = hv_new # the new actual values
12068       self.hv_inst = i_hvdict # the new dict (without defaults)
12069     else:
12070       self.hv_proposed = cluster.SimpleFillHV(instance.hypervisor, instance.os,
12071                                               instance.hvparams)
12072       self.hv_new = self.hv_inst = {}
12073
12074     # beparams processing
12075     if self.op.beparams:
12076       i_bedict = _GetUpdatedParams(instance.beparams, self.op.beparams,
12077                                    use_none=True)
12078       objects.UpgradeBeParams(i_bedict)
12079       utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
12080       be_new = cluster.SimpleFillBE(i_bedict)
12081       self.be_proposed = self.be_new = be_new # the new actual values
12082       self.be_inst = i_bedict # the new dict (without defaults)
12083     else:
12084       self.be_new = self.be_inst = {}
12085       self.be_proposed = cluster.SimpleFillBE(instance.beparams)
12086     be_old = cluster.FillBE(instance)
12087
12088     # CPU param validation -- checking every time a paramtere is
12089     # changed to cover all cases where either CPU mask or vcpus have
12090     # changed
12091     if (constants.BE_VCPUS in self.be_proposed and
12092         constants.HV_CPU_MASK in self.hv_proposed):
12093       cpu_list = \
12094         utils.ParseMultiCpuMask(self.hv_proposed[constants.HV_CPU_MASK])
12095       # Verify mask is consistent with number of vCPUs. Can skip this
12096       # test if only 1 entry in the CPU mask, which means same mask
12097       # is applied to all vCPUs.
12098       if (len(cpu_list) > 1 and
12099           len(cpu_list) != self.be_proposed[constants.BE_VCPUS]):
12100         raise errors.OpPrereqError("Number of vCPUs [%d] does not match the"
12101                                    " CPU mask [%s]" %
12102                                    (self.be_proposed[constants.BE_VCPUS],
12103                                     self.hv_proposed[constants.HV_CPU_MASK]),
12104                                    errors.ECODE_INVAL)
12105
12106       # Only perform this test if a new CPU mask is given
12107       if constants.HV_CPU_MASK in self.hv_new:
12108         # Calculate the largest CPU number requested
12109         max_requested_cpu = max(map(max, cpu_list))
12110         # Check that all of the instance's nodes have enough physical CPUs to
12111         # satisfy the requested CPU mask
12112         _CheckNodesPhysicalCPUs(self, instance.all_nodes,
12113                                 max_requested_cpu + 1, instance.hypervisor)
12114
12115     # osparams processing
12116     if self.op.osparams:
12117       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
12118       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
12119       self.os_inst = i_osdict # the new dict (without defaults)
12120     else:
12121       self.os_inst = {}
12122
12123     self.warn = []
12124
12125     #TODO(dynmem): do the appropriate check involving MINMEM
12126     if (constants.BE_MAXMEM in self.op.beparams and not self.op.force and
12127         be_new[constants.BE_MAXMEM] > be_old[constants.BE_MAXMEM]):
12128       mem_check_list = [pnode]
12129       if be_new[constants.BE_AUTO_BALANCE]:
12130         # either we changed auto_balance to yes or it was from before
12131         mem_check_list.extend(instance.secondary_nodes)
12132       instance_info = self.rpc.call_instance_info(pnode, instance.name,
12133                                                   instance.hypervisor)
12134       nodeinfo = self.rpc.call_node_info(mem_check_list, None,
12135                                          [instance.hypervisor])
12136       pninfo = nodeinfo[pnode]
12137       msg = pninfo.fail_msg
12138       if msg:
12139         # Assume the primary node is unreachable and go ahead
12140         self.warn.append("Can't get info from primary node %s: %s" %
12141                          (pnode, msg))
12142       else:
12143         (_, _, (pnhvinfo, )) = pninfo.payload
12144         if not isinstance(pnhvinfo.get("memory_free", None), int):
12145           self.warn.append("Node data from primary node %s doesn't contain"
12146                            " free memory information" % pnode)
12147         elif instance_info.fail_msg:
12148           self.warn.append("Can't get instance runtime information: %s" %
12149                           instance_info.fail_msg)
12150         else:
12151           if instance_info.payload:
12152             current_mem = int(instance_info.payload["memory"])
12153           else:
12154             # Assume instance not running
12155             # (there is a slight race condition here, but it's not very
12156             # probable, and we have no other way to check)
12157             # TODO: Describe race condition
12158             current_mem = 0
12159           #TODO(dynmem): do the appropriate check involving MINMEM
12160           miss_mem = (be_new[constants.BE_MAXMEM] - current_mem -
12161                       pnhvinfo["memory_free"])
12162           if miss_mem > 0:
12163             raise errors.OpPrereqError("This change will prevent the instance"
12164                                        " from starting, due to %d MB of memory"
12165                                        " missing on its primary node" %
12166                                        miss_mem,
12167                                        errors.ECODE_NORES)
12168
12169       if be_new[constants.BE_AUTO_BALANCE]:
12170         for node, nres in nodeinfo.items():
12171           if node not in instance.secondary_nodes:
12172             continue
12173           nres.Raise("Can't get info from secondary node %s" % node,
12174                      prereq=True, ecode=errors.ECODE_STATE)
12175           (_, _, (nhvinfo, )) = nres.payload
12176           if not isinstance(nhvinfo.get("memory_free", None), int):
12177             raise errors.OpPrereqError("Secondary node %s didn't return free"
12178                                        " memory information" % node,
12179                                        errors.ECODE_STATE)
12180           #TODO(dynmem): do the appropriate check involving MINMEM
12181           elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
12182             raise errors.OpPrereqError("This change will prevent the instance"
12183                                        " from failover to its secondary node"
12184                                        " %s, due to not enough memory" % node,
12185                                        errors.ECODE_STATE)
12186
12187     if self.op.runtime_mem:
12188       remote_info = self.rpc.call_instance_info(instance.primary_node,
12189                                                 instance.name,
12190                                                 instance.hypervisor)
12191       remote_info.Raise("Error checking node %s" % instance.primary_node)
12192       if not remote_info.payload: # not running already
12193         raise errors.OpPrereqError("Instance %s is not running" % instance.name,
12194                                    errors.ECODE_STATE)
12195
12196       current_memory = remote_info.payload["memory"]
12197       if (not self.op.force and
12198            (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or
12199             self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])):
12200         raise errors.OpPrereqError("Instance %s must have memory between %d"
12201                                    " and %d MB of memory unless --force is"
12202                                    " given" % (instance.name,
12203                                     self.be_proposed[constants.BE_MINMEM],
12204                                     self.be_proposed[constants.BE_MAXMEM]),
12205                                    errors.ECODE_INVAL)
12206
12207       if self.op.runtime_mem > current_memory:
12208         _CheckNodeFreeMemory(self, instance.primary_node,
12209                              "ballooning memory for instance %s" %
12210                              instance.name,
12211                              self.op.memory - current_memory,
12212                              instance.hypervisor)
12213
12214     # NIC processing
12215     self.nic_pnew = {}
12216     self.nic_pinst = {}
12217     for nic_op, nic_dict in self.op.nics:
12218       if nic_op == constants.DDM_REMOVE:
12219         if not instance.nics:
12220           raise errors.OpPrereqError("Instance has no NICs, cannot remove",
12221                                      errors.ECODE_INVAL)
12222         continue
12223       if nic_op != constants.DDM_ADD:
12224         # an existing nic
12225         if not instance.nics:
12226           raise errors.OpPrereqError("Invalid NIC index %s, instance has"
12227                                      " no NICs" % nic_op,
12228                                      errors.ECODE_INVAL)
12229         if nic_op < 0 or nic_op >= len(instance.nics):
12230           raise errors.OpPrereqError("Invalid NIC index %s, valid values"
12231                                      " are 0 to %d" %
12232                                      (nic_op, len(instance.nics) - 1),
12233                                      errors.ECODE_INVAL)
12234         old_nic_params = instance.nics[nic_op].nicparams
12235         old_nic_ip = instance.nics[nic_op].ip
12236       else:
12237         old_nic_params = {}
12238         old_nic_ip = None
12239
12240       update_params_dict = dict([(key, nic_dict[key])
12241                                  for key in constants.NICS_PARAMETERS
12242                                  if key in nic_dict])
12243
12244       if "bridge" in nic_dict:
12245         update_params_dict[constants.NIC_LINK] = nic_dict["bridge"]
12246
12247       new_nic_params = _GetUpdatedParams(old_nic_params,
12248                                          update_params_dict)
12249       utils.ForceDictType(new_nic_params, constants.NICS_PARAMETER_TYPES)
12250       new_filled_nic_params = cluster.SimpleFillNIC(new_nic_params)
12251       objects.NIC.CheckParameterSyntax(new_filled_nic_params)
12252       self.nic_pinst[nic_op] = new_nic_params
12253       self.nic_pnew[nic_op] = new_filled_nic_params
12254       new_nic_mode = new_filled_nic_params[constants.NIC_MODE]
12255
12256       if new_nic_mode == constants.NIC_MODE_BRIDGED:
12257         nic_bridge = new_filled_nic_params[constants.NIC_LINK]
12258         msg = self.rpc.call_bridges_exist(pnode, [nic_bridge]).fail_msg
12259         if msg:
12260           msg = "Error checking bridges on node %s: %s" % (pnode, msg)
12261           if self.op.force:
12262             self.warn.append(msg)
12263           else:
12264             raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
12265       if new_nic_mode == constants.NIC_MODE_ROUTED:
12266         if constants.INIC_IP in nic_dict:
12267           nic_ip = nic_dict[constants.INIC_IP]
12268         else:
12269           nic_ip = old_nic_ip
12270         if nic_ip is None:
12271           raise errors.OpPrereqError("Cannot set the nic ip to None"
12272                                      " on a routed nic", errors.ECODE_INVAL)
12273       if constants.INIC_MAC in nic_dict:
12274         nic_mac = nic_dict[constants.INIC_MAC]
12275         if nic_mac is None:
12276           raise errors.OpPrereqError("Cannot set the nic mac to None",
12277                                      errors.ECODE_INVAL)
12278         elif nic_mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
12279           # otherwise generate the mac
12280           nic_dict[constants.INIC_MAC] = \
12281             self.cfg.GenerateMAC(self.proc.GetECId())
12282         else:
12283           # or validate/reserve the current one
12284           try:
12285             self.cfg.ReserveMAC(nic_mac, self.proc.GetECId())
12286           except errors.ReservationError:
12287             raise errors.OpPrereqError("MAC address %s already in use"
12288                                        " in cluster" % nic_mac,
12289                                        errors.ECODE_NOTUNIQUE)
12290
12291     # DISK processing
12292     if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
12293       raise errors.OpPrereqError("Disk operations not supported for"
12294                                  " diskless instances",
12295                                  errors.ECODE_INVAL)
12296     for disk_op, _ in self.op.disks:
12297       if disk_op == constants.DDM_REMOVE:
12298         if len(instance.disks) == 1:
12299           raise errors.OpPrereqError("Cannot remove the last disk of"
12300                                      " an instance", errors.ECODE_INVAL)
12301         _CheckInstanceState(self, instance, INSTANCE_DOWN,
12302                             msg="cannot remove disks")
12303
12304       if (disk_op == constants.DDM_ADD and
12305           len(instance.disks) >= constants.MAX_DISKS):
12306         raise errors.OpPrereqError("Instance has too many disks (%d), cannot"
12307                                    " add more" % constants.MAX_DISKS,
12308                                    errors.ECODE_STATE)
12309       if disk_op not in (constants.DDM_ADD, constants.DDM_REMOVE):
12310         # an existing disk
12311         if disk_op < 0 or disk_op >= len(instance.disks):
12312           raise errors.OpPrereqError("Invalid disk index %s, valid values"
12313                                      " are 0 to %d" %
12314                                      (disk_op, len(instance.disks)),
12315                                      errors.ECODE_INVAL)
12316
12317     # disabling the instance
12318     if self.op.offline_inst:
12319       _CheckInstanceState(self, instance, INSTANCE_DOWN,
12320                           msg="cannot change instance state to offline")
12321
12322     # enabling the instance
12323     if self.op.online_inst:
12324       _CheckInstanceState(self, instance, INSTANCE_OFFLINE,
12325                           msg="cannot make instance go online")
12326
12327   def _ConvertPlainToDrbd(self, feedback_fn):
12328     """Converts an instance from plain to drbd.
12329
12330     """
12331     feedback_fn("Converting template to drbd")
12332     instance = self.instance
12333     pnode = instance.primary_node
12334     snode = self.op.remote_node
12335
12336     assert instance.disk_template == constants.DT_PLAIN
12337
12338     # create a fake disk info for _GenerateDiskTemplate
12339     disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
12340                   constants.IDISK_VG: d.logical_id[0]}
12341                  for d in instance.disks]
12342     new_disks = _GenerateDiskTemplate(self, self.op.disk_template,
12343                                       instance.name, pnode, [snode],
12344                                       disk_info, None, None, 0, feedback_fn,
12345                                       self.diskparams)
12346     info = _GetInstanceInfoText(instance)
12347     feedback_fn("Creating aditional volumes...")
12348     # first, create the missing data and meta devices
12349     for disk in new_disks:
12350       # unfortunately this is... not too nice
12351       _CreateSingleBlockDev(self, pnode, instance, disk.children[1],
12352                             info, True)
12353       for child in disk.children:
12354         _CreateSingleBlockDev(self, snode, instance, child, info, True)
12355     # at this stage, all new LVs have been created, we can rename the
12356     # old ones
12357     feedback_fn("Renaming original volumes...")
12358     rename_list = [(o, n.children[0].logical_id)
12359                    for (o, n) in zip(instance.disks, new_disks)]
12360     result = self.rpc.call_blockdev_rename(pnode, rename_list)
12361     result.Raise("Failed to rename original LVs")
12362
12363     feedback_fn("Initializing DRBD devices...")
12364     # all child devices are in place, we can now create the DRBD devices
12365     for disk in new_disks:
12366       for node in [pnode, snode]:
12367         f_create = node == pnode
12368         _CreateSingleBlockDev(self, node, instance, disk, info, f_create)
12369
12370     # at this point, the instance has been modified
12371     instance.disk_template = constants.DT_DRBD8
12372     instance.disks = new_disks
12373     self.cfg.Update(instance, feedback_fn)
12374
12375     # Release node locks while waiting for sync
12376     _ReleaseLocks(self, locking.LEVEL_NODE)
12377
12378     # disks are created, waiting for sync
12379     disk_abort = not _WaitForSync(self, instance,
12380                                   oneshot=not self.op.wait_for_sync)
12381     if disk_abort:
12382       raise errors.OpExecError("There are some degraded disks for"
12383                                " this instance, please cleanup manually")
12384
12385     # Node resource locks will be released by caller
12386
12387   def _ConvertDrbdToPlain(self, feedback_fn):
12388     """Converts an instance from drbd to plain.
12389
12390     """
12391     instance = self.instance
12392
12393     assert len(instance.secondary_nodes) == 1
12394     assert instance.disk_template == constants.DT_DRBD8
12395
12396     pnode = instance.primary_node
12397     snode = instance.secondary_nodes[0]
12398     feedback_fn("Converting template to plain")
12399
12400     old_disks = instance.disks
12401     new_disks = [d.children[0] for d in old_disks]
12402
12403     # copy over size and mode
12404     for parent, child in zip(old_disks, new_disks):
12405       child.size = parent.size
12406       child.mode = parent.mode
12407
12408     # update instance structure
12409     instance.disks = new_disks
12410     instance.disk_template = constants.DT_PLAIN
12411     self.cfg.Update(instance, feedback_fn)
12412
12413     # Release locks in case removing disks takes a while
12414     _ReleaseLocks(self, locking.LEVEL_NODE)
12415
12416     feedback_fn("Removing volumes on the secondary node...")
12417     for disk in old_disks:
12418       self.cfg.SetDiskID(disk, snode)
12419       msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
12420       if msg:
12421         self.LogWarning("Could not remove block device %s on node %s,"
12422                         " continuing anyway: %s", disk.iv_name, snode, msg)
12423
12424     feedback_fn("Removing unneeded volumes on the primary node...")
12425     for idx, disk in enumerate(old_disks):
12426       meta = disk.children[1]
12427       self.cfg.SetDiskID(meta, pnode)
12428       msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
12429       if msg:
12430         self.LogWarning("Could not remove metadata for disk %d on node %s,"
12431                         " continuing anyway: %s", idx, pnode, msg)
12432
12433     # this is a DRBD disk, return its port to the pool
12434     for disk in old_disks:
12435       tcp_port = disk.logical_id[2]
12436       self.cfg.AddTcpUdpPort(tcp_port)
12437
12438     # Node resource locks will be released by caller
12439
12440   def Exec(self, feedback_fn):
12441     """Modifies an instance.
12442
12443     All parameters take effect only at the next restart of the instance.
12444
12445     """
12446     # Process here the warnings from CheckPrereq, as we don't have a
12447     # feedback_fn there.
12448     for warn in self.warn:
12449       feedback_fn("WARNING: %s" % warn)
12450
12451     assert ((self.op.disk_template is None) ^
12452             bool(self.owned_locks(locking.LEVEL_NODE_RES))), \
12453       "Not owning any node resource locks"
12454
12455     result = []
12456     instance = self.instance
12457
12458     # runtime memory
12459     if self.op.runtime_mem:
12460       rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
12461                                                      instance,
12462                                                      self.op.runtime_mem)
12463       rpcres.Raise("Cannot modify instance runtime memory")
12464       result.append(("runtime_memory", self.op.runtime_mem))
12465
12466     # disk changes
12467     for disk_op, disk_dict in self.op.disks:
12468       if disk_op == constants.DDM_REMOVE:
12469         # remove the last disk
12470         device = instance.disks.pop()
12471         device_idx = len(instance.disks)
12472         for node, disk in device.ComputeNodeTree(instance.primary_node):
12473           self.cfg.SetDiskID(disk, node)
12474           msg = self.rpc.call_blockdev_remove(node, disk).fail_msg
12475           if msg:
12476             self.LogWarning("Could not remove disk/%d on node %s: %s,"
12477                             " continuing anyway", device_idx, node, msg)
12478         result.append(("disk/%d" % device_idx, "remove"))
12479
12480         # if this is a DRBD disk, return its port to the pool
12481         if device.dev_type in constants.LDS_DRBD:
12482           tcp_port = device.logical_id[2]
12483           self.cfg.AddTcpUdpPort(tcp_port)
12484       elif disk_op == constants.DDM_ADD:
12485         # add a new disk
12486         if instance.disk_template in (constants.DT_FILE,
12487                                         constants.DT_SHARED_FILE):
12488           file_driver, file_path = instance.disks[0].logical_id
12489           file_path = os.path.dirname(file_path)
12490         else:
12491           file_driver = file_path = None
12492         disk_idx_base = len(instance.disks)
12493         new_disk = _GenerateDiskTemplate(self,
12494                                          instance.disk_template,
12495                                          instance.name, instance.primary_node,
12496                                          instance.secondary_nodes,
12497                                          [disk_dict],
12498                                          file_path,
12499                                          file_driver,
12500                                          disk_idx_base,
12501                                          feedback_fn,
12502                                          self.diskparams)[0]
12503         instance.disks.append(new_disk)
12504         info = _GetInstanceInfoText(instance)
12505
12506         logging.info("Creating volume %s for instance %s",
12507                      new_disk.iv_name, instance.name)
12508         # Note: this needs to be kept in sync with _CreateDisks
12509         #HARDCODE
12510         for node in instance.all_nodes:
12511           f_create = node == instance.primary_node
12512           try:
12513             _CreateBlockDev(self, node, instance, new_disk,
12514                             f_create, info, f_create)
12515           except errors.OpExecError, err:
12516             self.LogWarning("Failed to create volume %s (%s) on"
12517                             " node %s: %s",
12518                             new_disk.iv_name, new_disk, node, err)
12519         result.append(("disk/%d" % disk_idx_base, "add:size=%s,mode=%s" %
12520                        (new_disk.size, new_disk.mode)))
12521       else:
12522         # change a given disk
12523         instance.disks[disk_op].mode = disk_dict[constants.IDISK_MODE]
12524         result.append(("disk.mode/%d" % disk_op,
12525                        disk_dict[constants.IDISK_MODE]))
12526
12527     if self.op.disk_template:
12528       if __debug__:
12529         check_nodes = set(instance.all_nodes)
12530         if self.op.remote_node:
12531           check_nodes.add(self.op.remote_node)
12532         for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
12533           owned = self.owned_locks(level)
12534           assert not (check_nodes - owned), \
12535             ("Not owning the correct locks, owning %r, expected at least %r" %
12536              (owned, check_nodes))
12537
12538       r_shut = _ShutdownInstanceDisks(self, instance)
12539       if not r_shut:
12540         raise errors.OpExecError("Cannot shutdown instance disks, unable to"
12541                                  " proceed with disk template conversion")
12542       mode = (instance.disk_template, self.op.disk_template)
12543       try:
12544         self._DISK_CONVERSIONS[mode](self, feedback_fn)
12545       except:
12546         self.cfg.ReleaseDRBDMinors(instance.name)
12547         raise
12548       result.append(("disk_template", self.op.disk_template))
12549
12550       assert instance.disk_template == self.op.disk_template, \
12551         ("Expected disk template '%s', found '%s'" %
12552          (self.op.disk_template, instance.disk_template))
12553
12554     # Release node and resource locks if there are any (they might already have
12555     # been released during disk conversion)
12556     _ReleaseLocks(self, locking.LEVEL_NODE)
12557     _ReleaseLocks(self, locking.LEVEL_NODE_RES)
12558
12559     # NIC changes
12560     for nic_op, nic_dict in self.op.nics:
12561       if nic_op == constants.DDM_REMOVE:
12562         # remove the last nic
12563         del instance.nics[-1]
12564         result.append(("nic.%d" % len(instance.nics), "remove"))
12565       elif nic_op == constants.DDM_ADD:
12566         # mac and bridge should be set, by now
12567         mac = nic_dict[constants.INIC_MAC]
12568         ip = nic_dict.get(constants.INIC_IP, None)
12569         nicparams = self.nic_pinst[constants.DDM_ADD]
12570         new_nic = objects.NIC(mac=mac, ip=ip, nicparams=nicparams)
12571         instance.nics.append(new_nic)
12572         result.append(("nic.%d" % (len(instance.nics) - 1),
12573                        "add:mac=%s,ip=%s,mode=%s,link=%s" %
12574                        (new_nic.mac, new_nic.ip,
12575                         self.nic_pnew[constants.DDM_ADD][constants.NIC_MODE],
12576                         self.nic_pnew[constants.DDM_ADD][constants.NIC_LINK]
12577                        )))
12578       else:
12579         for key in (constants.INIC_MAC, constants.INIC_IP):
12580           if key in nic_dict:
12581             setattr(instance.nics[nic_op], key, nic_dict[key])
12582         if nic_op in self.nic_pinst:
12583           instance.nics[nic_op].nicparams = self.nic_pinst[nic_op]
12584         for key, val in nic_dict.iteritems():
12585           result.append(("nic.%s/%d" % (key, nic_op), val))
12586
12587     # hvparams changes
12588     if self.op.hvparams:
12589       instance.hvparams = self.hv_inst
12590       for key, val in self.op.hvparams.iteritems():
12591         result.append(("hv/%s" % key, val))
12592
12593     # beparams changes
12594     if self.op.beparams:
12595       instance.beparams = self.be_inst
12596       for key, val in self.op.beparams.iteritems():
12597         result.append(("be/%s" % key, val))
12598
12599     # OS change
12600     if self.op.os_name:
12601       instance.os = self.op.os_name
12602
12603     # osparams changes
12604     if self.op.osparams:
12605       instance.osparams = self.os_inst
12606       for key, val in self.op.osparams.iteritems():
12607         result.append(("os/%s" % key, val))
12608
12609     # online/offline instance
12610     if self.op.online_inst:
12611       self.cfg.MarkInstanceDown(instance.name)
12612       result.append(("admin_state", constants.ADMINST_DOWN))
12613     if self.op.offline_inst:
12614       self.cfg.MarkInstanceOffline(instance.name)
12615       result.append(("admin_state", constants.ADMINST_OFFLINE))
12616
12617     self.cfg.Update(instance, feedback_fn)
12618
12619     assert not (self.owned_locks(locking.LEVEL_NODE_RES) or
12620                 self.owned_locks(locking.LEVEL_NODE)), \
12621       "All node locks should have been released by now"
12622
12623     return result
12624
12625   _DISK_CONVERSIONS = {
12626     (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
12627     (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
12628     }
12629
12630
12631 class LUInstanceChangeGroup(LogicalUnit):
12632   HPATH = "instance-change-group"
12633   HTYPE = constants.HTYPE_INSTANCE
12634   REQ_BGL = False
12635
12636   def ExpandNames(self):
12637     self.share_locks = _ShareAll()
12638     self.needed_locks = {
12639       locking.LEVEL_NODEGROUP: [],
12640       locking.LEVEL_NODE: [],
12641       }
12642
12643     self._ExpandAndLockInstance()
12644
12645     if self.op.target_groups:
12646       self.req_target_uuids = map(self.cfg.LookupNodeGroup,
12647                                   self.op.target_groups)
12648     else:
12649       self.req_target_uuids = None
12650
12651     self.op.iallocator = _GetDefaultIAllocator(self.cfg, self.op.iallocator)
12652
12653   def DeclareLocks(self, level):
12654     if level == locking.LEVEL_NODEGROUP:
12655       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
12656
12657       if self.req_target_uuids:
12658         lock_groups = set(self.req_target_uuids)
12659
12660         # Lock all groups used by instance optimistically; this requires going
12661         # via the node before it's locked, requiring verification later on
12662         instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_name)
12663         lock_groups.update(instance_groups)
12664       else:
12665         # No target groups, need to lock all of them
12666         lock_groups = locking.ALL_SET
12667
12668       self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
12669
12670     elif level == locking.LEVEL_NODE:
12671       if self.req_target_uuids:
12672         # Lock all nodes used by instances
12673         self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
12674         self._LockInstancesNodes()
12675
12676         # Lock all nodes in all potential target groups
12677         lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) -
12678                        self.cfg.GetInstanceNodeGroups(self.op.instance_name))
12679         member_nodes = [node_name
12680                         for group in lock_groups
12681                         for node_name in self.cfg.GetNodeGroup(group).members]
12682         self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
12683       else:
12684         # Lock all nodes as all groups are potential targets
12685         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
12686
12687   def CheckPrereq(self):
12688     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
12689     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
12690     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
12691
12692     assert (self.req_target_uuids is None or
12693             owned_groups.issuperset(self.req_target_uuids))
12694     assert owned_instances == set([self.op.instance_name])
12695
12696     # Get instance information
12697     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
12698
12699     # Check if node groups for locked instance are still correct
12700     assert owned_nodes.issuperset(self.instance.all_nodes), \
12701       ("Instance %s's nodes changed while we kept the lock" %
12702        self.op.instance_name)
12703
12704     inst_groups = _CheckInstanceNodeGroups(self.cfg, self.op.instance_name,
12705                                            owned_groups)
12706
12707     if self.req_target_uuids:
12708       # User requested specific target groups
12709       self.target_uuids = self.req_target_uuids
12710     else:
12711       # All groups except those used by the instance are potential targets
12712       self.target_uuids = owned_groups - inst_groups
12713
12714     conflicting_groups = self.target_uuids & inst_groups
12715     if conflicting_groups:
12716       raise errors.OpPrereqError("Can't use group(s) '%s' as targets, they are"
12717                                  " used by the instance '%s'" %
12718                                  (utils.CommaJoin(conflicting_groups),
12719                                   self.op.instance_name),
12720                                  errors.ECODE_INVAL)
12721
12722     if not self.target_uuids:
12723       raise errors.OpPrereqError("There are no possible target groups",
12724                                  errors.ECODE_INVAL)
12725
12726   def BuildHooksEnv(self):
12727     """Build hooks env.
12728
12729     """
12730     assert self.target_uuids
12731
12732     env = {
12733       "TARGET_GROUPS": " ".join(self.target_uuids),
12734       }
12735
12736     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
12737
12738     return env
12739
12740   def BuildHooksNodes(self):
12741     """Build hooks nodes.
12742
12743     """
12744     mn = self.cfg.GetMasterNode()
12745     return ([mn], [mn])
12746
12747   def Exec(self, feedback_fn):
12748     instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
12749
12750     assert instances == [self.op.instance_name], "Instance not locked"
12751
12752     ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_CHG_GROUP,
12753                      instances=instances, target_groups=list(self.target_uuids))
12754
12755     ial.Run(self.op.iallocator)
12756
12757     if not ial.success:
12758       raise errors.OpPrereqError("Can't compute solution for changing group of"
12759                                  " instance '%s' using iallocator '%s': %s" %
12760                                  (self.op.instance_name, self.op.iallocator,
12761                                   ial.info),
12762                                  errors.ECODE_NORES)
12763
12764     jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
12765
12766     self.LogInfo("Iallocator returned %s job(s) for changing group of"
12767                  " instance '%s'", len(jobs), self.op.instance_name)
12768
12769     return ResultWithJobs(jobs)
12770
12771
12772 class LUBackupQuery(NoHooksLU):
12773   """Query the exports list
12774
12775   """
12776   REQ_BGL = False
12777
12778   def ExpandNames(self):
12779     self.needed_locks = {}
12780     self.share_locks[locking.LEVEL_NODE] = 1
12781     if not self.op.nodes:
12782       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
12783     else:
12784       self.needed_locks[locking.LEVEL_NODE] = \
12785         _GetWantedNodes(self, self.op.nodes)
12786
12787   def Exec(self, feedback_fn):
12788     """Compute the list of all the exported system images.
12789
12790     @rtype: dict
12791     @return: a dictionary with the structure node->(export-list)
12792         where export-list is a list of the instances exported on
12793         that node.
12794
12795     """
12796     self.nodes = self.owned_locks(locking.LEVEL_NODE)
12797     rpcresult = self.rpc.call_export_list(self.nodes)
12798     result = {}
12799     for node in rpcresult:
12800       if rpcresult[node].fail_msg:
12801         result[node] = False
12802       else:
12803         result[node] = rpcresult[node].payload
12804
12805     return result
12806
12807
12808 class LUBackupPrepare(NoHooksLU):
12809   """Prepares an instance for an export and returns useful information.
12810
12811   """
12812   REQ_BGL = False
12813
12814   def ExpandNames(self):
12815     self._ExpandAndLockInstance()
12816
12817   def CheckPrereq(self):
12818     """Check prerequisites.
12819
12820     """
12821     instance_name = self.op.instance_name
12822
12823     self.instance = self.cfg.GetInstanceInfo(instance_name)
12824     assert self.instance is not None, \
12825           "Cannot retrieve locked instance %s" % self.op.instance_name
12826     _CheckNodeOnline(self, self.instance.primary_node)
12827
12828     self._cds = _GetClusterDomainSecret()
12829
12830   def Exec(self, feedback_fn):
12831     """Prepares an instance for an export.
12832
12833     """
12834     instance = self.instance
12835
12836     if self.op.mode == constants.EXPORT_MODE_REMOTE:
12837       salt = utils.GenerateSecret(8)
12838
12839       feedback_fn("Generating X509 certificate on %s" % instance.primary_node)
12840       result = self.rpc.call_x509_cert_create(instance.primary_node,
12841                                               constants.RIE_CERT_VALIDITY)
12842       result.Raise("Can't create X509 key and certificate on %s" % result.node)
12843
12844       (name, cert_pem) = result.payload
12845
12846       cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
12847                                              cert_pem)
12848
12849       return {
12850         "handshake": masterd.instance.ComputeRemoteExportHandshake(self._cds),
12851         "x509_key_name": (name, utils.Sha1Hmac(self._cds, name, salt=salt),
12852                           salt),
12853         "x509_ca": utils.SignX509Certificate(cert, self._cds, salt),
12854         }
12855
12856     return None
12857
12858
12859 class LUBackupExport(LogicalUnit):
12860   """Export an instance to an image in the cluster.
12861
12862   """
12863   HPATH = "instance-export"
12864   HTYPE = constants.HTYPE_INSTANCE
12865   REQ_BGL = False
12866
12867   def CheckArguments(self):
12868     """Check the arguments.
12869
12870     """
12871     self.x509_key_name = self.op.x509_key_name
12872     self.dest_x509_ca_pem = self.op.destination_x509_ca
12873
12874     if self.op.mode == constants.EXPORT_MODE_REMOTE:
12875       if not self.x509_key_name:
12876         raise errors.OpPrereqError("Missing X509 key name for encryption",
12877                                    errors.ECODE_INVAL)
12878
12879       if not self.dest_x509_ca_pem:
12880         raise errors.OpPrereqError("Missing destination X509 CA",
12881                                    errors.ECODE_INVAL)
12882
12883   def ExpandNames(self):
12884     self._ExpandAndLockInstance()
12885
12886     # Lock all nodes for local exports
12887     if self.op.mode == constants.EXPORT_MODE_LOCAL:
12888       # FIXME: lock only instance primary and destination node
12889       #
12890       # Sad but true, for now we have do lock all nodes, as we don't know where
12891       # the previous export might be, and in this LU we search for it and
12892       # remove it from its current node. In the future we could fix this by:
12893       #  - making a tasklet to search (share-lock all), then create the
12894       #    new one, then one to remove, after
12895       #  - removing the removal operation altogether
12896       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
12897
12898   def DeclareLocks(self, level):
12899     """Last minute lock declaration."""
12900     # All nodes are locked anyway, so nothing to do here.
12901
12902   def BuildHooksEnv(self):
12903     """Build hooks env.
12904
12905     This will run on the master, primary node and target node.
12906
12907     """
12908     env = {
12909       "EXPORT_MODE": self.op.mode,
12910       "EXPORT_NODE": self.op.target_node,
12911       "EXPORT_DO_SHUTDOWN": self.op.shutdown,
12912       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
12913       # TODO: Generic function for boolean env variables
12914       "REMOVE_INSTANCE": str(bool(self.op.remove_instance)),
12915       }
12916
12917     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
12918
12919     return env
12920
12921   def BuildHooksNodes(self):
12922     """Build hooks nodes.
12923
12924     """
12925     nl = [self.cfg.GetMasterNode(), self.instance.primary_node]
12926
12927     if self.op.mode == constants.EXPORT_MODE_LOCAL:
12928       nl.append(self.op.target_node)
12929
12930     return (nl, nl)
12931
12932   def CheckPrereq(self):
12933     """Check prerequisites.
12934
12935     This checks that the instance and node names are valid.
12936
12937     """
12938     instance_name = self.op.instance_name
12939
12940     self.instance = self.cfg.GetInstanceInfo(instance_name)
12941     assert self.instance is not None, \
12942           "Cannot retrieve locked instance %s" % self.op.instance_name
12943     _CheckNodeOnline(self, self.instance.primary_node)
12944
12945     if (self.op.remove_instance and
12946         self.instance.admin_state == constants.ADMINST_UP and
12947         not self.op.shutdown):
12948       raise errors.OpPrereqError("Can not remove instance without shutting it"
12949                                  " down before")
12950
12951     if self.op.mode == constants.EXPORT_MODE_LOCAL:
12952       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
12953       self.dst_node = self.cfg.GetNodeInfo(self.op.target_node)
12954       assert self.dst_node is not None
12955
12956       _CheckNodeOnline(self, self.dst_node.name)
12957       _CheckNodeNotDrained(self, self.dst_node.name)
12958
12959       self._cds = None
12960       self.dest_disk_info = None
12961       self.dest_x509_ca = None
12962
12963     elif self.op.mode == constants.EXPORT_MODE_REMOTE:
12964       self.dst_node = None
12965
12966       if len(self.op.target_node) != len(self.instance.disks):
12967         raise errors.OpPrereqError(("Received destination information for %s"
12968                                     " disks, but instance %s has %s disks") %
12969                                    (len(self.op.target_node), instance_name,
12970                                     len(self.instance.disks)),
12971                                    errors.ECODE_INVAL)
12972
12973       cds = _GetClusterDomainSecret()
12974
12975       # Check X509 key name
12976       try:
12977         (key_name, hmac_digest, hmac_salt) = self.x509_key_name
12978       except (TypeError, ValueError), err:
12979         raise errors.OpPrereqError("Invalid data for X509 key name: %s" % err)
12980
12981       if not utils.VerifySha1Hmac(cds, key_name, hmac_digest, salt=hmac_salt):
12982         raise errors.OpPrereqError("HMAC for X509 key name is wrong",
12983                                    errors.ECODE_INVAL)
12984
12985       # Load and verify CA
12986       try:
12987         (cert, _) = utils.LoadSignedX509Certificate(self.dest_x509_ca_pem, cds)
12988       except OpenSSL.crypto.Error, err:
12989         raise errors.OpPrereqError("Unable to load destination X509 CA (%s)" %
12990                                    (err, ), errors.ECODE_INVAL)
12991
12992       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
12993       if errcode is not None:
12994         raise errors.OpPrereqError("Invalid destination X509 CA (%s)" %
12995                                    (msg, ), errors.ECODE_INVAL)
12996
12997       self.dest_x509_ca = cert
12998
12999       # Verify target information
13000       disk_info = []
13001       for idx, disk_data in enumerate(self.op.target_node):
13002         try:
13003           (host, port, magic) = \
13004             masterd.instance.CheckRemoteExportDiskInfo(cds, idx, disk_data)
13005         except errors.GenericError, err:
13006           raise errors.OpPrereqError("Target info for disk %s: %s" %
13007                                      (idx, err), errors.ECODE_INVAL)
13008
13009         disk_info.append((host, port, magic))
13010
13011       assert len(disk_info) == len(self.op.target_node)
13012       self.dest_disk_info = disk_info
13013
13014     else:
13015       raise errors.ProgrammerError("Unhandled export mode %r" %
13016                                    self.op.mode)
13017
13018     # instance disk type verification
13019     # TODO: Implement export support for file-based disks
13020     for disk in self.instance.disks:
13021       if disk.dev_type == constants.LD_FILE:
13022         raise errors.OpPrereqError("Export not supported for instances with"
13023                                    " file-based disks", errors.ECODE_INVAL)
13024
13025   def _CleanupExports(self, feedback_fn):
13026     """Removes exports of current instance from all other nodes.
13027
13028     If an instance in a cluster with nodes A..D was exported to node C, its
13029     exports will be removed from the nodes A, B and D.
13030
13031     """
13032     assert self.op.mode != constants.EXPORT_MODE_REMOTE
13033
13034     nodelist = self.cfg.GetNodeList()
13035     nodelist.remove(self.dst_node.name)
13036
13037     # on one-node clusters nodelist will be empty after the removal
13038     # if we proceed the backup would be removed because OpBackupQuery
13039     # substitutes an empty list with the full cluster node list.
13040     iname = self.instance.name
13041     if nodelist:
13042       feedback_fn("Removing old exports for instance %s" % iname)
13043       exportlist = self.rpc.call_export_list(nodelist)
13044       for node in exportlist:
13045         if exportlist[node].fail_msg:
13046           continue
13047         if iname in exportlist[node].payload:
13048           msg = self.rpc.call_export_remove(node, iname).fail_msg
13049           if msg:
13050             self.LogWarning("Could not remove older export for instance %s"
13051                             " on node %s: %s", iname, node, msg)
13052
13053   def Exec(self, feedback_fn):
13054     """Export an instance to an image in the cluster.
13055
13056     """
13057     assert self.op.mode in constants.EXPORT_MODES
13058
13059     instance = self.instance
13060     src_node = instance.primary_node
13061
13062     if self.op.shutdown:
13063       # shutdown the instance, but not the disks
13064       feedback_fn("Shutting down instance %s" % instance.name)
13065       result = self.rpc.call_instance_shutdown(src_node, instance,
13066                                                self.op.shutdown_timeout)
13067       # TODO: Maybe ignore failures if ignore_remove_failures is set
13068       result.Raise("Could not shutdown instance %s on"
13069                    " node %s" % (instance.name, src_node))
13070
13071     # set the disks ID correctly since call_instance_start needs the
13072     # correct drbd minor to create the symlinks
13073     for disk in instance.disks:
13074       self.cfg.SetDiskID(disk, src_node)
13075
13076     activate_disks = (instance.admin_state != constants.ADMINST_UP)
13077
13078     if activate_disks:
13079       # Activate the instance disks if we'exporting a stopped instance
13080       feedback_fn("Activating disks for %s" % instance.name)
13081       _StartInstanceDisks(self, instance, None)
13082
13083     try:
13084       helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
13085                                                      instance)
13086
13087       helper.CreateSnapshots()
13088       try:
13089         if (self.op.shutdown and
13090             instance.admin_state == constants.ADMINST_UP and
13091             not self.op.remove_instance):
13092           assert not activate_disks
13093           feedback_fn("Starting instance %s" % instance.name)
13094           result = self.rpc.call_instance_start(src_node,
13095                                                 (instance, None, None), False)
13096           msg = result.fail_msg
13097           if msg:
13098             feedback_fn("Failed to start instance: %s" % msg)
13099             _ShutdownInstanceDisks(self, instance)
13100             raise errors.OpExecError("Could not start instance: %s" % msg)
13101
13102         if self.op.mode == constants.EXPORT_MODE_LOCAL:
13103           (fin_resu, dresults) = helper.LocalExport(self.dst_node)
13104         elif self.op.mode == constants.EXPORT_MODE_REMOTE:
13105           connect_timeout = constants.RIE_CONNECT_TIMEOUT
13106           timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
13107
13108           (key_name, _, _) = self.x509_key_name
13109
13110           dest_ca_pem = \
13111             OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
13112                                             self.dest_x509_ca)
13113
13114           (fin_resu, dresults) = helper.RemoteExport(self.dest_disk_info,
13115                                                      key_name, dest_ca_pem,
13116                                                      timeouts)
13117       finally:
13118         helper.Cleanup()
13119
13120       # Check for backwards compatibility
13121       assert len(dresults) == len(instance.disks)
13122       assert compat.all(isinstance(i, bool) for i in dresults), \
13123              "Not all results are boolean: %r" % dresults
13124
13125     finally:
13126       if activate_disks:
13127         feedback_fn("Deactivating disks for %s" % instance.name)
13128         _ShutdownInstanceDisks(self, instance)
13129
13130     if not (compat.all(dresults) and fin_resu):
13131       failures = []
13132       if not fin_resu:
13133         failures.append("export finalization")
13134       if not compat.all(dresults):
13135         fdsk = utils.CommaJoin(idx for (idx, dsk) in enumerate(dresults)
13136                                if not dsk)
13137         failures.append("disk export: disk(s) %s" % fdsk)
13138
13139       raise errors.OpExecError("Export failed, errors in %s" %
13140                                utils.CommaJoin(failures))
13141
13142     # At this point, the export was successful, we can cleanup/finish
13143
13144     # Remove instance if requested
13145     if self.op.remove_instance:
13146       feedback_fn("Removing instance %s" % instance.name)
13147       _RemoveInstance(self, feedback_fn, instance,
13148                       self.op.ignore_remove_failures)
13149
13150     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13151       self._CleanupExports(feedback_fn)
13152
13153     return fin_resu, dresults
13154
13155
13156 class LUBackupRemove(NoHooksLU):
13157   """Remove exports related to the named instance.
13158
13159   """
13160   REQ_BGL = False
13161
13162   def ExpandNames(self):
13163     self.needed_locks = {}
13164     # We need all nodes to be locked in order for RemoveExport to work, but we
13165     # don't need to lock the instance itself, as nothing will happen to it (and
13166     # we can remove exports also for a removed instance)
13167     self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13168
13169   def Exec(self, feedback_fn):
13170     """Remove any export.
13171
13172     """
13173     instance_name = self.cfg.ExpandInstanceName(self.op.instance_name)
13174     # If the instance was not found we'll try with the name that was passed in.
13175     # This will only work if it was an FQDN, though.
13176     fqdn_warn = False
13177     if not instance_name:
13178       fqdn_warn = True
13179       instance_name = self.op.instance_name
13180
13181     locked_nodes = self.owned_locks(locking.LEVEL_NODE)
13182     exportlist = self.rpc.call_export_list(locked_nodes)
13183     found = False
13184     for node in exportlist:
13185       msg = exportlist[node].fail_msg
13186       if msg:
13187         self.LogWarning("Failed to query node %s (continuing): %s", node, msg)
13188         continue
13189       if instance_name in exportlist[node].payload:
13190         found = True
13191         result = self.rpc.call_export_remove(node, instance_name)
13192         msg = result.fail_msg
13193         if msg:
13194           logging.error("Could not remove export for instance %s"
13195                         " on node %s: %s", instance_name, node, msg)
13196
13197     if fqdn_warn and not found:
13198       feedback_fn("Export not found. If trying to remove an export belonging"
13199                   " to a deleted instance please use its Fully Qualified"
13200                   " Domain Name.")
13201
13202
13203 class LUGroupAdd(LogicalUnit):
13204   """Logical unit for creating node groups.
13205
13206   """
13207   HPATH = "group-add"
13208   HTYPE = constants.HTYPE_GROUP
13209   REQ_BGL = False
13210
13211   def ExpandNames(self):
13212     # We need the new group's UUID here so that we can create and acquire the
13213     # corresponding lock. Later, in Exec(), we'll indicate to cfg.AddNodeGroup
13214     # that it should not check whether the UUID exists in the configuration.
13215     self.group_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
13216     self.needed_locks = {}
13217     self.add_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
13218
13219   def CheckPrereq(self):
13220     """Check prerequisites.
13221
13222     This checks that the given group name is not an existing node group
13223     already.
13224
13225     """
13226     try:
13227       existing_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13228     except errors.OpPrereqError:
13229       pass
13230     else:
13231       raise errors.OpPrereqError("Desired group name '%s' already exists as a"
13232                                  " node group (UUID: %s)" %
13233                                  (self.op.group_name, existing_uuid),
13234                                  errors.ECODE_EXISTS)
13235
13236     if self.op.ndparams:
13237       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
13238
13239     if self.op.hv_state:
13240       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None)
13241     else:
13242       self.new_hv_state = None
13243
13244     if self.op.disk_state:
13245       self.new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state, None)
13246     else:
13247       self.new_disk_state = None
13248
13249     if self.op.diskparams:
13250       for templ in constants.DISK_TEMPLATES:
13251         if templ not in self.op.diskparams:
13252           self.op.diskparams[templ] = {}
13253         utils.ForceDictType(self.op.diskparams[templ], constants.DISK_DT_TYPES)
13254     else:
13255       self.op.diskparams = self.cfg.GetClusterInfo().diskparams
13256
13257     if self.op.ipolicy:
13258       cluster = self.cfg.GetClusterInfo()
13259       full_ipolicy = cluster.SimpleFillIPolicy(self.op.ipolicy)
13260       try:
13261         objects.InstancePolicy.CheckParameterSyntax(full_ipolicy)
13262       except errors.ConfigurationError, err:
13263         raise errors.OpPrereqError("Invalid instance policy: %s" % err,
13264                                    errors.ECODE_INVAL)
13265
13266   def BuildHooksEnv(self):
13267     """Build hooks env.
13268
13269     """
13270     return {
13271       "GROUP_NAME": self.op.group_name,
13272       }
13273
13274   def BuildHooksNodes(self):
13275     """Build hooks nodes.
13276
13277     """
13278     mn = self.cfg.GetMasterNode()
13279     return ([mn], [mn])
13280
13281   def Exec(self, feedback_fn):
13282     """Add the node group to the cluster.
13283
13284     """
13285     group_obj = objects.NodeGroup(name=self.op.group_name, members=[],
13286                                   uuid=self.group_uuid,
13287                                   alloc_policy=self.op.alloc_policy,
13288                                   ndparams=self.op.ndparams,
13289                                   diskparams=self.op.diskparams,
13290                                   ipolicy=self.op.ipolicy,
13291                                   hv_state_static=self.new_hv_state,
13292                                   disk_state_static=self.new_disk_state)
13293
13294     self.cfg.AddNodeGroup(group_obj, self.proc.GetECId(), check_uuid=False)
13295     del self.remove_locks[locking.LEVEL_NODEGROUP]
13296
13297
13298 class LUGroupAssignNodes(NoHooksLU):
13299   """Logical unit for assigning nodes to groups.
13300
13301   """
13302   REQ_BGL = False
13303
13304   def ExpandNames(self):
13305     # These raise errors.OpPrereqError on their own:
13306     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13307     self.op.nodes = _GetWantedNodes(self, self.op.nodes)
13308
13309     # We want to lock all the affected nodes and groups. We have readily
13310     # available the list of nodes, and the *destination* group. To gather the
13311     # list of "source" groups, we need to fetch node information later on.
13312     self.needed_locks = {
13313       locking.LEVEL_NODEGROUP: set([self.group_uuid]),
13314       locking.LEVEL_NODE: self.op.nodes,
13315       }
13316
13317   def DeclareLocks(self, level):
13318     if level == locking.LEVEL_NODEGROUP:
13319       assert len(self.needed_locks[locking.LEVEL_NODEGROUP]) == 1
13320
13321       # Try to get all affected nodes' groups without having the group or node
13322       # lock yet. Needs verification later in the code flow.
13323       groups = self.cfg.GetNodeGroupsFromNodes(self.op.nodes)
13324
13325       self.needed_locks[locking.LEVEL_NODEGROUP].update(groups)
13326
13327   def CheckPrereq(self):
13328     """Check prerequisites.
13329
13330     """
13331     assert self.needed_locks[locking.LEVEL_NODEGROUP]
13332     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
13333             frozenset(self.op.nodes))
13334
13335     expected_locks = (set([self.group_uuid]) |
13336                       self.cfg.GetNodeGroupsFromNodes(self.op.nodes))
13337     actual_locks = self.owned_locks(locking.LEVEL_NODEGROUP)
13338     if actual_locks != expected_locks:
13339       raise errors.OpExecError("Nodes changed groups since locks were acquired,"
13340                                " current groups are '%s', used to be '%s'" %
13341                                (utils.CommaJoin(expected_locks),
13342                                 utils.CommaJoin(actual_locks)))
13343
13344     self.node_data = self.cfg.GetAllNodesInfo()
13345     self.group = self.cfg.GetNodeGroup(self.group_uuid)
13346     instance_data = self.cfg.GetAllInstancesInfo()
13347
13348     if self.group is None:
13349       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
13350                                (self.op.group_name, self.group_uuid))
13351
13352     (new_splits, previous_splits) = \
13353       self.CheckAssignmentForSplitInstances([(node, self.group_uuid)
13354                                              for node in self.op.nodes],
13355                                             self.node_data, instance_data)
13356
13357     if new_splits:
13358       fmt_new_splits = utils.CommaJoin(utils.NiceSort(new_splits))
13359
13360       if not self.op.force:
13361         raise errors.OpExecError("The following instances get split by this"
13362                                  " change and --force was not given: %s" %
13363                                  fmt_new_splits)
13364       else:
13365         self.LogWarning("This operation will split the following instances: %s",
13366                         fmt_new_splits)
13367
13368         if previous_splits:
13369           self.LogWarning("In addition, these already-split instances continue"
13370                           " to be split across groups: %s",
13371                           utils.CommaJoin(utils.NiceSort(previous_splits)))
13372
13373   def Exec(self, feedback_fn):
13374     """Assign nodes to a new group.
13375
13376     """
13377     mods = [(node_name, self.group_uuid) for node_name in self.op.nodes]
13378
13379     self.cfg.AssignGroupNodes(mods)
13380
13381   @staticmethod
13382   def CheckAssignmentForSplitInstances(changes, node_data, instance_data):
13383     """Check for split instances after a node assignment.
13384
13385     This method considers a series of node assignments as an atomic operation,
13386     and returns information about split instances after applying the set of
13387     changes.
13388
13389     In particular, it returns information about newly split instances, and
13390     instances that were already split, and remain so after the change.
13391
13392     Only instances whose disk template is listed in constants.DTS_INT_MIRROR are
13393     considered.
13394
13395     @type changes: list of (node_name, new_group_uuid) pairs.
13396     @param changes: list of node assignments to consider.
13397     @param node_data: a dict with data for all nodes
13398     @param instance_data: a dict with all instances to consider
13399     @rtype: a two-tuple
13400     @return: a list of instances that were previously okay and result split as a
13401       consequence of this change, and a list of instances that were previously
13402       split and this change does not fix.
13403
13404     """
13405     changed_nodes = dict((node, group) for node, group in changes
13406                          if node_data[node].group != group)
13407
13408     all_split_instances = set()
13409     previously_split_instances = set()
13410
13411     def InstanceNodes(instance):
13412       return [instance.primary_node] + list(instance.secondary_nodes)
13413
13414     for inst in instance_data.values():
13415       if inst.disk_template not in constants.DTS_INT_MIRROR:
13416         continue
13417
13418       instance_nodes = InstanceNodes(inst)
13419
13420       if len(set(node_data[node].group for node in instance_nodes)) > 1:
13421         previously_split_instances.add(inst.name)
13422
13423       if len(set(changed_nodes.get(node, node_data[node].group)
13424                  for node in instance_nodes)) > 1:
13425         all_split_instances.add(inst.name)
13426
13427     return (list(all_split_instances - previously_split_instances),
13428             list(previously_split_instances & all_split_instances))
13429
13430
13431 class _GroupQuery(_QueryBase):
13432   FIELDS = query.GROUP_FIELDS
13433
13434   def ExpandNames(self, lu):
13435     lu.needed_locks = {}
13436
13437     self._all_groups = lu.cfg.GetAllNodeGroupsInfo()
13438     self._cluster = lu.cfg.GetClusterInfo()
13439     name_to_uuid = dict((g.name, g.uuid) for g in self._all_groups.values())
13440
13441     if not self.names:
13442       self.wanted = [name_to_uuid[name]
13443                      for name in utils.NiceSort(name_to_uuid.keys())]
13444     else:
13445       # Accept names to be either names or UUIDs.
13446       missing = []
13447       self.wanted = []
13448       all_uuid = frozenset(self._all_groups.keys())
13449
13450       for name in self.names:
13451         if name in all_uuid:
13452           self.wanted.append(name)
13453         elif name in name_to_uuid:
13454           self.wanted.append(name_to_uuid[name])
13455         else:
13456           missing.append(name)
13457
13458       if missing:
13459         raise errors.OpPrereqError("Some groups do not exist: %s" %
13460                                    utils.CommaJoin(missing),
13461                                    errors.ECODE_NOENT)
13462
13463   def DeclareLocks(self, lu, level):
13464     pass
13465
13466   def _GetQueryData(self, lu):
13467     """Computes the list of node groups and their attributes.
13468
13469     """
13470     do_nodes = query.GQ_NODE in self.requested_data
13471     do_instances = query.GQ_INST in self.requested_data
13472
13473     group_to_nodes = None
13474     group_to_instances = None
13475
13476     # For GQ_NODE, we need to map group->[nodes], and group->[instances] for
13477     # GQ_INST. The former is attainable with just GetAllNodesInfo(), but for the
13478     # latter GetAllInstancesInfo() is not enough, for we have to go through
13479     # instance->node. Hence, we will need to process nodes even if we only need
13480     # instance information.
13481     if do_nodes or do_instances:
13482       all_nodes = lu.cfg.GetAllNodesInfo()
13483       group_to_nodes = dict((uuid, []) for uuid in self.wanted)
13484       node_to_group = {}
13485
13486       for node in all_nodes.values():
13487         if node.group in group_to_nodes:
13488           group_to_nodes[node.group].append(node.name)
13489           node_to_group[node.name] = node.group
13490
13491       if do_instances:
13492         all_instances = lu.cfg.GetAllInstancesInfo()
13493         group_to_instances = dict((uuid, []) for uuid in self.wanted)
13494
13495         for instance in all_instances.values():
13496           node = instance.primary_node
13497           if node in node_to_group:
13498             group_to_instances[node_to_group[node]].append(instance.name)
13499
13500         if not do_nodes:
13501           # Do not pass on node information if it was not requested.
13502           group_to_nodes = None
13503
13504     return query.GroupQueryData(self._cluster,
13505                                 [self._all_groups[uuid]
13506                                  for uuid in self.wanted],
13507                                 group_to_nodes, group_to_instances)
13508
13509
13510 class LUGroupQuery(NoHooksLU):
13511   """Logical unit for querying node groups.
13512
13513   """
13514   REQ_BGL = False
13515
13516   def CheckArguments(self):
13517     self.gq = _GroupQuery(qlang.MakeSimpleFilter("name", self.op.names),
13518                           self.op.output_fields, False)
13519
13520   def ExpandNames(self):
13521     self.gq.ExpandNames(self)
13522
13523   def DeclareLocks(self, level):
13524     self.gq.DeclareLocks(self, level)
13525
13526   def Exec(self, feedback_fn):
13527     return self.gq.OldStyleQuery(self)
13528
13529
13530 class LUGroupSetParams(LogicalUnit):
13531   """Modifies the parameters of a node group.
13532
13533   """
13534   HPATH = "group-modify"
13535   HTYPE = constants.HTYPE_GROUP
13536   REQ_BGL = False
13537
13538   def CheckArguments(self):
13539     all_changes = [
13540       self.op.ndparams,
13541       self.op.diskparams,
13542       self.op.alloc_policy,
13543       self.op.hv_state,
13544       self.op.disk_state,
13545       self.op.ipolicy,
13546       ]
13547
13548     if all_changes.count(None) == len(all_changes):
13549       raise errors.OpPrereqError("Please pass at least one modification",
13550                                  errors.ECODE_INVAL)
13551
13552   def ExpandNames(self):
13553     # This raises errors.OpPrereqError on its own:
13554     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13555
13556     self.needed_locks = {
13557       locking.LEVEL_INSTANCE: [],
13558       locking.LEVEL_NODEGROUP: [self.group_uuid],
13559       }
13560
13561     self.share_locks[locking.LEVEL_INSTANCE] = 1
13562
13563   def DeclareLocks(self, level):
13564     if level == locking.LEVEL_INSTANCE:
13565       assert not self.needed_locks[locking.LEVEL_INSTANCE]
13566
13567       # Lock instances optimistically, needs verification once group lock has
13568       # been acquired
13569       self.needed_locks[locking.LEVEL_INSTANCE] = \
13570           self.cfg.GetNodeGroupInstances(self.group_uuid)
13571
13572   def CheckPrereq(self):
13573     """Check prerequisites.
13574
13575     """
13576     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
13577
13578     # Check if locked instances are still correct
13579     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
13580
13581     self.group = self.cfg.GetNodeGroup(self.group_uuid)
13582     cluster = self.cfg.GetClusterInfo()
13583
13584     if self.group is None:
13585       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
13586                                (self.op.group_name, self.group_uuid))
13587
13588     if self.op.ndparams:
13589       new_ndparams = _GetUpdatedParams(self.group.ndparams, self.op.ndparams)
13590       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
13591       self.new_ndparams = new_ndparams
13592
13593     if self.op.diskparams:
13594       self.new_diskparams = dict()
13595       for templ in constants.DISK_TEMPLATES:
13596         if templ not in self.op.diskparams:
13597           self.op.diskparams[templ] = {}
13598         new_templ_params = _GetUpdatedParams(self.group.diskparams[templ],
13599                                              self.op.diskparams[templ])
13600         utils.ForceDictType(new_templ_params, constants.DISK_DT_TYPES)
13601         self.new_diskparams[templ] = new_templ_params
13602
13603     if self.op.hv_state:
13604       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
13605                                                  self.group.hv_state_static)
13606
13607     if self.op.disk_state:
13608       self.new_disk_state = \
13609         _MergeAndVerifyDiskState(self.op.disk_state,
13610                                  self.group.disk_state_static)
13611
13612     if self.op.ipolicy:
13613       self.new_ipolicy = _GetUpdatedIPolicy(self.group.ipolicy,
13614                                             self.op.ipolicy,
13615                                             group_policy=True)
13616
13617       new_ipolicy = cluster.SimpleFillIPolicy(self.new_ipolicy)
13618       inst_filter = lambda inst: inst.name in owned_instances
13619       instances = self.cfg.GetInstancesInfoByFilter(inst_filter).values()
13620       violations = \
13621           _ComputeNewInstanceViolations(_CalculateGroupIPolicy(cluster,
13622                                                                self.group),
13623                                         new_ipolicy, instances)
13624
13625       if violations:
13626         self.LogWarning("After the ipolicy change the following instances"
13627                         " violate them: %s",
13628                         utils.CommaJoin(violations))
13629
13630   def BuildHooksEnv(self):
13631     """Build hooks env.
13632
13633     """
13634     return {
13635       "GROUP_NAME": self.op.group_name,
13636       "NEW_ALLOC_POLICY": self.op.alloc_policy,
13637       }
13638
13639   def BuildHooksNodes(self):
13640     """Build hooks nodes.
13641
13642     """
13643     mn = self.cfg.GetMasterNode()
13644     return ([mn], [mn])
13645
13646   def Exec(self, feedback_fn):
13647     """Modifies the node group.
13648
13649     """
13650     result = []
13651
13652     if self.op.ndparams:
13653       self.group.ndparams = self.new_ndparams
13654       result.append(("ndparams", str(self.group.ndparams)))
13655
13656     if self.op.diskparams:
13657       self.group.diskparams = self.new_diskparams
13658       result.append(("diskparams", str(self.group.diskparams)))
13659
13660     if self.op.alloc_policy:
13661       self.group.alloc_policy = self.op.alloc_policy
13662
13663     if self.op.hv_state:
13664       self.group.hv_state_static = self.new_hv_state
13665
13666     if self.op.disk_state:
13667       self.group.disk_state_static = self.new_disk_state
13668
13669     if self.op.ipolicy:
13670       self.group.ipolicy = self.new_ipolicy
13671
13672     self.cfg.Update(self.group, feedback_fn)
13673     return result
13674
13675
13676 class LUGroupRemove(LogicalUnit):
13677   HPATH = "group-remove"
13678   HTYPE = constants.HTYPE_GROUP
13679   REQ_BGL = False
13680
13681   def ExpandNames(self):
13682     # This will raises errors.OpPrereqError on its own:
13683     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13684     self.needed_locks = {
13685       locking.LEVEL_NODEGROUP: [self.group_uuid],
13686       }
13687
13688   def CheckPrereq(self):
13689     """Check prerequisites.
13690
13691     This checks that the given group name exists as a node group, that is
13692     empty (i.e., contains no nodes), and that is not the last group of the
13693     cluster.
13694
13695     """
13696     # Verify that the group is empty.
13697     group_nodes = [node.name
13698                    for node in self.cfg.GetAllNodesInfo().values()
13699                    if node.group == self.group_uuid]
13700
13701     if group_nodes:
13702       raise errors.OpPrereqError("Group '%s' not empty, has the following"
13703                                  " nodes: %s" %
13704                                  (self.op.group_name,
13705                                   utils.CommaJoin(utils.NiceSort(group_nodes))),
13706                                  errors.ECODE_STATE)
13707
13708     # Verify the cluster would not be left group-less.
13709     if len(self.cfg.GetNodeGroupList()) == 1:
13710       raise errors.OpPrereqError("Group '%s' is the only group,"
13711                                  " cannot be removed" %
13712                                  self.op.group_name,
13713                                  errors.ECODE_STATE)
13714
13715   def BuildHooksEnv(self):
13716     """Build hooks env.
13717
13718     """
13719     return {
13720       "GROUP_NAME": self.op.group_name,
13721       }
13722
13723   def BuildHooksNodes(self):
13724     """Build hooks nodes.
13725
13726     """
13727     mn = self.cfg.GetMasterNode()
13728     return ([mn], [mn])
13729
13730   def Exec(self, feedback_fn):
13731     """Remove the node group.
13732
13733     """
13734     try:
13735       self.cfg.RemoveNodeGroup(self.group_uuid)
13736     except errors.ConfigurationError:
13737       raise errors.OpExecError("Group '%s' with UUID %s disappeared" %
13738                                (self.op.group_name, self.group_uuid))
13739
13740     self.remove_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
13741
13742
13743 class LUGroupRename(LogicalUnit):
13744   HPATH = "group-rename"
13745   HTYPE = constants.HTYPE_GROUP
13746   REQ_BGL = False
13747
13748   def ExpandNames(self):
13749     # This raises errors.OpPrereqError on its own:
13750     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13751
13752     self.needed_locks = {
13753       locking.LEVEL_NODEGROUP: [self.group_uuid],
13754       }
13755
13756   def CheckPrereq(self):
13757     """Check prerequisites.
13758
13759     Ensures requested new name is not yet used.
13760
13761     """
13762     try:
13763       new_name_uuid = self.cfg.LookupNodeGroup(self.op.new_name)
13764     except errors.OpPrereqError:
13765       pass
13766     else:
13767       raise errors.OpPrereqError("Desired new name '%s' clashes with existing"
13768                                  " node group (UUID: %s)" %
13769                                  (self.op.new_name, new_name_uuid),
13770                                  errors.ECODE_EXISTS)
13771
13772   def BuildHooksEnv(self):
13773     """Build hooks env.
13774
13775     """
13776     return {
13777       "OLD_NAME": self.op.group_name,
13778       "NEW_NAME": self.op.new_name,
13779       }
13780
13781   def BuildHooksNodes(self):
13782     """Build hooks nodes.
13783
13784     """
13785     mn = self.cfg.GetMasterNode()
13786
13787     all_nodes = self.cfg.GetAllNodesInfo()
13788     all_nodes.pop(mn, None)
13789
13790     run_nodes = [mn]
13791     run_nodes.extend(node.name for node in all_nodes.values()
13792                      if node.group == self.group_uuid)
13793
13794     return (run_nodes, run_nodes)
13795
13796   def Exec(self, feedback_fn):
13797     """Rename the node group.
13798
13799     """
13800     group = self.cfg.GetNodeGroup(self.group_uuid)
13801
13802     if group is None:
13803       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
13804                                (self.op.group_name, self.group_uuid))
13805
13806     group.name = self.op.new_name
13807     self.cfg.Update(group, feedback_fn)
13808
13809     return self.op.new_name
13810
13811
13812 class LUGroupEvacuate(LogicalUnit):
13813   HPATH = "group-evacuate"
13814   HTYPE = constants.HTYPE_GROUP
13815   REQ_BGL = False
13816
13817   def ExpandNames(self):
13818     # This raises errors.OpPrereqError on its own:
13819     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13820
13821     if self.op.target_groups:
13822       self.req_target_uuids = map(self.cfg.LookupNodeGroup,
13823                                   self.op.target_groups)
13824     else:
13825       self.req_target_uuids = []
13826
13827     if self.group_uuid in self.req_target_uuids:
13828       raise errors.OpPrereqError("Group to be evacuated (%s) can not be used"
13829                                  " as a target group (targets are %s)" %
13830                                  (self.group_uuid,
13831                                   utils.CommaJoin(self.req_target_uuids)),
13832                                  errors.ECODE_INVAL)
13833
13834     self.op.iallocator = _GetDefaultIAllocator(self.cfg, self.op.iallocator)
13835
13836     self.share_locks = _ShareAll()
13837     self.needed_locks = {
13838       locking.LEVEL_INSTANCE: [],
13839       locking.LEVEL_NODEGROUP: [],
13840       locking.LEVEL_NODE: [],
13841       }
13842
13843   def DeclareLocks(self, level):
13844     if level == locking.LEVEL_INSTANCE:
13845       assert not self.needed_locks[locking.LEVEL_INSTANCE]
13846
13847       # Lock instances optimistically, needs verification once node and group
13848       # locks have been acquired
13849       self.needed_locks[locking.LEVEL_INSTANCE] = \
13850         self.cfg.GetNodeGroupInstances(self.group_uuid)
13851
13852     elif level == locking.LEVEL_NODEGROUP:
13853       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
13854
13855       if self.req_target_uuids:
13856         lock_groups = set([self.group_uuid] + self.req_target_uuids)
13857
13858         # Lock all groups used by instances optimistically; this requires going
13859         # via the node before it's locked, requiring verification later on
13860         lock_groups.update(group_uuid
13861                            for instance_name in
13862                              self.owned_locks(locking.LEVEL_INSTANCE)
13863                            for group_uuid in
13864                              self.cfg.GetInstanceNodeGroups(instance_name))
13865       else:
13866         # No target groups, need to lock all of them
13867         lock_groups = locking.ALL_SET
13868
13869       self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
13870
13871     elif level == locking.LEVEL_NODE:
13872       # This will only lock the nodes in the group to be evacuated which
13873       # contain actual instances
13874       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
13875       self._LockInstancesNodes()
13876
13877       # Lock all nodes in group to be evacuated and target groups
13878       owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
13879       assert self.group_uuid in owned_groups
13880       member_nodes = [node_name
13881                       for group in owned_groups
13882                       for node_name in self.cfg.GetNodeGroup(group).members]
13883       self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
13884
13885   def CheckPrereq(self):
13886     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
13887     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
13888     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
13889
13890     assert owned_groups.issuperset(self.req_target_uuids)
13891     assert self.group_uuid in owned_groups
13892
13893     # Check if locked instances are still correct
13894     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
13895
13896     # Get instance information
13897     self.instances = dict(self.cfg.GetMultiInstanceInfo(owned_instances))
13898
13899     # Check if node groups for locked instances are still correct
13900     for instance_name in owned_instances:
13901       inst = self.instances[instance_name]
13902       assert owned_nodes.issuperset(inst.all_nodes), \
13903         "Instance %s's nodes changed while we kept the lock" % instance_name
13904
13905       inst_groups = _CheckInstanceNodeGroups(self.cfg, instance_name,
13906                                              owned_groups)
13907
13908       assert self.group_uuid in inst_groups, \
13909         "Instance %s has no node in group %s" % (instance_name, self.group_uuid)
13910
13911     if self.req_target_uuids:
13912       # User requested specific target groups
13913       self.target_uuids = self.req_target_uuids
13914     else:
13915       # All groups except the one to be evacuated are potential targets
13916       self.target_uuids = [group_uuid for group_uuid in owned_groups
13917                            if group_uuid != self.group_uuid]
13918
13919       if not self.target_uuids:
13920         raise errors.OpPrereqError("There are no possible target groups",
13921                                    errors.ECODE_INVAL)
13922
13923   def BuildHooksEnv(self):
13924     """Build hooks env.
13925
13926     """
13927     return {
13928       "GROUP_NAME": self.op.group_name,
13929       "TARGET_GROUPS": " ".join(self.target_uuids),
13930       }
13931
13932   def BuildHooksNodes(self):
13933     """Build hooks nodes.
13934
13935     """
13936     mn = self.cfg.GetMasterNode()
13937
13938     assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
13939
13940     run_nodes = [mn] + self.cfg.GetNodeGroup(self.group_uuid).members
13941
13942     return (run_nodes, run_nodes)
13943
13944   def Exec(self, feedback_fn):
13945     instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
13946
13947     assert self.group_uuid not in self.target_uuids
13948
13949     ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_CHG_GROUP,
13950                      instances=instances, target_groups=self.target_uuids)
13951
13952     ial.Run(self.op.iallocator)
13953
13954     if not ial.success:
13955       raise errors.OpPrereqError("Can't compute group evacuation using"
13956                                  " iallocator '%s': %s" %
13957                                  (self.op.iallocator, ial.info),
13958                                  errors.ECODE_NORES)
13959
13960     jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
13961
13962     self.LogInfo("Iallocator returned %s job(s) for evacuating node group %s",
13963                  len(jobs), self.op.group_name)
13964
13965     return ResultWithJobs(jobs)
13966
13967
13968 class TagsLU(NoHooksLU): # pylint: disable=W0223
13969   """Generic tags LU.
13970
13971   This is an abstract class which is the parent of all the other tags LUs.
13972
13973   """
13974   def ExpandNames(self):
13975     self.group_uuid = None
13976     self.needed_locks = {}
13977     if self.op.kind == constants.TAG_NODE:
13978       self.op.name = _ExpandNodeName(self.cfg, self.op.name)
13979       self.needed_locks[locking.LEVEL_NODE] = self.op.name
13980     elif self.op.kind == constants.TAG_INSTANCE:
13981       self.op.name = _ExpandInstanceName(self.cfg, self.op.name)
13982       self.needed_locks[locking.LEVEL_INSTANCE] = self.op.name
13983     elif self.op.kind == constants.TAG_NODEGROUP:
13984       self.group_uuid = self.cfg.LookupNodeGroup(self.op.name)
13985
13986     # FIXME: Acquire BGL for cluster tag operations (as of this writing it's
13987     # not possible to acquire the BGL based on opcode parameters)
13988
13989   def CheckPrereq(self):
13990     """Check prerequisites.
13991
13992     """
13993     if self.op.kind == constants.TAG_CLUSTER:
13994       self.target = self.cfg.GetClusterInfo()
13995     elif self.op.kind == constants.TAG_NODE:
13996       self.target = self.cfg.GetNodeInfo(self.op.name)
13997     elif self.op.kind == constants.TAG_INSTANCE:
13998       self.target = self.cfg.GetInstanceInfo(self.op.name)
13999     elif self.op.kind == constants.TAG_NODEGROUP:
14000       self.target = self.cfg.GetNodeGroup(self.group_uuid)
14001     else:
14002       raise errors.OpPrereqError("Wrong tag type requested (%s)" %
14003                                  str(self.op.kind), errors.ECODE_INVAL)
14004
14005
14006 class LUTagsGet(TagsLU):
14007   """Returns the tags of a given object.
14008
14009   """
14010   REQ_BGL = False
14011
14012   def ExpandNames(self):
14013     TagsLU.ExpandNames(self)
14014
14015     # Share locks as this is only a read operation
14016     self.share_locks = _ShareAll()
14017
14018   def Exec(self, feedback_fn):
14019     """Returns the tag list.
14020
14021     """
14022     return list(self.target.GetTags())
14023
14024
14025 class LUTagsSearch(NoHooksLU):
14026   """Searches the tags for a given pattern.
14027
14028   """
14029   REQ_BGL = False
14030
14031   def ExpandNames(self):
14032     self.needed_locks = {}
14033
14034   def CheckPrereq(self):
14035     """Check prerequisites.
14036
14037     This checks the pattern passed for validity by compiling it.
14038
14039     """
14040     try:
14041       self.re = re.compile(self.op.pattern)
14042     except re.error, err:
14043       raise errors.OpPrereqError("Invalid search pattern '%s': %s" %
14044                                  (self.op.pattern, err), errors.ECODE_INVAL)
14045
14046   def Exec(self, feedback_fn):
14047     """Returns the tag list.
14048
14049     """
14050     cfg = self.cfg
14051     tgts = [("/cluster", cfg.GetClusterInfo())]
14052     ilist = cfg.GetAllInstancesInfo().values()
14053     tgts.extend([("/instances/%s" % i.name, i) for i in ilist])
14054     nlist = cfg.GetAllNodesInfo().values()
14055     tgts.extend([("/nodes/%s" % n.name, n) for n in nlist])
14056     tgts.extend(("/nodegroup/%s" % n.name, n)
14057                 for n in cfg.GetAllNodeGroupsInfo().values())
14058     results = []
14059     for path, target in tgts:
14060       for tag in target.GetTags():
14061         if self.re.search(tag):
14062           results.append((path, tag))
14063     return results
14064
14065
14066 class LUTagsSet(TagsLU):
14067   """Sets a tag on a given object.
14068
14069   """
14070   REQ_BGL = False
14071
14072   def CheckPrereq(self):
14073     """Check prerequisites.
14074
14075     This checks the type and length of the tag name and value.
14076
14077     """
14078     TagsLU.CheckPrereq(self)
14079     for tag in self.op.tags:
14080       objects.TaggableObject.ValidateTag(tag)
14081
14082   def Exec(self, feedback_fn):
14083     """Sets the tag.
14084
14085     """
14086     try:
14087       for tag in self.op.tags:
14088         self.target.AddTag(tag)
14089     except errors.TagError, err:
14090       raise errors.OpExecError("Error while setting tag: %s" % str(err))
14091     self.cfg.Update(self.target, feedback_fn)
14092
14093
14094 class LUTagsDel(TagsLU):
14095   """Delete a list of tags from a given object.
14096
14097   """
14098   REQ_BGL = False
14099
14100   def CheckPrereq(self):
14101     """Check prerequisites.
14102
14103     This checks that we have the given tag.
14104
14105     """
14106     TagsLU.CheckPrereq(self)
14107     for tag in self.op.tags:
14108       objects.TaggableObject.ValidateTag(tag)
14109     del_tags = frozenset(self.op.tags)
14110     cur_tags = self.target.GetTags()
14111
14112     diff_tags = del_tags - cur_tags
14113     if diff_tags:
14114       diff_names = ("'%s'" % i for i in sorted(diff_tags))
14115       raise errors.OpPrereqError("Tag(s) %s not found" %
14116                                  (utils.CommaJoin(diff_names), ),
14117                                  errors.ECODE_NOENT)
14118
14119   def Exec(self, feedback_fn):
14120     """Remove the tag from the object.
14121
14122     """
14123     for tag in self.op.tags:
14124       self.target.RemoveTag(tag)
14125     self.cfg.Update(self.target, feedback_fn)
14126
14127
14128 class LUTestDelay(NoHooksLU):
14129   """Sleep for a specified amount of time.
14130
14131   This LU sleeps on the master and/or nodes for a specified amount of
14132   time.
14133
14134   """
14135   REQ_BGL = False
14136
14137   def ExpandNames(self):
14138     """Expand names and set required locks.
14139
14140     This expands the node list, if any.
14141
14142     """
14143     self.needed_locks = {}
14144     if self.op.on_nodes:
14145       # _GetWantedNodes can be used here, but is not always appropriate to use
14146       # this way in ExpandNames. Check LogicalUnit.ExpandNames docstring for
14147       # more information.
14148       self.op.on_nodes = _GetWantedNodes(self, self.op.on_nodes)
14149       self.needed_locks[locking.LEVEL_NODE] = self.op.on_nodes
14150
14151   def _TestDelay(self):
14152     """Do the actual sleep.
14153
14154     """
14155     if self.op.on_master:
14156       if not utils.TestDelay(self.op.duration):
14157         raise errors.OpExecError("Error during master delay test")
14158     if self.op.on_nodes:
14159       result = self.rpc.call_test_delay(self.op.on_nodes, self.op.duration)
14160       for node, node_result in result.items():
14161         node_result.Raise("Failure during rpc call to node %s" % node)
14162
14163   def Exec(self, feedback_fn):
14164     """Execute the test delay opcode, with the wanted repetitions.
14165
14166     """
14167     if self.op.repeat == 0:
14168       self._TestDelay()
14169     else:
14170       top_value = self.op.repeat - 1
14171       for i in range(self.op.repeat):
14172         self.LogInfo("Test delay iteration %d/%d" % (i, top_value))
14173         self._TestDelay()
14174
14175
14176 class LUTestJqueue(NoHooksLU):
14177   """Utility LU to test some aspects of the job queue.
14178
14179   """
14180   REQ_BGL = False
14181
14182   # Must be lower than default timeout for WaitForJobChange to see whether it
14183   # notices changed jobs
14184   _CLIENT_CONNECT_TIMEOUT = 20.0
14185   _CLIENT_CONFIRM_TIMEOUT = 60.0
14186
14187   @classmethod
14188   def _NotifyUsingSocket(cls, cb, errcls):
14189     """Opens a Unix socket and waits for another program to connect.
14190
14191     @type cb: callable
14192     @param cb: Callback to send socket name to client
14193     @type errcls: class
14194     @param errcls: Exception class to use for errors
14195
14196     """
14197     # Using a temporary directory as there's no easy way to create temporary
14198     # sockets without writing a custom loop around tempfile.mktemp and
14199     # socket.bind
14200     tmpdir = tempfile.mkdtemp()
14201     try:
14202       tmpsock = utils.PathJoin(tmpdir, "sock")
14203
14204       logging.debug("Creating temporary socket at %s", tmpsock)
14205       sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
14206       try:
14207         sock.bind(tmpsock)
14208         sock.listen(1)
14209
14210         # Send details to client
14211         cb(tmpsock)
14212
14213         # Wait for client to connect before continuing
14214         sock.settimeout(cls._CLIENT_CONNECT_TIMEOUT)
14215         try:
14216           (conn, _) = sock.accept()
14217         except socket.error, err:
14218           raise errcls("Client didn't connect in time (%s)" % err)
14219       finally:
14220         sock.close()
14221     finally:
14222       # Remove as soon as client is connected
14223       shutil.rmtree(tmpdir)
14224
14225     # Wait for client to close
14226     try:
14227       try:
14228         # pylint: disable=E1101
14229         # Instance of '_socketobject' has no ... member
14230         conn.settimeout(cls._CLIENT_CONFIRM_TIMEOUT)
14231         conn.recv(1)
14232       except socket.error, err:
14233         raise errcls("Client failed to confirm notification (%s)" % err)
14234     finally:
14235       conn.close()
14236
14237   def _SendNotification(self, test, arg, sockname):
14238     """Sends a notification to the client.
14239
14240     @type test: string
14241     @param test: Test name
14242     @param arg: Test argument (depends on test)
14243     @type sockname: string
14244     @param sockname: Socket path
14245
14246     """
14247     self.Log(constants.ELOG_JQUEUE_TEST, (sockname, test, arg))
14248
14249   def _Notify(self, prereq, test, arg):
14250     """Notifies the client of a test.
14251
14252     @type prereq: bool
14253     @param prereq: Whether this is a prereq-phase test
14254     @type test: string
14255     @param test: Test name
14256     @param arg: Test argument (depends on test)
14257
14258     """
14259     if prereq:
14260       errcls = errors.OpPrereqError
14261     else:
14262       errcls = errors.OpExecError
14263
14264     return self._NotifyUsingSocket(compat.partial(self._SendNotification,
14265                                                   test, arg),
14266                                    errcls)
14267
14268   def CheckArguments(self):
14269     self.checkargs_calls = getattr(self, "checkargs_calls", 0) + 1
14270     self.expandnames_calls = 0
14271
14272   def ExpandNames(self):
14273     checkargs_calls = getattr(self, "checkargs_calls", 0)
14274     if checkargs_calls < 1:
14275       raise errors.ProgrammerError("CheckArguments was not called")
14276
14277     self.expandnames_calls += 1
14278
14279     if self.op.notify_waitlock:
14280       self._Notify(True, constants.JQT_EXPANDNAMES, None)
14281
14282     self.LogInfo("Expanding names")
14283
14284     # Get lock on master node (just to get a lock, not for a particular reason)
14285     self.needed_locks = {
14286       locking.LEVEL_NODE: self.cfg.GetMasterNode(),
14287       }
14288
14289   def Exec(self, feedback_fn):
14290     if self.expandnames_calls < 1:
14291       raise errors.ProgrammerError("ExpandNames was not called")
14292
14293     if self.op.notify_exec:
14294       self._Notify(False, constants.JQT_EXEC, None)
14295
14296     self.LogInfo("Executing")
14297
14298     if self.op.log_messages:
14299       self._Notify(False, constants.JQT_STARTMSG, len(self.op.log_messages))
14300       for idx, msg in enumerate(self.op.log_messages):
14301         self.LogInfo("Sending log message %s", idx + 1)
14302         feedback_fn(constants.JQT_MSGPREFIX + msg)
14303         # Report how many test messages have been sent
14304         self._Notify(False, constants.JQT_LOGMSG, idx + 1)
14305
14306     if self.op.fail:
14307       raise errors.OpExecError("Opcode failure was requested")
14308
14309     return True
14310
14311
14312 class IAllocator(object):
14313   """IAllocator framework.
14314
14315   An IAllocator instance has three sets of attributes:
14316     - cfg that is needed to query the cluster
14317     - input data (all members of the _KEYS class attribute are required)
14318     - four buffer attributes (in|out_data|text), that represent the
14319       input (to the external script) in text and data structure format,
14320       and the output from it, again in two formats
14321     - the result variables from the script (success, info, nodes) for
14322       easy usage
14323
14324   """
14325   # pylint: disable=R0902
14326   # lots of instance attributes
14327
14328   def __init__(self, cfg, rpc_runner, mode, **kwargs):
14329     self.cfg = cfg
14330     self.rpc = rpc_runner
14331     # init buffer variables
14332     self.in_text = self.out_text = self.in_data = self.out_data = None
14333     # init all input fields so that pylint is happy
14334     self.mode = mode
14335     self.memory = self.disks = self.disk_template = None
14336     self.os = self.tags = self.nics = self.vcpus = None
14337     self.hypervisor = None
14338     self.relocate_from = None
14339     self.name = None
14340     self.instances = None
14341     self.evac_mode = None
14342     self.target_groups = []
14343     # computed fields
14344     self.required_nodes = None
14345     # init result fields
14346     self.success = self.info = self.result = None
14347
14348     try:
14349       (fn, keydata, self._result_check) = self._MODE_DATA[self.mode]
14350     except KeyError:
14351       raise errors.ProgrammerError("Unknown mode '%s' passed to the"
14352                                    " IAllocator" % self.mode)
14353
14354     keyset = [n for (n, _) in keydata]
14355
14356     for key in kwargs:
14357       if key not in keyset:
14358         raise errors.ProgrammerError("Invalid input parameter '%s' to"
14359                                      " IAllocator" % key)
14360       setattr(self, key, kwargs[key])
14361
14362     for key in keyset:
14363       if key not in kwargs:
14364         raise errors.ProgrammerError("Missing input parameter '%s' to"
14365                                      " IAllocator" % key)
14366     self._BuildInputData(compat.partial(fn, self), keydata)
14367
14368   def _ComputeClusterData(self):
14369     """Compute the generic allocator input data.
14370
14371     This is the data that is independent of the actual operation.
14372
14373     """
14374     cfg = self.cfg
14375     cluster_info = cfg.GetClusterInfo()
14376     # cluster data
14377     data = {
14378       "version": constants.IALLOCATOR_VERSION,
14379       "cluster_name": cfg.GetClusterName(),
14380       "cluster_tags": list(cluster_info.GetTags()),
14381       "enabled_hypervisors": list(cluster_info.enabled_hypervisors),
14382       "ipolicy": cluster_info.ipolicy,
14383       }
14384     ninfo = cfg.GetAllNodesInfo()
14385     iinfo = cfg.GetAllInstancesInfo().values()
14386     i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo]
14387
14388     # node data
14389     node_list = [n.name for n in ninfo.values() if n.vm_capable]
14390
14391     if self.mode == constants.IALLOCATOR_MODE_ALLOC:
14392       hypervisor_name = self.hypervisor
14393     elif self.mode == constants.IALLOCATOR_MODE_RELOC:
14394       hypervisor_name = cfg.GetInstanceInfo(self.name).hypervisor
14395     else:
14396       hypervisor_name = cluster_info.primary_hypervisor
14397
14398     node_data = self.rpc.call_node_info(node_list, [cfg.GetVGName()],
14399                                         [hypervisor_name])
14400     node_iinfo = \
14401       self.rpc.call_all_instances_info(node_list,
14402                                        cluster_info.enabled_hypervisors)
14403
14404     data["nodegroups"] = self._ComputeNodeGroupData(cfg)
14405
14406     config_ndata = self._ComputeBasicNodeData(ninfo)
14407     data["nodes"] = self._ComputeDynamicNodeData(ninfo, node_data, node_iinfo,
14408                                                  i_list, config_ndata)
14409     assert len(data["nodes"]) == len(ninfo), \
14410         "Incomplete node data computed"
14411
14412     data["instances"] = self._ComputeInstanceData(cluster_info, i_list)
14413
14414     self.in_data = data
14415
14416   @staticmethod
14417   def _ComputeNodeGroupData(cfg):
14418     """Compute node groups data.
14419
14420     """
14421     cluster = cfg.GetClusterInfo()
14422     ng = dict((guuid, {
14423       "name": gdata.name,
14424       "alloc_policy": gdata.alloc_policy,
14425       "ipolicy": _CalculateGroupIPolicy(cluster, gdata),
14426       })
14427       for guuid, gdata in cfg.GetAllNodeGroupsInfo().items())
14428
14429     return ng
14430
14431   @staticmethod
14432   def _ComputeBasicNodeData(node_cfg):
14433     """Compute global node data.
14434
14435     @rtype: dict
14436     @returns: a dict of name: (node dict, node config)
14437
14438     """
14439     # fill in static (config-based) values
14440     node_results = dict((ninfo.name, {
14441       "tags": list(ninfo.GetTags()),
14442       "primary_ip": ninfo.primary_ip,
14443       "secondary_ip": ninfo.secondary_ip,
14444       "offline": ninfo.offline,
14445       "drained": ninfo.drained,
14446       "master_candidate": ninfo.master_candidate,
14447       "group": ninfo.group,
14448       "master_capable": ninfo.master_capable,
14449       "vm_capable": ninfo.vm_capable,
14450       })
14451       for ninfo in node_cfg.values())
14452
14453     return node_results
14454
14455   @staticmethod
14456   def _ComputeDynamicNodeData(node_cfg, node_data, node_iinfo, i_list,
14457                               node_results):
14458     """Compute global node data.
14459
14460     @param node_results: the basic node structures as filled from the config
14461
14462     """
14463     #TODO(dynmem): compute the right data on MAX and MIN memory
14464     # make a copy of the current dict
14465     node_results = dict(node_results)
14466     for nname, nresult in node_data.items():
14467       assert nname in node_results, "Missing basic data for node %s" % nname
14468       ninfo = node_cfg[nname]
14469
14470       if not (ninfo.offline or ninfo.drained):
14471         nresult.Raise("Can't get data for node %s" % nname)
14472         node_iinfo[nname].Raise("Can't get node instance info from node %s" %
14473                                 nname)
14474         remote_info = _MakeLegacyNodeInfo(nresult.payload)
14475
14476         for attr in ["memory_total", "memory_free", "memory_dom0",
14477                      "vg_size", "vg_free", "cpu_total"]:
14478           if attr not in remote_info:
14479             raise errors.OpExecError("Node '%s' didn't return attribute"
14480                                      " '%s'" % (nname, attr))
14481           if not isinstance(remote_info[attr], int):
14482             raise errors.OpExecError("Node '%s' returned invalid value"
14483                                      " for '%s': %s" %
14484                                      (nname, attr, remote_info[attr]))
14485         # compute memory used by primary instances
14486         i_p_mem = i_p_up_mem = 0
14487         for iinfo, beinfo in i_list:
14488           if iinfo.primary_node == nname:
14489             i_p_mem += beinfo[constants.BE_MAXMEM]
14490             if iinfo.name not in node_iinfo[nname].payload:
14491               i_used_mem = 0
14492             else:
14493               i_used_mem = int(node_iinfo[nname].payload[iinfo.name]["memory"])
14494             i_mem_diff = beinfo[constants.BE_MAXMEM] - i_used_mem
14495             remote_info["memory_free"] -= max(0, i_mem_diff)
14496
14497             if iinfo.admin_state == constants.ADMINST_UP:
14498               i_p_up_mem += beinfo[constants.BE_MAXMEM]
14499
14500         # compute memory used by instances
14501         pnr_dyn = {
14502           "total_memory": remote_info["memory_total"],
14503           "reserved_memory": remote_info["memory_dom0"],
14504           "free_memory": remote_info["memory_free"],
14505           "total_disk": remote_info["vg_size"],
14506           "free_disk": remote_info["vg_free"],
14507           "total_cpus": remote_info["cpu_total"],
14508           "i_pri_memory": i_p_mem,
14509           "i_pri_up_memory": i_p_up_mem,
14510           }
14511         pnr_dyn.update(node_results[nname])
14512         node_results[nname] = pnr_dyn
14513
14514     return node_results
14515
14516   @staticmethod
14517   def _ComputeInstanceData(cluster_info, i_list):
14518     """Compute global instance data.
14519
14520     """
14521     instance_data = {}
14522     for iinfo, beinfo in i_list:
14523       nic_data = []
14524       for nic in iinfo.nics:
14525         filled_params = cluster_info.SimpleFillNIC(nic.nicparams)
14526         nic_dict = {
14527           "mac": nic.mac,
14528           "ip": nic.ip,
14529           "mode": filled_params[constants.NIC_MODE],
14530           "link": filled_params[constants.NIC_LINK],
14531           }
14532         if filled_params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
14533           nic_dict["bridge"] = filled_params[constants.NIC_LINK]
14534         nic_data.append(nic_dict)
14535       pir = {
14536         "tags": list(iinfo.GetTags()),
14537         "admin_state": iinfo.admin_state,
14538         "vcpus": beinfo[constants.BE_VCPUS],
14539         "memory": beinfo[constants.BE_MAXMEM],
14540         "os": iinfo.os,
14541         "nodes": [iinfo.primary_node] + list(iinfo.secondary_nodes),
14542         "nics": nic_data,
14543         "disks": [{constants.IDISK_SIZE: dsk.size,
14544                    constants.IDISK_MODE: dsk.mode}
14545                   for dsk in iinfo.disks],
14546         "disk_template": iinfo.disk_template,
14547         "hypervisor": iinfo.hypervisor,
14548         }
14549       pir["disk_space_total"] = _ComputeDiskSize(iinfo.disk_template,
14550                                                  pir["disks"])
14551       instance_data[iinfo.name] = pir
14552
14553     return instance_data
14554
14555   def _AddNewInstance(self):
14556     """Add new instance data to allocator structure.
14557
14558     This in combination with _AllocatorGetClusterData will create the
14559     correct structure needed as input for the allocator.
14560
14561     The checks for the completeness of the opcode must have already been
14562     done.
14563
14564     """
14565     disk_space = _ComputeDiskSize(self.disk_template, self.disks)
14566
14567     if self.disk_template in constants.DTS_INT_MIRROR:
14568       self.required_nodes = 2
14569     else:
14570       self.required_nodes = 1
14571
14572     request = {
14573       "name": self.name,
14574       "disk_template": self.disk_template,
14575       "tags": self.tags,
14576       "os": self.os,
14577       "vcpus": self.vcpus,
14578       "memory": self.memory,
14579       "disks": self.disks,
14580       "disk_space_total": disk_space,
14581       "nics": self.nics,
14582       "required_nodes": self.required_nodes,
14583       "hypervisor": self.hypervisor,
14584       }
14585
14586     return request
14587
14588   def _AddRelocateInstance(self):
14589     """Add relocate instance data to allocator structure.
14590
14591     This in combination with _IAllocatorGetClusterData will create the
14592     correct structure needed as input for the allocator.
14593
14594     The checks for the completeness of the opcode must have already been
14595     done.
14596
14597     """
14598     instance = self.cfg.GetInstanceInfo(self.name)
14599     if instance is None:
14600       raise errors.ProgrammerError("Unknown instance '%s' passed to"
14601                                    " IAllocator" % self.name)
14602
14603     if instance.disk_template not in constants.DTS_MIRRORED:
14604       raise errors.OpPrereqError("Can't relocate non-mirrored instances",
14605                                  errors.ECODE_INVAL)
14606
14607     if instance.disk_template in constants.DTS_INT_MIRROR and \
14608         len(instance.secondary_nodes) != 1:
14609       raise errors.OpPrereqError("Instance has not exactly one secondary node",
14610                                  errors.ECODE_STATE)
14611
14612     self.required_nodes = 1
14613     disk_sizes = [{constants.IDISK_SIZE: disk.size} for disk in instance.disks]
14614     disk_space = _ComputeDiskSize(instance.disk_template, disk_sizes)
14615
14616     request = {
14617       "name": self.name,
14618       "disk_space_total": disk_space,
14619       "required_nodes": self.required_nodes,
14620       "relocate_from": self.relocate_from,
14621       }
14622     return request
14623
14624   def _AddNodeEvacuate(self):
14625     """Get data for node-evacuate requests.
14626
14627     """
14628     return {
14629       "instances": self.instances,
14630       "evac_mode": self.evac_mode,
14631       }
14632
14633   def _AddChangeGroup(self):
14634     """Get data for node-evacuate requests.
14635
14636     """
14637     return {
14638       "instances": self.instances,
14639       "target_groups": self.target_groups,
14640       }
14641
14642   def _BuildInputData(self, fn, keydata):
14643     """Build input data structures.
14644
14645     """
14646     self._ComputeClusterData()
14647
14648     request = fn()
14649     request["type"] = self.mode
14650     for keyname, keytype in keydata:
14651       if keyname not in request:
14652         raise errors.ProgrammerError("Request parameter %s is missing" %
14653                                      keyname)
14654       val = request[keyname]
14655       if not keytype(val):
14656         raise errors.ProgrammerError("Request parameter %s doesn't pass"
14657                                      " validation, value %s, expected"
14658                                      " type %s" % (keyname, val, keytype))
14659     self.in_data["request"] = request
14660
14661     self.in_text = serializer.Dump(self.in_data)
14662
14663   _STRING_LIST = ht.TListOf(ht.TString)
14664   _JOB_LIST = ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, {
14665      # pylint: disable=E1101
14666      # Class '...' has no 'OP_ID' member
14667      "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID,
14668                           opcodes.OpInstanceMigrate.OP_ID,
14669                           opcodes.OpInstanceReplaceDisks.OP_ID])
14670      })))
14671
14672   _NEVAC_MOVED = \
14673     ht.TListOf(ht.TAnd(ht.TIsLength(3),
14674                        ht.TItems([ht.TNonEmptyString,
14675                                   ht.TNonEmptyString,
14676                                   ht.TListOf(ht.TNonEmptyString),
14677                                  ])))
14678   _NEVAC_FAILED = \
14679     ht.TListOf(ht.TAnd(ht.TIsLength(2),
14680                        ht.TItems([ht.TNonEmptyString,
14681                                   ht.TMaybeString,
14682                                  ])))
14683   _NEVAC_RESULT = ht.TAnd(ht.TIsLength(3),
14684                           ht.TItems([_NEVAC_MOVED, _NEVAC_FAILED, _JOB_LIST]))
14685
14686   _MODE_DATA = {
14687     constants.IALLOCATOR_MODE_ALLOC:
14688       (_AddNewInstance,
14689        [
14690         ("name", ht.TString),
14691         ("memory", ht.TInt),
14692         ("disks", ht.TListOf(ht.TDict)),
14693         ("disk_template", ht.TString),
14694         ("os", ht.TString),
14695         ("tags", _STRING_LIST),
14696         ("nics", ht.TListOf(ht.TDict)),
14697         ("vcpus", ht.TInt),
14698         ("hypervisor", ht.TString),
14699         ], ht.TList),
14700     constants.IALLOCATOR_MODE_RELOC:
14701       (_AddRelocateInstance,
14702        [("name", ht.TString), ("relocate_from", _STRING_LIST)],
14703        ht.TList),
14704      constants.IALLOCATOR_MODE_NODE_EVAC:
14705       (_AddNodeEvacuate, [
14706         ("instances", _STRING_LIST),
14707         ("evac_mode", ht.TElemOf(constants.IALLOCATOR_NEVAC_MODES)),
14708         ], _NEVAC_RESULT),
14709      constants.IALLOCATOR_MODE_CHG_GROUP:
14710       (_AddChangeGroup, [
14711         ("instances", _STRING_LIST),
14712         ("target_groups", _STRING_LIST),
14713         ], _NEVAC_RESULT),
14714     }
14715
14716   def Run(self, name, validate=True, call_fn=None):
14717     """Run an instance allocator and return the results.
14718
14719     """
14720     if call_fn is None:
14721       call_fn = self.rpc.call_iallocator_runner
14722
14723     result = call_fn(self.cfg.GetMasterNode(), name, self.in_text)
14724     result.Raise("Failure while running the iallocator script")
14725
14726     self.out_text = result.payload
14727     if validate:
14728       self._ValidateResult()
14729
14730   def _ValidateResult(self):
14731     """Process the allocator results.
14732
14733     This will process and if successful save the result in
14734     self.out_data and the other parameters.
14735
14736     """
14737     try:
14738       rdict = serializer.Load(self.out_text)
14739     except Exception, err:
14740       raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
14741
14742     if not isinstance(rdict, dict):
14743       raise errors.OpExecError("Can't parse iallocator results: not a dict")
14744
14745     # TODO: remove backwards compatiblity in later versions
14746     if "nodes" in rdict and "result" not in rdict:
14747       rdict["result"] = rdict["nodes"]
14748       del rdict["nodes"]
14749
14750     for key in "success", "info", "result":
14751       if key not in rdict:
14752         raise errors.OpExecError("Can't parse iallocator results:"
14753                                  " missing key '%s'" % key)
14754       setattr(self, key, rdict[key])
14755
14756     if not self._result_check(self.result):
14757       raise errors.OpExecError("Iallocator returned invalid result,"
14758                                " expected %s, got %s" %
14759                                (self._result_check, self.result),
14760                                errors.ECODE_INVAL)
14761
14762     if self.mode == constants.IALLOCATOR_MODE_RELOC:
14763       assert self.relocate_from is not None
14764       assert self.required_nodes == 1
14765
14766       node2group = dict((name, ndata["group"])
14767                         for (name, ndata) in self.in_data["nodes"].items())
14768
14769       fn = compat.partial(self._NodesToGroups, node2group,
14770                           self.in_data["nodegroups"])
14771
14772       instance = self.cfg.GetInstanceInfo(self.name)
14773       request_groups = fn(self.relocate_from + [instance.primary_node])
14774       result_groups = fn(rdict["result"] + [instance.primary_node])
14775
14776       if self.success and not set(result_groups).issubset(request_groups):
14777         raise errors.OpExecError("Groups of nodes returned by iallocator (%s)"
14778                                  " differ from original groups (%s)" %
14779                                  (utils.CommaJoin(result_groups),
14780                                   utils.CommaJoin(request_groups)))
14781
14782     elif self.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
14783       assert self.evac_mode in constants.IALLOCATOR_NEVAC_MODES
14784
14785     self.out_data = rdict
14786
14787   @staticmethod
14788   def _NodesToGroups(node2group, groups, nodes):
14789     """Returns a list of unique group names for a list of nodes.
14790
14791     @type node2group: dict
14792     @param node2group: Map from node name to group UUID
14793     @type groups: dict
14794     @param groups: Group information
14795     @type nodes: list
14796     @param nodes: Node names
14797
14798     """
14799     result = set()
14800
14801     for node in nodes:
14802       try:
14803         group_uuid = node2group[node]
14804       except KeyError:
14805         # Ignore unknown node
14806         pass
14807       else:
14808         try:
14809           group = groups[group_uuid]
14810         except KeyError:
14811           # Can't find group, let's use UUID
14812           group_name = group_uuid
14813         else:
14814           group_name = group["name"]
14815
14816         result.add(group_name)
14817
14818     return sorted(result)
14819
14820
14821 class LUTestAllocator(NoHooksLU):
14822   """Run allocator tests.
14823
14824   This LU runs the allocator tests
14825
14826   """
14827   def CheckPrereq(self):
14828     """Check prerequisites.
14829
14830     This checks the opcode parameters depending on the director and mode test.
14831
14832     """
14833     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
14834       for attr in ["memory", "disks", "disk_template",
14835                    "os", "tags", "nics", "vcpus"]:
14836         if not hasattr(self.op, attr):
14837           raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
14838                                      attr, errors.ECODE_INVAL)
14839       iname = self.cfg.ExpandInstanceName(self.op.name)
14840       if iname is not None:
14841         raise errors.OpPrereqError("Instance '%s' already in the cluster" %
14842                                    iname, errors.ECODE_EXISTS)
14843       if not isinstance(self.op.nics, list):
14844         raise errors.OpPrereqError("Invalid parameter 'nics'",
14845                                    errors.ECODE_INVAL)
14846       if not isinstance(self.op.disks, list):
14847         raise errors.OpPrereqError("Invalid parameter 'disks'",
14848                                    errors.ECODE_INVAL)
14849       for row in self.op.disks:
14850         if (not isinstance(row, dict) or
14851             constants.IDISK_SIZE not in row or
14852             not isinstance(row[constants.IDISK_SIZE], int) or
14853             constants.IDISK_MODE not in row or
14854             row[constants.IDISK_MODE] not in constants.DISK_ACCESS_SET):
14855           raise errors.OpPrereqError("Invalid contents of the 'disks'"
14856                                      " parameter", errors.ECODE_INVAL)
14857       if self.op.hypervisor is None:
14858         self.op.hypervisor = self.cfg.GetHypervisorType()
14859     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
14860       fname = _ExpandInstanceName(self.cfg, self.op.name)
14861       self.op.name = fname
14862       self.relocate_from = \
14863           list(self.cfg.GetInstanceInfo(fname).secondary_nodes)
14864     elif self.op.mode in (constants.IALLOCATOR_MODE_CHG_GROUP,
14865                           constants.IALLOCATOR_MODE_NODE_EVAC):
14866       if not self.op.instances:
14867         raise errors.OpPrereqError("Missing instances", errors.ECODE_INVAL)
14868       self.op.instances = _GetWantedInstances(self, self.op.instances)
14869     else:
14870       raise errors.OpPrereqError("Invalid test allocator mode '%s'" %
14871                                  self.op.mode, errors.ECODE_INVAL)
14872
14873     if self.op.direction == constants.IALLOCATOR_DIR_OUT:
14874       if self.op.allocator is None:
14875         raise errors.OpPrereqError("Missing allocator name",
14876                                    errors.ECODE_INVAL)
14877     elif self.op.direction != constants.IALLOCATOR_DIR_IN:
14878       raise errors.OpPrereqError("Wrong allocator test '%s'" %
14879                                  self.op.direction, errors.ECODE_INVAL)
14880
14881   def Exec(self, feedback_fn):
14882     """Run the allocator test.
14883
14884     """
14885     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
14886       ial = IAllocator(self.cfg, self.rpc,
14887                        mode=self.op.mode,
14888                        name=self.op.name,
14889                        memory=self.op.memory,
14890                        disks=self.op.disks,
14891                        disk_template=self.op.disk_template,
14892                        os=self.op.os,
14893                        tags=self.op.tags,
14894                        nics=self.op.nics,
14895                        vcpus=self.op.vcpus,
14896                        hypervisor=self.op.hypervisor,
14897                        )
14898     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
14899       ial = IAllocator(self.cfg, self.rpc,
14900                        mode=self.op.mode,
14901                        name=self.op.name,
14902                        relocate_from=list(self.relocate_from),
14903                        )
14904     elif self.op.mode == constants.IALLOCATOR_MODE_CHG_GROUP:
14905       ial = IAllocator(self.cfg, self.rpc,
14906                        mode=self.op.mode,
14907                        instances=self.op.instances,
14908                        target_groups=self.op.target_groups)
14909     elif self.op.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
14910       ial = IAllocator(self.cfg, self.rpc,
14911                        mode=self.op.mode,
14912                        instances=self.op.instances,
14913                        evac_mode=self.op.evac_mode)
14914     else:
14915       raise errors.ProgrammerError("Uncatched mode %s in"
14916                                    " LUTestAllocator.Exec", self.op.mode)
14917
14918     if self.op.direction == constants.IALLOCATOR_DIR_IN:
14919       result = ial.in_text
14920     else:
14921       ial.Run(self.op.allocator, validate=False)
14922       result = ial.out_text
14923     return result
14924
14925
14926 #: Query type implementations
14927 _QUERY_IMPL = {
14928   constants.QR_INSTANCE: _InstanceQuery,
14929   constants.QR_NODE: _NodeQuery,
14930   constants.QR_GROUP: _GroupQuery,
14931   constants.QR_OS: _OsQuery,
14932   }
14933
14934 assert set(_QUERY_IMPL.keys()) == constants.QR_VIA_OP
14935
14936
14937 def _GetQueryImplementation(name):
14938   """Returns the implemtnation for a query type.
14939
14940   @param name: Query type, must be one of L{constants.QR_VIA_OP}
14941
14942   """
14943   try:
14944     return _QUERY_IMPL[name]
14945   except KeyError:
14946     raise errors.OpPrereqError("Unknown query resource '%s'" % name,
14947                                errors.ECODE_INVAL)