Shared storage node migration
[ganeti-local] / lib / cmdlib.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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-msg=W0201,C0302
25
26 # W0201 since most LU attributes are defined in CheckPrereq or similar
27 # functions
28
29 # C0302: since we have waaaay to 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
44 from ganeti import ssh
45 from ganeti import utils
46 from ganeti import errors
47 from ganeti import hypervisor
48 from ganeti import locking
49 from ganeti import constants
50 from ganeti import objects
51 from ganeti import serializer
52 from ganeti import ssconf
53 from ganeti import uidpool
54 from ganeti import compat
55 from ganeti import masterd
56 from ganeti import netutils
57 from ganeti import query
58 from ganeti import qlang
59 from ganeti import opcodes
60
61 import ganeti.masterd.instance # pylint: disable-msg=W0611
62
63
64 def _SupportsOob(cfg, node):
65   """Tells if node supports OOB.
66
67   @type cfg: L{config.ConfigWriter}
68   @param cfg: The cluster configuration
69   @type node: L{objects.Node}
70   @param node: The node
71   @return: The OOB script if supported or an empty string otherwise
72
73   """
74   return cfg.GetNdParams(node)[constants.ND_OOB_PROGRAM]
75
76
77 # End types
78 class LogicalUnit(object):
79   """Logical Unit base class.
80
81   Subclasses must follow these rules:
82     - implement ExpandNames
83     - implement CheckPrereq (except when tasklets are used)
84     - implement Exec (except when tasklets are used)
85     - implement BuildHooksEnv
86     - redefine HPATH and HTYPE
87     - optionally redefine their run requirements:
88         REQ_BGL: the LU needs to hold the Big Ganeti Lock exclusively
89
90   Note that all commands require root permissions.
91
92   @ivar dry_run_result: the value (if any) that will be returned to the caller
93       in dry-run mode (signalled by opcode dry_run parameter)
94
95   """
96   HPATH = None
97   HTYPE = None
98   REQ_BGL = True
99
100   def __init__(self, processor, op, context, rpc):
101     """Constructor for LogicalUnit.
102
103     This needs to be overridden in derived classes in order to check op
104     validity.
105
106     """
107     self.proc = processor
108     self.op = op
109     self.cfg = context.cfg
110     self.context = context
111     self.rpc = rpc
112     # Dicts used to declare locking needs to mcpu
113     self.needed_locks = None
114     self.acquired_locks = {}
115     self.share_locks = dict.fromkeys(locking.LEVELS, 0)
116     self.add_locks = {}
117     self.remove_locks = {}
118     # Used to force good behavior when calling helper functions
119     self.recalculate_locks = {}
120     self.__ssh = None
121     # logging
122     self.Log = processor.Log # pylint: disable-msg=C0103
123     self.LogWarning = processor.LogWarning # pylint: disable-msg=C0103
124     self.LogInfo = processor.LogInfo # pylint: disable-msg=C0103
125     self.LogStep = processor.LogStep # pylint: disable-msg=C0103
126     # support for dry-run
127     self.dry_run_result = None
128     # support for generic debug attribute
129     if (not hasattr(self.op, "debug_level") or
130         not isinstance(self.op.debug_level, int)):
131       self.op.debug_level = 0
132
133     # Tasklets
134     self.tasklets = None
135
136     # Validate opcode parameters and set defaults
137     self.op.Validate(True)
138
139     self.CheckArguments()
140
141   def __GetSSH(self):
142     """Returns the SshRunner object
143
144     """
145     if not self.__ssh:
146       self.__ssh = ssh.SshRunner(self.cfg.GetClusterName())
147     return self.__ssh
148
149   ssh = property(fget=__GetSSH)
150
151   def CheckArguments(self):
152     """Check syntactic validity for the opcode arguments.
153
154     This method is for doing a simple syntactic check and ensure
155     validity of opcode parameters, without any cluster-related
156     checks. While the same can be accomplished in ExpandNames and/or
157     CheckPrereq, doing these separate is better because:
158
159       - ExpandNames is left as as purely a lock-related function
160       - CheckPrereq is run after we have acquired locks (and possible
161         waited for them)
162
163     The function is allowed to change the self.op attribute so that
164     later methods can no longer worry about missing parameters.
165
166     """
167     pass
168
169   def ExpandNames(self):
170     """Expand names for this LU.
171
172     This method is called before starting to execute the opcode, and it should
173     update all the parameters of the opcode to their canonical form (e.g. a
174     short node name must be fully expanded after this method has successfully
175     completed). This way locking, hooks, logging, etc. can work correctly.
176
177     LUs which implement this method must also populate the self.needed_locks
178     member, as a dict with lock levels as keys, and a list of needed lock names
179     as values. Rules:
180
181       - use an empty dict if you don't need any lock
182       - if you don't need any lock at a particular level omit that level
183       - don't put anything for the BGL level
184       - if you want all locks at a level use locking.ALL_SET as a value
185
186     If you need to share locks (rather than acquire them exclusively) at one
187     level you can modify self.share_locks, setting a true value (usually 1) for
188     that level. By default locks are not shared.
189
190     This function can also define a list of tasklets, which then will be
191     executed in order instead of the usual LU-level CheckPrereq and Exec
192     functions, if those are not defined by the LU.
193
194     Examples::
195
196       # Acquire all nodes and one instance
197       self.needed_locks = {
198         locking.LEVEL_NODE: locking.ALL_SET,
199         locking.LEVEL_INSTANCE: ['instance1.example.com'],
200       }
201       # Acquire just two nodes
202       self.needed_locks = {
203         locking.LEVEL_NODE: ['node1.example.com', 'node2.example.com'],
204       }
205       # Acquire no locks
206       self.needed_locks = {} # No, you can't leave it to the default value None
207
208     """
209     # The implementation of this method is mandatory only if the new LU is
210     # concurrent, so that old LUs don't need to be changed all at the same
211     # time.
212     if self.REQ_BGL:
213       self.needed_locks = {} # Exclusive LUs don't need locks.
214     else:
215       raise NotImplementedError
216
217   def DeclareLocks(self, level):
218     """Declare LU locking needs for a level
219
220     While most LUs can just declare their locking needs at ExpandNames time,
221     sometimes there's the need to calculate some locks after having acquired
222     the ones before. This function is called just before acquiring locks at a
223     particular level, but after acquiring the ones at lower levels, and permits
224     such calculations. It can be used to modify self.needed_locks, and by
225     default it does nothing.
226
227     This function is only called if you have something already set in
228     self.needed_locks for the level.
229
230     @param level: Locking level which is going to be locked
231     @type level: member of ganeti.locking.LEVELS
232
233     """
234
235   def CheckPrereq(self):
236     """Check prerequisites for this LU.
237
238     This method should check that the prerequisites for the execution
239     of this LU are fulfilled. It can do internode communication, but
240     it should be idempotent - no cluster or system changes are
241     allowed.
242
243     The method should raise errors.OpPrereqError in case something is
244     not fulfilled. Its return value is ignored.
245
246     This method should also update all the parameters of the opcode to
247     their canonical form if it hasn't been done by ExpandNames before.
248
249     """
250     if self.tasklets is not None:
251       for (idx, tl) in enumerate(self.tasklets):
252         logging.debug("Checking prerequisites for tasklet %s/%s",
253                       idx + 1, len(self.tasklets))
254         tl.CheckPrereq()
255     else:
256       pass
257
258   def Exec(self, feedback_fn):
259     """Execute the LU.
260
261     This method should implement the actual work. It should raise
262     errors.OpExecError for failures that are somewhat dealt with in
263     code, or expected.
264
265     """
266     if self.tasklets is not None:
267       for (idx, tl) in enumerate(self.tasklets):
268         logging.debug("Executing tasklet %s/%s", idx + 1, len(self.tasklets))
269         tl.Exec(feedback_fn)
270     else:
271       raise NotImplementedError
272
273   def BuildHooksEnv(self):
274     """Build hooks environment for this LU.
275
276     This method should return a three-node tuple consisting of: a dict
277     containing the environment that will be used for running the
278     specific hook for this LU, a list of node names on which the hook
279     should run before the execution, and a list of node names on which
280     the hook should run after the execution.
281
282     The keys of the dict must not have 'GANETI_' prefixed as this will
283     be handled in the hooks runner. Also note additional keys will be
284     added by the hooks runner. If the LU doesn't define any
285     environment, an empty dict (and not None) should be returned.
286
287     No nodes should be returned as an empty list (and not None).
288
289     Note that if the HPATH for a LU class is None, this function will
290     not be called.
291
292     """
293     raise NotImplementedError
294
295   def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result):
296     """Notify the LU about the results of its hooks.
297
298     This method is called every time a hooks phase is executed, and notifies
299     the Logical Unit about the hooks' result. The LU can then use it to alter
300     its result based on the hooks.  By default the method does nothing and the
301     previous result is passed back unchanged but any LU can define it if it
302     wants to use the local cluster hook-scripts somehow.
303
304     @param phase: one of L{constants.HOOKS_PHASE_POST} or
305         L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
306     @param hook_results: the results of the multi-node hooks rpc call
307     @param feedback_fn: function used send feedback back to the caller
308     @param lu_result: the previous Exec result this LU had, or None
309         in the PRE phase
310     @return: the new Exec result, based on the previous result
311         and hook results
312
313     """
314     # API must be kept, thus we ignore the unused argument and could
315     # be a function warnings
316     # pylint: disable-msg=W0613,R0201
317     return lu_result
318
319   def _ExpandAndLockInstance(self):
320     """Helper function to expand and lock an instance.
321
322     Many LUs that work on an instance take its name in self.op.instance_name
323     and need to expand it and then declare the expanded name for locking. This
324     function does it, and then updates self.op.instance_name to the expanded
325     name. It also initializes needed_locks as a dict, if this hasn't been done
326     before.
327
328     """
329     if self.needed_locks is None:
330       self.needed_locks = {}
331     else:
332       assert locking.LEVEL_INSTANCE not in self.needed_locks, \
333         "_ExpandAndLockInstance called with instance-level locks set"
334     self.op.instance_name = _ExpandInstanceName(self.cfg,
335                                                 self.op.instance_name)
336     self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
337
338   def _LockInstancesNodes(self, primary_only=False):
339     """Helper function to declare instances' nodes for locking.
340
341     This function should be called after locking one or more instances to lock
342     their nodes. Its effect is populating self.needed_locks[locking.LEVEL_NODE]
343     with all primary or secondary nodes for instances already locked and
344     present in self.needed_locks[locking.LEVEL_INSTANCE].
345
346     It should be called from DeclareLocks, and for safety only works if
347     self.recalculate_locks[locking.LEVEL_NODE] is set.
348
349     In the future it may grow parameters to just lock some instance's nodes, or
350     to just lock primaries or secondary nodes, if needed.
351
352     If should be called in DeclareLocks in a way similar to::
353
354       if level == locking.LEVEL_NODE:
355         self._LockInstancesNodes()
356
357     @type primary_only: boolean
358     @param primary_only: only lock primary nodes of locked instances
359
360     """
361     assert locking.LEVEL_NODE in self.recalculate_locks, \
362       "_LockInstancesNodes helper function called with no nodes to recalculate"
363
364     # TODO: check if we're really been called with the instance locks held
365
366     # For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the
367     # future we might want to have different behaviors depending on the value
368     # of self.recalculate_locks[locking.LEVEL_NODE]
369     wanted_nodes = []
370     for instance_name in self.acquired_locks[locking.LEVEL_INSTANCE]:
371       instance = self.context.cfg.GetInstanceInfo(instance_name)
372       wanted_nodes.append(instance.primary_node)
373       if not primary_only:
374         wanted_nodes.extend(instance.secondary_nodes)
375
376     if self.recalculate_locks[locking.LEVEL_NODE] == constants.LOCKS_REPLACE:
377       self.needed_locks[locking.LEVEL_NODE] = wanted_nodes
378     elif self.recalculate_locks[locking.LEVEL_NODE] == constants.LOCKS_APPEND:
379       self.needed_locks[locking.LEVEL_NODE].extend(wanted_nodes)
380
381     del self.recalculate_locks[locking.LEVEL_NODE]
382
383
384 class NoHooksLU(LogicalUnit): # pylint: disable-msg=W0223
385   """Simple LU which runs no hooks.
386
387   This LU is intended as a parent for other LogicalUnits which will
388   run no hooks, in order to reduce duplicate code.
389
390   """
391   HPATH = None
392   HTYPE = None
393
394   def BuildHooksEnv(self):
395     """Empty BuildHooksEnv for NoHooksLu.
396
397     This just raises an error.
398
399     """
400     assert False, "BuildHooksEnv called for NoHooksLUs"
401
402
403 class Tasklet:
404   """Tasklet base class.
405
406   Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
407   they can mix legacy code with tasklets. Locking needs to be done in the LU,
408   tasklets know nothing about locks.
409
410   Subclasses must follow these rules:
411     - Implement CheckPrereq
412     - Implement Exec
413
414   """
415   def __init__(self, lu):
416     self.lu = lu
417
418     # Shortcuts
419     self.cfg = lu.cfg
420     self.rpc = lu.rpc
421
422   def CheckPrereq(self):
423     """Check prerequisites for this tasklets.
424
425     This method should check whether the prerequisites for the execution of
426     this tasklet are fulfilled. It can do internode communication, but it
427     should be idempotent - no cluster or system changes are allowed.
428
429     The method should raise errors.OpPrereqError in case something is not
430     fulfilled. Its return value is ignored.
431
432     This method should also update all parameters to their canonical form if it
433     hasn't been done before.
434
435     """
436     pass
437
438   def Exec(self, feedback_fn):
439     """Execute the tasklet.
440
441     This method should implement the actual work. It should raise
442     errors.OpExecError for failures that are somewhat dealt with in code, or
443     expected.
444
445     """
446     raise NotImplementedError
447
448
449 class _QueryBase:
450   """Base for query utility classes.
451
452   """
453   #: Attribute holding field definitions
454   FIELDS = None
455
456   def __init__(self, filter_, fields, use_locking):
457     """Initializes this class.
458
459     """
460     self.use_locking = use_locking
461
462     self.query = query.Query(self.FIELDS, fields, filter_=filter_,
463                              namefield="name")
464     self.requested_data = self.query.RequestedData()
465     self.names = self.query.RequestedNames()
466
467     # Sort only if no names were requested
468     self.sort_by_name = not self.names
469
470     self.do_locking = None
471     self.wanted = None
472
473   def _GetNames(self, lu, all_names, lock_level):
474     """Helper function to determine names asked for in the query.
475
476     """
477     if self.do_locking:
478       names = lu.acquired_locks[lock_level]
479     else:
480       names = all_names
481
482     if self.wanted == locking.ALL_SET:
483       assert not self.names
484       # caller didn't specify names, so ordering is not important
485       return utils.NiceSort(names)
486
487     # caller specified names and we must keep the same order
488     assert self.names
489     assert not self.do_locking or lu.acquired_locks[lock_level]
490
491     missing = set(self.wanted).difference(names)
492     if missing:
493       raise errors.OpExecError("Some items were removed before retrieving"
494                                " their data: %s" % missing)
495
496     # Return expanded names
497     return self.wanted
498
499   @classmethod
500   def FieldsQuery(cls, fields):
501     """Returns list of available fields.
502
503     @return: List of L{objects.QueryFieldDefinition}
504
505     """
506     return query.QueryFields(cls.FIELDS, fields)
507
508   def ExpandNames(self, lu):
509     """Expand names for this query.
510
511     See L{LogicalUnit.ExpandNames}.
512
513     """
514     raise NotImplementedError()
515
516   def DeclareLocks(self, lu, level):
517     """Declare locks for this query.
518
519     See L{LogicalUnit.DeclareLocks}.
520
521     """
522     raise NotImplementedError()
523
524   def _GetQueryData(self, lu):
525     """Collects all data for this query.
526
527     @return: Query data object
528
529     """
530     raise NotImplementedError()
531
532   def NewStyleQuery(self, lu):
533     """Collect data and execute query.
534
535     """
536     return query.GetQueryResponse(self.query, self._GetQueryData(lu),
537                                   sort_by_name=self.sort_by_name)
538
539   def OldStyleQuery(self, lu):
540     """Collect data and execute query.
541
542     """
543     return self.query.OldStyleQuery(self._GetQueryData(lu),
544                                     sort_by_name=self.sort_by_name)
545
546
547 def _GetWantedNodes(lu, nodes):
548   """Returns list of checked and expanded node names.
549
550   @type lu: L{LogicalUnit}
551   @param lu: the logical unit on whose behalf we execute
552   @type nodes: list
553   @param nodes: list of node names or None for all nodes
554   @rtype: list
555   @return: the list of nodes, sorted
556   @raise errors.ProgrammerError: if the nodes parameter is wrong type
557
558   """
559   if nodes:
560     return [_ExpandNodeName(lu.cfg, name) for name in nodes]
561
562   return utils.NiceSort(lu.cfg.GetNodeList())
563
564
565 def _GetWantedInstances(lu, instances):
566   """Returns list of checked and expanded instance names.
567
568   @type lu: L{LogicalUnit}
569   @param lu: the logical unit on whose behalf we execute
570   @type instances: list
571   @param instances: list of instance names or None for all instances
572   @rtype: list
573   @return: the list of instances, sorted
574   @raise errors.OpPrereqError: if the instances parameter is wrong type
575   @raise errors.OpPrereqError: if any of the passed instances is not found
576
577   """
578   if instances:
579     wanted = [_ExpandInstanceName(lu.cfg, name) for name in instances]
580   else:
581     wanted = utils.NiceSort(lu.cfg.GetInstanceList())
582   return wanted
583
584
585 def _GetUpdatedParams(old_params, update_dict,
586                       use_default=True, use_none=False):
587   """Return the new version of a parameter dictionary.
588
589   @type old_params: dict
590   @param old_params: old parameters
591   @type update_dict: dict
592   @param update_dict: dict containing new parameter values, or
593       constants.VALUE_DEFAULT to reset the parameter to its default
594       value
595   @param use_default: boolean
596   @type use_default: whether to recognise L{constants.VALUE_DEFAULT}
597       values as 'to be deleted' values
598   @param use_none: boolean
599   @type use_none: whether to recognise C{None} values as 'to be
600       deleted' values
601   @rtype: dict
602   @return: the new parameter dictionary
603
604   """
605   params_copy = copy.deepcopy(old_params)
606   for key, val in update_dict.iteritems():
607     if ((use_default and val == constants.VALUE_DEFAULT) or
608         (use_none and val is None)):
609       try:
610         del params_copy[key]
611       except KeyError:
612         pass
613     else:
614       params_copy[key] = val
615   return params_copy
616
617
618 def _CheckOutputFields(static, dynamic, selected):
619   """Checks whether all selected fields are valid.
620
621   @type static: L{utils.FieldSet}
622   @param static: static fields set
623   @type dynamic: L{utils.FieldSet}
624   @param dynamic: dynamic fields set
625
626   """
627   f = utils.FieldSet()
628   f.Extend(static)
629   f.Extend(dynamic)
630
631   delta = f.NonMatching(selected)
632   if delta:
633     raise errors.OpPrereqError("Unknown output fields selected: %s"
634                                % ",".join(delta), errors.ECODE_INVAL)
635
636
637 def _CheckGlobalHvParams(params):
638   """Validates that given hypervisor params are not global ones.
639
640   This will ensure that instances don't get customised versions of
641   global params.
642
643   """
644   used_globals = constants.HVC_GLOBALS.intersection(params)
645   if used_globals:
646     msg = ("The following hypervisor parameters are global and cannot"
647            " be customized at instance level, please modify them at"
648            " cluster level: %s" % utils.CommaJoin(used_globals))
649     raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
650
651
652 def _CheckNodeOnline(lu, node, msg=None):
653   """Ensure that a given node is online.
654
655   @param lu: the LU on behalf of which we make the check
656   @param node: the node to check
657   @param msg: if passed, should be a message to replace the default one
658   @raise errors.OpPrereqError: if the node is offline
659
660   """
661   if msg is None:
662     msg = "Can't use offline node"
663   if lu.cfg.GetNodeInfo(node).offline:
664     raise errors.OpPrereqError("%s: %s" % (msg, node), errors.ECODE_STATE)
665
666
667 def _CheckNodeNotDrained(lu, node):
668   """Ensure that a given node is not drained.
669
670   @param lu: the LU on behalf of which we make the check
671   @param node: the node to check
672   @raise errors.OpPrereqError: if the node is drained
673
674   """
675   if lu.cfg.GetNodeInfo(node).drained:
676     raise errors.OpPrereqError("Can't use drained node %s" % node,
677                                errors.ECODE_STATE)
678
679
680 def _CheckNodeVmCapable(lu, node):
681   """Ensure that a given node is vm capable.
682
683   @param lu: the LU on behalf of which we make the check
684   @param node: the node to check
685   @raise errors.OpPrereqError: if the node is not vm capable
686
687   """
688   if not lu.cfg.GetNodeInfo(node).vm_capable:
689     raise errors.OpPrereqError("Can't use non-vm_capable node %s" % node,
690                                errors.ECODE_STATE)
691
692
693 def _CheckNodeHasOS(lu, node, os_name, force_variant):
694   """Ensure that a node supports a given OS.
695
696   @param lu: the LU on behalf of which we make the check
697   @param node: the node to check
698   @param os_name: the OS to query about
699   @param force_variant: whether to ignore variant errors
700   @raise errors.OpPrereqError: if the node is not supporting the OS
701
702   """
703   result = lu.rpc.call_os_get(node, os_name)
704   result.Raise("OS '%s' not in supported OS list for node %s" %
705                (os_name, node),
706                prereq=True, ecode=errors.ECODE_INVAL)
707   if not force_variant:
708     _CheckOSVariant(result.payload, os_name)
709
710
711 def _CheckNodeHasSecondaryIP(lu, node, secondary_ip, prereq):
712   """Ensure that a node has the given secondary ip.
713
714   @type lu: L{LogicalUnit}
715   @param lu: the LU on behalf of which we make the check
716   @type node: string
717   @param node: the node to check
718   @type secondary_ip: string
719   @param secondary_ip: the ip to check
720   @type prereq: boolean
721   @param prereq: whether to throw a prerequisite or an execute error
722   @raise errors.OpPrereqError: if the node doesn't have the ip, and prereq=True
723   @raise errors.OpExecError: if the node doesn't have the ip, and prereq=False
724
725   """
726   result = lu.rpc.call_node_has_ip_address(node, secondary_ip)
727   result.Raise("Failure checking secondary ip on node %s" % node,
728                prereq=prereq, ecode=errors.ECODE_ENVIRON)
729   if not result.payload:
730     msg = ("Node claims it doesn't have the secondary ip you gave (%s),"
731            " please fix and re-run this command" % secondary_ip)
732     if prereq:
733       raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
734     else:
735       raise errors.OpExecError(msg)
736
737
738 def _GetClusterDomainSecret():
739   """Reads the cluster domain secret.
740
741   """
742   return utils.ReadOneLineFile(constants.CLUSTER_DOMAIN_SECRET_FILE,
743                                strict=True)
744
745
746 def _CheckInstanceDown(lu, instance, reason):
747   """Ensure that an instance is not running."""
748   if instance.admin_up:
749     raise errors.OpPrereqError("Instance %s is marked to be up, %s" %
750                                (instance.name, reason), errors.ECODE_STATE)
751
752   pnode = instance.primary_node
753   ins_l = lu.rpc.call_instance_list([pnode], [instance.hypervisor])[pnode]
754   ins_l.Raise("Can't contact node %s for instance information" % pnode,
755               prereq=True, ecode=errors.ECODE_ENVIRON)
756
757   if instance.name in ins_l.payload:
758     raise errors.OpPrereqError("Instance %s is running, %s" %
759                                (instance.name, reason), errors.ECODE_STATE)
760
761
762 def _ExpandItemName(fn, name, kind):
763   """Expand an item name.
764
765   @param fn: the function to use for expansion
766   @param name: requested item name
767   @param kind: text description ('Node' or 'Instance')
768   @return: the resolved (full) name
769   @raise errors.OpPrereqError: if the item is not found
770
771   """
772   full_name = fn(name)
773   if full_name is None:
774     raise errors.OpPrereqError("%s '%s' not known" % (kind, name),
775                                errors.ECODE_NOENT)
776   return full_name
777
778
779 def _ExpandNodeName(cfg, name):
780   """Wrapper over L{_ExpandItemName} for nodes."""
781   return _ExpandItemName(cfg.ExpandNodeName, name, "Node")
782
783
784 def _ExpandInstanceName(cfg, name):
785   """Wrapper over L{_ExpandItemName} for instance."""
786   return _ExpandItemName(cfg.ExpandInstanceName, name, "Instance")
787
788
789 def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
790                           memory, vcpus, nics, disk_template, disks,
791                           bep, hvp, hypervisor_name):
792   """Builds instance related env variables for hooks
793
794   This builds the hook environment from individual variables.
795
796   @type name: string
797   @param name: the name of the instance
798   @type primary_node: string
799   @param primary_node: the name of the instance's primary node
800   @type secondary_nodes: list
801   @param secondary_nodes: list of secondary nodes as strings
802   @type os_type: string
803   @param os_type: the name of the instance's OS
804   @type status: boolean
805   @param status: the should_run status of the instance
806   @type memory: string
807   @param memory: the memory size of the instance
808   @type vcpus: string
809   @param vcpus: the count of VCPUs the instance has
810   @type nics: list
811   @param nics: list of tuples (ip, mac, mode, link) representing
812       the NICs the instance has
813   @type disk_template: string
814   @param disk_template: the disk template of the instance
815   @type disks: list
816   @param disks: the list of (size, mode) pairs
817   @type bep: dict
818   @param bep: the backend parameters for the instance
819   @type hvp: dict
820   @param hvp: the hypervisor parameters for the instance
821   @type hypervisor_name: string
822   @param hypervisor_name: the hypervisor for the instance
823   @rtype: dict
824   @return: the hook environment for this instance
825
826   """
827   if status:
828     str_status = "up"
829   else:
830     str_status = "down"
831   env = {
832     "OP_TARGET": name,
833     "INSTANCE_NAME": name,
834     "INSTANCE_PRIMARY": primary_node,
835     "INSTANCE_SECONDARIES": " ".join(secondary_nodes),
836     "INSTANCE_OS_TYPE": os_type,
837     "INSTANCE_STATUS": str_status,
838     "INSTANCE_MEMORY": memory,
839     "INSTANCE_VCPUS": vcpus,
840     "INSTANCE_DISK_TEMPLATE": disk_template,
841     "INSTANCE_HYPERVISOR": hypervisor_name,
842   }
843
844   if nics:
845     nic_count = len(nics)
846     for idx, (ip, mac, mode, link) in enumerate(nics):
847       if ip is None:
848         ip = ""
849       env["INSTANCE_NIC%d_IP" % idx] = ip
850       env["INSTANCE_NIC%d_MAC" % idx] = mac
851       env["INSTANCE_NIC%d_MODE" % idx] = mode
852       env["INSTANCE_NIC%d_LINK" % idx] = link
853       if mode == constants.NIC_MODE_BRIDGED:
854         env["INSTANCE_NIC%d_BRIDGE" % idx] = link
855   else:
856     nic_count = 0
857
858   env["INSTANCE_NIC_COUNT"] = nic_count
859
860   if disks:
861     disk_count = len(disks)
862     for idx, (size, mode) in enumerate(disks):
863       env["INSTANCE_DISK%d_SIZE" % idx] = size
864       env["INSTANCE_DISK%d_MODE" % idx] = mode
865   else:
866     disk_count = 0
867
868   env["INSTANCE_DISK_COUNT"] = disk_count
869
870   for source, kind in [(bep, "BE"), (hvp, "HV")]:
871     for key, value in source.items():
872       env["INSTANCE_%s_%s" % (kind, key)] = value
873
874   return env
875
876
877 def _NICListToTuple(lu, nics):
878   """Build a list of nic information tuples.
879
880   This list is suitable to be passed to _BuildInstanceHookEnv or as a return
881   value in LUInstanceQueryData.
882
883   @type lu:  L{LogicalUnit}
884   @param lu: the logical unit on whose behalf we execute
885   @type nics: list of L{objects.NIC}
886   @param nics: list of nics to convert to hooks tuples
887
888   """
889   hooks_nics = []
890   cluster = lu.cfg.GetClusterInfo()
891   for nic in nics:
892     ip = nic.ip
893     mac = nic.mac
894     filled_params = cluster.SimpleFillNIC(nic.nicparams)
895     mode = filled_params[constants.NIC_MODE]
896     link = filled_params[constants.NIC_LINK]
897     hooks_nics.append((ip, mac, mode, link))
898   return hooks_nics
899
900
901 def _BuildInstanceHookEnvByObject(lu, instance, override=None):
902   """Builds instance related env variables for hooks from an object.
903
904   @type lu: L{LogicalUnit}
905   @param lu: the logical unit on whose behalf we execute
906   @type instance: L{objects.Instance}
907   @param instance: the instance for which we should build the
908       environment
909   @type override: dict
910   @param override: dictionary with key/values that will override
911       our values
912   @rtype: dict
913   @return: the hook environment dictionary
914
915   """
916   cluster = lu.cfg.GetClusterInfo()
917   bep = cluster.FillBE(instance)
918   hvp = cluster.FillHV(instance)
919   args = {
920     'name': instance.name,
921     'primary_node': instance.primary_node,
922     'secondary_nodes': instance.secondary_nodes,
923     'os_type': instance.os,
924     'status': instance.admin_up,
925     'memory': bep[constants.BE_MEMORY],
926     'vcpus': bep[constants.BE_VCPUS],
927     'nics': _NICListToTuple(lu, instance.nics),
928     'disk_template': instance.disk_template,
929     'disks': [(disk.size, disk.mode) for disk in instance.disks],
930     'bep': bep,
931     'hvp': hvp,
932     'hypervisor_name': instance.hypervisor,
933   }
934   if override:
935     args.update(override)
936   return _BuildInstanceHookEnv(**args) # pylint: disable-msg=W0142
937
938
939 def _AdjustCandidatePool(lu, exceptions):
940   """Adjust the candidate pool after node operations.
941
942   """
943   mod_list = lu.cfg.MaintainCandidatePool(exceptions)
944   if mod_list:
945     lu.LogInfo("Promoted nodes to master candidate role: %s",
946                utils.CommaJoin(node.name for node in mod_list))
947     for name in mod_list:
948       lu.context.ReaddNode(name)
949   mc_now, mc_max, _ = lu.cfg.GetMasterCandidateStats(exceptions)
950   if mc_now > mc_max:
951     lu.LogInfo("Note: more nodes are candidates (%d) than desired (%d)" %
952                (mc_now, mc_max))
953
954
955 def _DecideSelfPromotion(lu, exceptions=None):
956   """Decide whether I should promote myself as a master candidate.
957
958   """
959   cp_size = lu.cfg.GetClusterInfo().candidate_pool_size
960   mc_now, mc_should, _ = lu.cfg.GetMasterCandidateStats(exceptions)
961   # the new node will increase mc_max with one, so:
962   mc_should = min(mc_should + 1, cp_size)
963   return mc_now < mc_should
964
965
966 def _CheckNicsBridgesExist(lu, target_nics, target_node):
967   """Check that the brigdes needed by a list of nics exist.
968
969   """
970   cluster = lu.cfg.GetClusterInfo()
971   paramslist = [cluster.SimpleFillNIC(nic.nicparams) for nic in target_nics]
972   brlist = [params[constants.NIC_LINK] for params in paramslist
973             if params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED]
974   if brlist:
975     result = lu.rpc.call_bridges_exist(target_node, brlist)
976     result.Raise("Error checking bridges on destination node '%s'" %
977                  target_node, prereq=True, ecode=errors.ECODE_ENVIRON)
978
979
980 def _CheckInstanceBridgesExist(lu, instance, node=None):
981   """Check that the brigdes needed by an instance exist.
982
983   """
984   if node is None:
985     node = instance.primary_node
986   _CheckNicsBridgesExist(lu, instance.nics, node)
987
988
989 def _CheckOSVariant(os_obj, name):
990   """Check whether an OS name conforms to the os variants specification.
991
992   @type os_obj: L{objects.OS}
993   @param os_obj: OS object to check
994   @type name: string
995   @param name: OS name passed by the user, to check for validity
996
997   """
998   if not os_obj.supported_variants:
999     return
1000   variant = objects.OS.GetVariant(name)
1001   if not variant:
1002     raise errors.OpPrereqError("OS name must include a variant",
1003                                errors.ECODE_INVAL)
1004
1005   if variant not in os_obj.supported_variants:
1006     raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
1007
1008
1009 def _GetNodeInstancesInner(cfg, fn):
1010   return [i for i in cfg.GetAllInstancesInfo().values() if fn(i)]
1011
1012
1013 def _GetNodeInstances(cfg, node_name):
1014   """Returns a list of all primary and secondary instances on a node.
1015
1016   """
1017
1018   return _GetNodeInstancesInner(cfg, lambda inst: node_name in inst.all_nodes)
1019
1020
1021 def _GetNodePrimaryInstances(cfg, node_name):
1022   """Returns primary instances on a node.
1023
1024   """
1025   return _GetNodeInstancesInner(cfg,
1026                                 lambda inst: node_name == inst.primary_node)
1027
1028
1029 def _GetNodeSecondaryInstances(cfg, node_name):
1030   """Returns secondary instances on a node.
1031
1032   """
1033   return _GetNodeInstancesInner(cfg,
1034                                 lambda inst: node_name in inst.secondary_nodes)
1035
1036
1037 def _GetStorageTypeArgs(cfg, storage_type):
1038   """Returns the arguments for a storage type.
1039
1040   """
1041   # Special case for file storage
1042   if storage_type == constants.ST_FILE:
1043     # storage.FileStorage wants a list of storage directories
1044     return [[cfg.GetFileStorageDir(), cfg.GetSharedFileStorageDir()]]
1045
1046   return []
1047
1048
1049 def _FindFaultyInstanceDisks(cfg, rpc, instance, node_name, prereq):
1050   faulty = []
1051
1052   for dev in instance.disks:
1053     cfg.SetDiskID(dev, node_name)
1054
1055   result = rpc.call_blockdev_getmirrorstatus(node_name, instance.disks)
1056   result.Raise("Failed to get disk status from node %s" % node_name,
1057                prereq=prereq, ecode=errors.ECODE_ENVIRON)
1058
1059   for idx, bdev_status in enumerate(result.payload):
1060     if bdev_status and bdev_status.ldisk_status == constants.LDS_FAULTY:
1061       faulty.append(idx)
1062
1063   return faulty
1064
1065
1066 def _CheckIAllocatorOrNode(lu, iallocator_slot, node_slot):
1067   """Check the sanity of iallocator and node arguments and use the
1068   cluster-wide iallocator if appropriate.
1069
1070   Check that at most one of (iallocator, node) is specified. If none is
1071   specified, then the LU's opcode's iallocator slot is filled with the
1072   cluster-wide default iallocator.
1073
1074   @type iallocator_slot: string
1075   @param iallocator_slot: the name of the opcode iallocator slot
1076   @type node_slot: string
1077   @param node_slot: the name of the opcode target node slot
1078
1079   """
1080   node = getattr(lu.op, node_slot, None)
1081   iallocator = getattr(lu.op, iallocator_slot, None)
1082
1083   if node is not None and iallocator is not None:
1084     raise errors.OpPrereqError("Do not specify both, iallocator and node.",
1085                                errors.ECODE_INVAL)
1086   elif node is None and iallocator is None:
1087     default_iallocator = lu.cfg.GetDefaultIAllocator()
1088     if default_iallocator:
1089       setattr(lu.op, iallocator_slot, default_iallocator)
1090     else:
1091       raise errors.OpPrereqError("No iallocator or node given and no"
1092                                  " cluster-wide default iallocator found."
1093                                  " Please specify either an iallocator or a"
1094                                  " node, or set a cluster-wide default"
1095                                  " iallocator.")
1096
1097
1098 class LUClusterPostInit(LogicalUnit):
1099   """Logical unit for running hooks after cluster initialization.
1100
1101   """
1102   HPATH = "cluster-init"
1103   HTYPE = constants.HTYPE_CLUSTER
1104
1105   def BuildHooksEnv(self):
1106     """Build hooks env.
1107
1108     """
1109     env = {"OP_TARGET": self.cfg.GetClusterName()}
1110     mn = self.cfg.GetMasterNode()
1111     return env, [], [mn]
1112
1113   def Exec(self, feedback_fn):
1114     """Nothing to do.
1115
1116     """
1117     return True
1118
1119
1120 class LUClusterDestroy(LogicalUnit):
1121   """Logical unit for destroying the cluster.
1122
1123   """
1124   HPATH = "cluster-destroy"
1125   HTYPE = constants.HTYPE_CLUSTER
1126
1127   def BuildHooksEnv(self):
1128     """Build hooks env.
1129
1130     """
1131     env = {"OP_TARGET": self.cfg.GetClusterName()}
1132     return env, [], []
1133
1134   def CheckPrereq(self):
1135     """Check prerequisites.
1136
1137     This checks whether the cluster is empty.
1138
1139     Any errors are signaled by raising errors.OpPrereqError.
1140
1141     """
1142     master = self.cfg.GetMasterNode()
1143
1144     nodelist = self.cfg.GetNodeList()
1145     if len(nodelist) != 1 or nodelist[0] != master:
1146       raise errors.OpPrereqError("There are still %d node(s) in"
1147                                  " this cluster." % (len(nodelist) - 1),
1148                                  errors.ECODE_INVAL)
1149     instancelist = self.cfg.GetInstanceList()
1150     if instancelist:
1151       raise errors.OpPrereqError("There are still %d instance(s) in"
1152                                  " this cluster." % len(instancelist),
1153                                  errors.ECODE_INVAL)
1154
1155   def Exec(self, feedback_fn):
1156     """Destroys the cluster.
1157
1158     """
1159     master = self.cfg.GetMasterNode()
1160
1161     # Run post hooks on master node before it's removed
1162     hm = self.proc.hmclass(self.rpc.call_hooks_runner, self)
1163     try:
1164       hm.RunPhase(constants.HOOKS_PHASE_POST, [master])
1165     except:
1166       # pylint: disable-msg=W0702
1167       self.LogWarning("Errors occurred running hooks on %s" % master)
1168
1169     result = self.rpc.call_node_stop_master(master, False)
1170     result.Raise("Could not disable the master role")
1171
1172     return master
1173
1174
1175 def _VerifyCertificate(filename):
1176   """Verifies a certificate for LUClusterVerify.
1177
1178   @type filename: string
1179   @param filename: Path to PEM file
1180
1181   """
1182   try:
1183     cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
1184                                            utils.ReadFile(filename))
1185   except Exception, err: # pylint: disable-msg=W0703
1186     return (LUClusterVerify.ETYPE_ERROR,
1187             "Failed to load X509 certificate %s: %s" % (filename, err))
1188
1189   (errcode, msg) = \
1190     utils.VerifyX509Certificate(cert, constants.SSL_CERT_EXPIRATION_WARN,
1191                                 constants.SSL_CERT_EXPIRATION_ERROR)
1192
1193   if msg:
1194     fnamemsg = "While verifying %s: %s" % (filename, msg)
1195   else:
1196     fnamemsg = None
1197
1198   if errcode is None:
1199     return (None, fnamemsg)
1200   elif errcode == utils.CERT_WARNING:
1201     return (LUClusterVerify.ETYPE_WARNING, fnamemsg)
1202   elif errcode == utils.CERT_ERROR:
1203     return (LUClusterVerify.ETYPE_ERROR, fnamemsg)
1204
1205   raise errors.ProgrammerError("Unhandled certificate error code %r" % errcode)
1206
1207
1208 class LUClusterVerify(LogicalUnit):
1209   """Verifies the cluster status.
1210
1211   """
1212   HPATH = "cluster-verify"
1213   HTYPE = constants.HTYPE_CLUSTER
1214   REQ_BGL = False
1215
1216   TCLUSTER = "cluster"
1217   TNODE = "node"
1218   TINSTANCE = "instance"
1219
1220   ECLUSTERCFG = (TCLUSTER, "ECLUSTERCFG")
1221   ECLUSTERCERT = (TCLUSTER, "ECLUSTERCERT")
1222   EINSTANCEBADNODE = (TINSTANCE, "EINSTANCEBADNODE")
1223   EINSTANCEDOWN = (TINSTANCE, "EINSTANCEDOWN")
1224   EINSTANCELAYOUT = (TINSTANCE, "EINSTANCELAYOUT")
1225   EINSTANCEMISSINGDISK = (TINSTANCE, "EINSTANCEMISSINGDISK")
1226   EINSTANCEFAULTYDISK = (TINSTANCE, "EINSTANCEFAULTYDISK")
1227   EINSTANCEWRONGNODE = (TINSTANCE, "EINSTANCEWRONGNODE")
1228   EINSTANCESPLITGROUPS = (TINSTANCE, "EINSTANCESPLITGROUPS")
1229   ENODEDRBD = (TNODE, "ENODEDRBD")
1230   ENODEDRBDHELPER = (TNODE, "ENODEDRBDHELPER")
1231   ENODEFILECHECK = (TNODE, "ENODEFILECHECK")
1232   ENODEHOOKS = (TNODE, "ENODEHOOKS")
1233   ENODEHV = (TNODE, "ENODEHV")
1234   ENODELVM = (TNODE, "ENODELVM")
1235   ENODEN1 = (TNODE, "ENODEN1")
1236   ENODENET = (TNODE, "ENODENET")
1237   ENODEOS = (TNODE, "ENODEOS")
1238   ENODEORPHANINSTANCE = (TNODE, "ENODEORPHANINSTANCE")
1239   ENODEORPHANLV = (TNODE, "ENODEORPHANLV")
1240   ENODERPC = (TNODE, "ENODERPC")
1241   ENODESSH = (TNODE, "ENODESSH")
1242   ENODEVERSION = (TNODE, "ENODEVERSION")
1243   ENODESETUP = (TNODE, "ENODESETUP")
1244   ENODETIME = (TNODE, "ENODETIME")
1245   ENODEOOBPATH = (TNODE, "ENODEOOBPATH")
1246
1247   ETYPE_FIELD = "code"
1248   ETYPE_ERROR = "ERROR"
1249   ETYPE_WARNING = "WARNING"
1250
1251   _HOOKS_INDENT_RE = re.compile("^", re.M)
1252
1253   class NodeImage(object):
1254     """A class representing the logical and physical status of a node.
1255
1256     @type name: string
1257     @ivar name: the node name to which this object refers
1258     @ivar volumes: a structure as returned from
1259         L{ganeti.backend.GetVolumeList} (runtime)
1260     @ivar instances: a list of running instances (runtime)
1261     @ivar pinst: list of configured primary instances (config)
1262     @ivar sinst: list of configured secondary instances (config)
1263     @ivar sbp: dictionary of {primary-node: list of instances} for all
1264         instances for which this node is secondary (config)
1265     @ivar mfree: free memory, as reported by hypervisor (runtime)
1266     @ivar dfree: free disk, as reported by the node (runtime)
1267     @ivar offline: the offline status (config)
1268     @type rpc_fail: boolean
1269     @ivar rpc_fail: whether the RPC verify call was successfull (overall,
1270         not whether the individual keys were correct) (runtime)
1271     @type lvm_fail: boolean
1272     @ivar lvm_fail: whether the RPC call didn't return valid LVM data
1273     @type hyp_fail: boolean
1274     @ivar hyp_fail: whether the RPC call didn't return the instance list
1275     @type ghost: boolean
1276     @ivar ghost: whether this is a known node or not (config)
1277     @type os_fail: boolean
1278     @ivar os_fail: whether the RPC call didn't return valid OS data
1279     @type oslist: list
1280     @ivar oslist: list of OSes as diagnosed by DiagnoseOS
1281     @type vm_capable: boolean
1282     @ivar vm_capable: whether the node can host instances
1283
1284     """
1285     def __init__(self, offline=False, name=None, vm_capable=True):
1286       self.name = name
1287       self.volumes = {}
1288       self.instances = []
1289       self.pinst = []
1290       self.sinst = []
1291       self.sbp = {}
1292       self.mfree = 0
1293       self.dfree = 0
1294       self.offline = offline
1295       self.vm_capable = vm_capable
1296       self.rpc_fail = False
1297       self.lvm_fail = False
1298       self.hyp_fail = False
1299       self.ghost = False
1300       self.os_fail = False
1301       self.oslist = {}
1302
1303   def ExpandNames(self):
1304     self.needed_locks = {
1305       locking.LEVEL_NODE: locking.ALL_SET,
1306       locking.LEVEL_INSTANCE: locking.ALL_SET,
1307     }
1308     self.share_locks = dict.fromkeys(locking.LEVELS, 1)
1309
1310   def _Error(self, ecode, item, msg, *args, **kwargs):
1311     """Format an error message.
1312
1313     Based on the opcode's error_codes parameter, either format a
1314     parseable error code, or a simpler error string.
1315
1316     This must be called only from Exec and functions called from Exec.
1317
1318     """
1319     ltype = kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR)
1320     itype, etxt = ecode
1321     # first complete the msg
1322     if args:
1323       msg = msg % args
1324     # then format the whole message
1325     if self.op.error_codes:
1326       msg = "%s:%s:%s:%s:%s" % (ltype, etxt, itype, item, msg)
1327     else:
1328       if item:
1329         item = " " + item
1330       else:
1331         item = ""
1332       msg = "%s: %s%s: %s" % (ltype, itype, item, msg)
1333     # and finally report it via the feedback_fn
1334     self._feedback_fn("  - %s" % msg)
1335
1336   def _ErrorIf(self, cond, *args, **kwargs):
1337     """Log an error message if the passed condition is True.
1338
1339     """
1340     cond = bool(cond) or self.op.debug_simulate_errors
1341     if cond:
1342       self._Error(*args, **kwargs)
1343     # do not mark the operation as failed for WARN cases only
1344     if kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR) == self.ETYPE_ERROR:
1345       self.bad = self.bad or cond
1346
1347   def _VerifyNode(self, ninfo, nresult):
1348     """Perform some basic validation on data returned from a node.
1349
1350       - check the result data structure is well formed and has all the
1351         mandatory fields
1352       - check ganeti version
1353
1354     @type ninfo: L{objects.Node}
1355     @param ninfo: the node to check
1356     @param nresult: the results from the node
1357     @rtype: boolean
1358     @return: whether overall this call was successful (and we can expect
1359          reasonable values in the respose)
1360
1361     """
1362     node = ninfo.name
1363     _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1364
1365     # main result, nresult should be a non-empty dict
1366     test = not nresult or not isinstance(nresult, dict)
1367     _ErrorIf(test, self.ENODERPC, node,
1368                   "unable to verify node: no data returned")
1369     if test:
1370       return False
1371
1372     # compares ganeti version
1373     local_version = constants.PROTOCOL_VERSION
1374     remote_version = nresult.get("version", None)
1375     test = not (remote_version and
1376                 isinstance(remote_version, (list, tuple)) and
1377                 len(remote_version) == 2)
1378     _ErrorIf(test, self.ENODERPC, node,
1379              "connection to node returned invalid data")
1380     if test:
1381       return False
1382
1383     test = local_version != remote_version[0]
1384     _ErrorIf(test, self.ENODEVERSION, node,
1385              "incompatible protocol versions: master %s,"
1386              " node %s", local_version, remote_version[0])
1387     if test:
1388       return False
1389
1390     # node seems compatible, we can actually try to look into its results
1391
1392     # full package version
1393     self._ErrorIf(constants.RELEASE_VERSION != remote_version[1],
1394                   self.ENODEVERSION, node,
1395                   "software version mismatch: master %s, node %s",
1396                   constants.RELEASE_VERSION, remote_version[1],
1397                   code=self.ETYPE_WARNING)
1398
1399     hyp_result = nresult.get(constants.NV_HYPERVISOR, None)
1400     if ninfo.vm_capable and isinstance(hyp_result, dict):
1401       for hv_name, hv_result in hyp_result.iteritems():
1402         test = hv_result is not None
1403         _ErrorIf(test, self.ENODEHV, node,
1404                  "hypervisor %s verify failure: '%s'", hv_name, hv_result)
1405
1406     hvp_result = nresult.get(constants.NV_HVPARAMS, None)
1407     if ninfo.vm_capable and isinstance(hvp_result, list):
1408       for item, hv_name, hv_result in hvp_result:
1409         _ErrorIf(True, self.ENODEHV, node,
1410                  "hypervisor %s parameter verify failure (source %s): %s",
1411                  hv_name, item, hv_result)
1412
1413     test = nresult.get(constants.NV_NODESETUP,
1414                            ["Missing NODESETUP results"])
1415     _ErrorIf(test, self.ENODESETUP, node, "node setup error: %s",
1416              "; ".join(test))
1417
1418     return True
1419
1420   def _VerifyNodeTime(self, ninfo, nresult,
1421                       nvinfo_starttime, nvinfo_endtime):
1422     """Check the node time.
1423
1424     @type ninfo: L{objects.Node}
1425     @param ninfo: the node to check
1426     @param nresult: the remote results for the node
1427     @param nvinfo_starttime: the start time of the RPC call
1428     @param nvinfo_endtime: the end time of the RPC call
1429
1430     """
1431     node = ninfo.name
1432     _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1433
1434     ntime = nresult.get(constants.NV_TIME, None)
1435     try:
1436       ntime_merged = utils.MergeTime(ntime)
1437     except (ValueError, TypeError):
1438       _ErrorIf(True, self.ENODETIME, node, "Node returned invalid time")
1439       return
1440
1441     if ntime_merged < (nvinfo_starttime - constants.NODE_MAX_CLOCK_SKEW):
1442       ntime_diff = "%.01fs" % abs(nvinfo_starttime - ntime_merged)
1443     elif ntime_merged > (nvinfo_endtime + constants.NODE_MAX_CLOCK_SKEW):
1444       ntime_diff = "%.01fs" % abs(ntime_merged - nvinfo_endtime)
1445     else:
1446       ntime_diff = None
1447
1448     _ErrorIf(ntime_diff is not None, self.ENODETIME, node,
1449              "Node time diverges by at least %s from master node time",
1450              ntime_diff)
1451
1452   def _VerifyNodeLVM(self, ninfo, nresult, vg_name):
1453     """Check the node time.
1454
1455     @type ninfo: L{objects.Node}
1456     @param ninfo: the node to check
1457     @param nresult: the remote results for the node
1458     @param vg_name: the configured VG name
1459
1460     """
1461     if vg_name is None:
1462       return
1463
1464     node = ninfo.name
1465     _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1466
1467     # checks vg existence and size > 20G
1468     vglist = nresult.get(constants.NV_VGLIST, None)
1469     test = not vglist
1470     _ErrorIf(test, self.ENODELVM, node, "unable to check volume groups")
1471     if not test:
1472       vgstatus = utils.CheckVolumeGroupSize(vglist, vg_name,
1473                                             constants.MIN_VG_SIZE)
1474       _ErrorIf(vgstatus, self.ENODELVM, node, vgstatus)
1475
1476     # check pv names
1477     pvlist = nresult.get(constants.NV_PVLIST, None)
1478     test = pvlist is None
1479     _ErrorIf(test, self.ENODELVM, node, "Can't get PV list from node")
1480     if not test:
1481       # check that ':' is not present in PV names, since it's a
1482       # special character for lvcreate (denotes the range of PEs to
1483       # use on the PV)
1484       for _, pvname, owner_vg in pvlist:
1485         test = ":" in pvname
1486         _ErrorIf(test, self.ENODELVM, node, "Invalid character ':' in PV"
1487                  " '%s' of VG '%s'", pvname, owner_vg)
1488
1489   def _VerifyNodeNetwork(self, ninfo, nresult):
1490     """Check the node time.
1491
1492     @type ninfo: L{objects.Node}
1493     @param ninfo: the node to check
1494     @param nresult: the remote results for the node
1495
1496     """
1497     node = ninfo.name
1498     _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1499
1500     test = constants.NV_NODELIST not in nresult
1501     _ErrorIf(test, self.ENODESSH, node,
1502              "node hasn't returned node ssh connectivity data")
1503     if not test:
1504       if nresult[constants.NV_NODELIST]:
1505         for a_node, a_msg in nresult[constants.NV_NODELIST].items():
1506           _ErrorIf(True, self.ENODESSH, node,
1507                    "ssh communication with node '%s': %s", a_node, a_msg)
1508
1509     test = constants.NV_NODENETTEST not in nresult
1510     _ErrorIf(test, self.ENODENET, node,
1511              "node hasn't returned node tcp connectivity data")
1512     if not test:
1513       if nresult[constants.NV_NODENETTEST]:
1514         nlist = utils.NiceSort(nresult[constants.NV_NODENETTEST].keys())
1515         for anode in nlist:
1516           _ErrorIf(True, self.ENODENET, node,
1517                    "tcp communication with node '%s': %s",
1518                    anode, nresult[constants.NV_NODENETTEST][anode])
1519
1520     test = constants.NV_MASTERIP not in nresult
1521     _ErrorIf(test, self.ENODENET, node,
1522              "node hasn't returned node master IP reachability data")
1523     if not test:
1524       if not nresult[constants.NV_MASTERIP]:
1525         if node == self.master_node:
1526           msg = "the master node cannot reach the master IP (not configured?)"
1527         else:
1528           msg = "cannot reach the master IP"
1529         _ErrorIf(True, self.ENODENET, node, msg)
1530
1531   def _VerifyInstance(self, instance, instanceconfig, node_image,
1532                       diskstatus):
1533     """Verify an instance.
1534
1535     This function checks to see if the required block devices are
1536     available on the instance's node.
1537
1538     """
1539     _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1540     node_current = instanceconfig.primary_node
1541
1542     node_vol_should = {}
1543     instanceconfig.MapLVsByNode(node_vol_should)
1544
1545     for node in node_vol_should:
1546       n_img = node_image[node]
1547       if n_img.offline or n_img.rpc_fail or n_img.lvm_fail:
1548         # ignore missing volumes on offline or broken nodes
1549         continue
1550       for volume in node_vol_should[node]:
1551         test = volume not in n_img.volumes
1552         _ErrorIf(test, self.EINSTANCEMISSINGDISK, instance,
1553                  "volume %s missing on node %s", volume, node)
1554
1555     if instanceconfig.admin_up:
1556       pri_img = node_image[node_current]
1557       test = instance not in pri_img.instances and not pri_img.offline
1558       _ErrorIf(test, self.EINSTANCEDOWN, instance,
1559                "instance not running on its primary node %s",
1560                node_current)
1561
1562     for node, n_img in node_image.items():
1563       if node != node_current:
1564         test = instance in n_img.instances
1565         _ErrorIf(test, self.EINSTANCEWRONGNODE, instance,
1566                  "instance should not run on node %s", node)
1567
1568     diskdata = [(nname, success, status, idx)
1569                 for (nname, disks) in diskstatus.items()
1570                 for idx, (success, status) in enumerate(disks)]
1571
1572     for nname, success, bdev_status, idx in diskdata:
1573       # the 'ghost node' construction in Exec() ensures that we have a
1574       # node here
1575       snode = node_image[nname]
1576       bad_snode = snode.ghost or snode.offline
1577       _ErrorIf(instanceconfig.admin_up and not success and not bad_snode,
1578                self.EINSTANCEFAULTYDISK, instance,
1579                "couldn't retrieve status for disk/%s on %s: %s",
1580                idx, nname, bdev_status)
1581       _ErrorIf((instanceconfig.admin_up and success and
1582                 bdev_status.ldisk_status == constants.LDS_FAULTY),
1583                self.EINSTANCEFAULTYDISK, instance,
1584                "disk/%s on %s is faulty", idx, nname)
1585
1586   def _VerifyOrphanVolumes(self, node_vol_should, node_image, reserved):
1587     """Verify if there are any unknown volumes in the cluster.
1588
1589     The .os, .swap and backup volumes are ignored. All other volumes are
1590     reported as unknown.
1591
1592     @type reserved: L{ganeti.utils.FieldSet}
1593     @param reserved: a FieldSet of reserved volume names
1594
1595     """
1596     for node, n_img in node_image.items():
1597       if n_img.offline or n_img.rpc_fail or n_img.lvm_fail:
1598         # skip non-healthy nodes
1599         continue
1600       for volume in n_img.volumes:
1601         test = ((node not in node_vol_should or
1602                 volume not in node_vol_should[node]) and
1603                 not reserved.Matches(volume))
1604         self._ErrorIf(test, self.ENODEORPHANLV, node,
1605                       "volume %s is unknown", volume)
1606
1607   def _VerifyOrphanInstances(self, instancelist, node_image):
1608     """Verify the list of running instances.
1609
1610     This checks what instances are running but unknown to the cluster.
1611
1612     """
1613     for node, n_img in node_image.items():
1614       for o_inst in n_img.instances:
1615         test = o_inst not in instancelist
1616         self._ErrorIf(test, self.ENODEORPHANINSTANCE, node,
1617                       "instance %s on node %s should not exist", o_inst, node)
1618
1619   def _VerifyNPlusOneMemory(self, node_image, instance_cfg):
1620     """Verify N+1 Memory Resilience.
1621
1622     Check that if one single node dies we can still start all the
1623     instances it was primary for.
1624
1625     """
1626     cluster_info = self.cfg.GetClusterInfo()
1627     for node, n_img in node_image.items():
1628       # This code checks that every node which is now listed as
1629       # secondary has enough memory to host all instances it is
1630       # supposed to should a single other node in the cluster fail.
1631       # FIXME: not ready for failover to an arbitrary node
1632       # FIXME: does not support file-backed instances
1633       # WARNING: we currently take into account down instances as well
1634       # as up ones, considering that even if they're down someone
1635       # might want to start them even in the event of a node failure.
1636       if n_img.offline:
1637         # we're skipping offline nodes from the N+1 warning, since
1638         # most likely we don't have good memory infromation from them;
1639         # we already list instances living on such nodes, and that's
1640         # enough warning
1641         continue
1642       for prinode, instances in n_img.sbp.items():
1643         needed_mem = 0
1644         for instance in instances:
1645           bep = cluster_info.FillBE(instance_cfg[instance])
1646           if bep[constants.BE_AUTO_BALANCE]:
1647             needed_mem += bep[constants.BE_MEMORY]
1648         test = n_img.mfree < needed_mem
1649         self._ErrorIf(test, self.ENODEN1, node,
1650                       "not enough memory to accomodate instance failovers"
1651                       " should node %s fail", prinode)
1652
1653   def _VerifyNodeFiles(self, ninfo, nresult, file_list, local_cksum,
1654                        master_files):
1655     """Verifies and computes the node required file checksums.
1656
1657     @type ninfo: L{objects.Node}
1658     @param ninfo: the node to check
1659     @param nresult: the remote results for the node
1660     @param file_list: required list of files
1661     @param local_cksum: dictionary of local files and their checksums
1662     @param master_files: list of files that only masters should have
1663
1664     """
1665     node = ninfo.name
1666     _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1667
1668     remote_cksum = nresult.get(constants.NV_FILELIST, None)
1669     test = not isinstance(remote_cksum, dict)
1670     _ErrorIf(test, self.ENODEFILECHECK, node,
1671              "node hasn't returned file checksum data")
1672     if test:
1673       return
1674
1675     for file_name in file_list:
1676       node_is_mc = ninfo.master_candidate
1677       must_have = (file_name not in master_files) or node_is_mc
1678       # missing
1679       test1 = file_name not in remote_cksum
1680       # invalid checksum
1681       test2 = not test1 and remote_cksum[file_name] != local_cksum[file_name]
1682       # existing and good
1683       test3 = not test1 and remote_cksum[file_name] == local_cksum[file_name]
1684       _ErrorIf(test1 and must_have, self.ENODEFILECHECK, node,
1685                "file '%s' missing", file_name)
1686       _ErrorIf(test2 and must_have, self.ENODEFILECHECK, node,
1687                "file '%s' has wrong checksum", file_name)
1688       # not candidate and this is not a must-have file
1689       _ErrorIf(test2 and not must_have, self.ENODEFILECHECK, node,
1690                "file '%s' should not exist on non master"
1691                " candidates (and the file is outdated)", file_name)
1692       # all good, except non-master/non-must have combination
1693       _ErrorIf(test3 and not must_have, self.ENODEFILECHECK, node,
1694                "file '%s' should not exist"
1695                " on non master candidates", file_name)
1696
1697   def _VerifyNodeDrbd(self, ninfo, nresult, instanceinfo, drbd_helper,
1698                       drbd_map):
1699     """Verifies and the node DRBD status.
1700
1701     @type ninfo: L{objects.Node}
1702     @param ninfo: the node to check
1703     @param nresult: the remote results for the node
1704     @param instanceinfo: the dict of instances
1705     @param drbd_helper: the configured DRBD usermode helper
1706     @param drbd_map: the DRBD map as returned by
1707         L{ganeti.config.ConfigWriter.ComputeDRBDMap}
1708
1709     """
1710     node = ninfo.name
1711     _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1712
1713     if drbd_helper:
1714       helper_result = nresult.get(constants.NV_DRBDHELPER, None)
1715       test = (helper_result == None)
1716       _ErrorIf(test, self.ENODEDRBDHELPER, node,
1717                "no drbd usermode helper returned")
1718       if helper_result:
1719         status, payload = helper_result
1720         test = not status
1721         _ErrorIf(test, self.ENODEDRBDHELPER, node,
1722                  "drbd usermode helper check unsuccessful: %s", payload)
1723         test = status and (payload != drbd_helper)
1724         _ErrorIf(test, self.ENODEDRBDHELPER, node,
1725                  "wrong drbd usermode helper: %s", payload)
1726
1727     # compute the DRBD minors
1728     node_drbd = {}
1729     for minor, instance in drbd_map[node].items():
1730       test = instance not in instanceinfo
1731       _ErrorIf(test, self.ECLUSTERCFG, None,
1732                "ghost instance '%s' in temporary DRBD map", instance)
1733         # ghost instance should not be running, but otherwise we
1734         # don't give double warnings (both ghost instance and
1735         # unallocated minor in use)
1736       if test:
1737         node_drbd[minor] = (instance, False)
1738       else:
1739         instance = instanceinfo[instance]
1740         node_drbd[minor] = (instance.name, instance.admin_up)
1741
1742     # and now check them
1743     used_minors = nresult.get(constants.NV_DRBDLIST, [])
1744     test = not isinstance(used_minors, (tuple, list))
1745     _ErrorIf(test, self.ENODEDRBD, node,
1746              "cannot parse drbd status file: %s", str(used_minors))
1747     if test:
1748       # we cannot check drbd status
1749       return
1750
1751     for minor, (iname, must_exist) in node_drbd.items():
1752       test = minor not in used_minors and must_exist
1753       _ErrorIf(test, self.ENODEDRBD, node,
1754                "drbd minor %d of instance %s is not active", minor, iname)
1755     for minor in used_minors:
1756       test = minor not in node_drbd
1757       _ErrorIf(test, self.ENODEDRBD, node,
1758                "unallocated drbd minor %d is in use", minor)
1759
1760   def _UpdateNodeOS(self, ninfo, nresult, nimg):
1761     """Builds the node OS structures.
1762
1763     @type ninfo: L{objects.Node}
1764     @param ninfo: the node to check
1765     @param nresult: the remote results for the node
1766     @param nimg: the node image object
1767
1768     """
1769     node = ninfo.name
1770     _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1771
1772     remote_os = nresult.get(constants.NV_OSLIST, None)
1773     test = (not isinstance(remote_os, list) or
1774             not compat.all(isinstance(v, list) and len(v) == 7
1775                            for v in remote_os))
1776
1777     _ErrorIf(test, self.ENODEOS, node,
1778              "node hasn't returned valid OS data")
1779
1780     nimg.os_fail = test
1781
1782     if test:
1783       return
1784
1785     os_dict = {}
1786
1787     for (name, os_path, status, diagnose,
1788          variants, parameters, api_ver) in nresult[constants.NV_OSLIST]:
1789
1790       if name not in os_dict:
1791         os_dict[name] = []
1792
1793       # parameters is a list of lists instead of list of tuples due to
1794       # JSON lacking a real tuple type, fix it:
1795       parameters = [tuple(v) for v in parameters]
1796       os_dict[name].append((os_path, status, diagnose,
1797                             set(variants), set(parameters), set(api_ver)))
1798
1799     nimg.oslist = os_dict
1800
1801   def _VerifyNodeOS(self, ninfo, nimg, base):
1802     """Verifies the node OS list.
1803
1804     @type ninfo: L{objects.Node}
1805     @param ninfo: the node to check
1806     @param nimg: the node image object
1807     @param base: the 'template' node we match against (e.g. from the master)
1808
1809     """
1810     node = ninfo.name
1811     _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1812
1813     assert not nimg.os_fail, "Entered _VerifyNodeOS with failed OS rpc?"
1814
1815     for os_name, os_data in nimg.oslist.items():
1816       assert os_data, "Empty OS status for OS %s?!" % os_name
1817       f_path, f_status, f_diag, f_var, f_param, f_api = os_data[0]
1818       _ErrorIf(not f_status, self.ENODEOS, node,
1819                "Invalid OS %s (located at %s): %s", os_name, f_path, f_diag)
1820       _ErrorIf(len(os_data) > 1, self.ENODEOS, node,
1821                "OS '%s' has multiple entries (first one shadows the rest): %s",
1822                os_name, utils.CommaJoin([v[0] for v in os_data]))
1823       # this will catched in backend too
1824       _ErrorIf(compat.any(v >= constants.OS_API_V15 for v in f_api)
1825                and not f_var, self.ENODEOS, node,
1826                "OS %s with API at least %d does not declare any variant",
1827                os_name, constants.OS_API_V15)
1828       # comparisons with the 'base' image
1829       test = os_name not in base.oslist
1830       _ErrorIf(test, self.ENODEOS, node,
1831                "Extra OS %s not present on reference node (%s)",
1832                os_name, base.name)
1833       if test:
1834         continue
1835       assert base.oslist[os_name], "Base node has empty OS status?"
1836       _, b_status, _, b_var, b_param, b_api = base.oslist[os_name][0]
1837       if not b_status:
1838         # base OS is invalid, skipping
1839         continue
1840       for kind, a, b in [("API version", f_api, b_api),
1841                          ("variants list", f_var, b_var),
1842                          ("parameters", f_param, b_param)]:
1843         _ErrorIf(a != b, self.ENODEOS, node,
1844                  "OS %s %s differs from reference node %s: %s vs. %s",
1845                  kind, os_name, base.name,
1846                  utils.CommaJoin(a), utils.CommaJoin(b))
1847
1848     # check any missing OSes
1849     missing = set(base.oslist.keys()).difference(nimg.oslist.keys())
1850     _ErrorIf(missing, self.ENODEOS, node,
1851              "OSes present on reference node %s but missing on this node: %s",
1852              base.name, utils.CommaJoin(missing))
1853
1854   def _VerifyOob(self, ninfo, nresult):
1855     """Verifies out of band functionality of a node.
1856
1857     @type ninfo: L{objects.Node}
1858     @param ninfo: the node to check
1859     @param nresult: the remote results for the node
1860
1861     """
1862     node = ninfo.name
1863     # We just have to verify the paths on master and/or master candidates
1864     # as the oob helper is invoked on the master
1865     if ((ninfo.master_candidate or ninfo.master_capable) and
1866         constants.NV_OOB_PATHS in nresult):
1867       for path_result in nresult[constants.NV_OOB_PATHS]:
1868         self._ErrorIf(path_result, self.ENODEOOBPATH, node, path_result)
1869
1870   def _UpdateNodeVolumes(self, ninfo, nresult, nimg, vg_name):
1871     """Verifies and updates the node volume data.
1872
1873     This function will update a L{NodeImage}'s internal structures
1874     with data from the remote call.
1875
1876     @type ninfo: L{objects.Node}
1877     @param ninfo: the node to check
1878     @param nresult: the remote results for the node
1879     @param nimg: the node image object
1880     @param vg_name: the configured VG name
1881
1882     """
1883     node = ninfo.name
1884     _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1885
1886     nimg.lvm_fail = True
1887     lvdata = nresult.get(constants.NV_LVLIST, "Missing LV data")
1888     if vg_name is None:
1889       pass
1890     elif isinstance(lvdata, basestring):
1891       _ErrorIf(True, self.ENODELVM, node, "LVM problem on node: %s",
1892                utils.SafeEncode(lvdata))
1893     elif not isinstance(lvdata, dict):
1894       _ErrorIf(True, self.ENODELVM, node, "rpc call to node failed (lvlist)")
1895     else:
1896       nimg.volumes = lvdata
1897       nimg.lvm_fail = False
1898
1899   def _UpdateNodeInstances(self, ninfo, nresult, nimg):
1900     """Verifies and updates the node instance list.
1901
1902     If the listing was successful, then updates this node's instance
1903     list. Otherwise, it marks the RPC call as failed for the instance
1904     list key.
1905
1906     @type ninfo: L{objects.Node}
1907     @param ninfo: the node to check
1908     @param nresult: the remote results for the node
1909     @param nimg: the node image object
1910
1911     """
1912     idata = nresult.get(constants.NV_INSTANCELIST, None)
1913     test = not isinstance(idata, list)
1914     self._ErrorIf(test, self.ENODEHV, ninfo.name, "rpc call to node failed"
1915                   " (instancelist): %s", utils.SafeEncode(str(idata)))
1916     if test:
1917       nimg.hyp_fail = True
1918     else:
1919       nimg.instances = idata
1920
1921   def _UpdateNodeInfo(self, ninfo, nresult, nimg, vg_name):
1922     """Verifies and computes a node information map
1923
1924     @type ninfo: L{objects.Node}
1925     @param ninfo: the node to check
1926     @param nresult: the remote results for the node
1927     @param nimg: the node image object
1928     @param vg_name: the configured VG name
1929
1930     """
1931     node = ninfo.name
1932     _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1933
1934     # try to read free memory (from the hypervisor)
1935     hv_info = nresult.get(constants.NV_HVINFO, None)
1936     test = not isinstance(hv_info, dict) or "memory_free" not in hv_info
1937     _ErrorIf(test, self.ENODEHV, node, "rpc call to node failed (hvinfo)")
1938     if not test:
1939       try:
1940         nimg.mfree = int(hv_info["memory_free"])
1941       except (ValueError, TypeError):
1942         _ErrorIf(True, self.ENODERPC, node,
1943                  "node returned invalid nodeinfo, check hypervisor")
1944
1945     # FIXME: devise a free space model for file based instances as well
1946     if vg_name is not None:
1947       test = (constants.NV_VGLIST not in nresult or
1948               vg_name not in nresult[constants.NV_VGLIST])
1949       _ErrorIf(test, self.ENODELVM, node,
1950                "node didn't return data for the volume group '%s'"
1951                " - it is either missing or broken", vg_name)
1952       if not test:
1953         try:
1954           nimg.dfree = int(nresult[constants.NV_VGLIST][vg_name])
1955         except (ValueError, TypeError):
1956           _ErrorIf(True, self.ENODERPC, node,
1957                    "node returned invalid LVM info, check LVM status")
1958
1959   def _CollectDiskInfo(self, nodelist, node_image, instanceinfo):
1960     """Gets per-disk status information for all instances.
1961
1962     @type nodelist: list of strings
1963     @param nodelist: Node names
1964     @type node_image: dict of (name, L{objects.Node})
1965     @param node_image: Node objects
1966     @type instanceinfo: dict of (name, L{objects.Instance})
1967     @param instanceinfo: Instance objects
1968     @rtype: {instance: {node: [(succes, payload)]}}
1969     @return: a dictionary of per-instance dictionaries with nodes as
1970         keys and disk information as values; the disk information is a
1971         list of tuples (success, payload)
1972
1973     """
1974     _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
1975
1976     node_disks = {}
1977     node_disks_devonly = {}
1978     diskless_instances = set()
1979     diskless = constants.DT_DISKLESS
1980
1981     for nname in nodelist:
1982       node_instances = list(itertools.chain(node_image[nname].pinst,
1983                                             node_image[nname].sinst))
1984       diskless_instances.update(inst for inst in node_instances
1985                                 if instanceinfo[inst].disk_template == diskless)
1986       disks = [(inst, disk)
1987                for inst in node_instances
1988                for disk in instanceinfo[inst].disks]
1989
1990       if not disks:
1991         # No need to collect data
1992         continue
1993
1994       node_disks[nname] = disks
1995
1996       # Creating copies as SetDiskID below will modify the objects and that can
1997       # lead to incorrect data returned from nodes
1998       devonly = [dev.Copy() for (_, dev) in disks]
1999
2000       for dev in devonly:
2001         self.cfg.SetDiskID(dev, nname)
2002
2003       node_disks_devonly[nname] = devonly
2004
2005     assert len(node_disks) == len(node_disks_devonly)
2006
2007     # Collect data from all nodes with disks
2008     result = self.rpc.call_blockdev_getmirrorstatus_multi(node_disks.keys(),
2009                                                           node_disks_devonly)
2010
2011     assert len(result) == len(node_disks)
2012
2013     instdisk = {}
2014
2015     for (nname, nres) in result.items():
2016       disks = node_disks[nname]
2017
2018       if nres.offline:
2019         # No data from this node
2020         data = len(disks) * [(False, "node offline")]
2021       else:
2022         msg = nres.fail_msg
2023         _ErrorIf(msg, self.ENODERPC, nname,
2024                  "while getting disk information: %s", msg)
2025         if msg:
2026           # No data from this node
2027           data = len(disks) * [(False, msg)]
2028         else:
2029           data = []
2030           for idx, i in enumerate(nres.payload):
2031             if isinstance(i, (tuple, list)) and len(i) == 2:
2032               data.append(i)
2033             else:
2034               logging.warning("Invalid result from node %s, entry %d: %s",
2035                               nname, idx, i)
2036               data.append((False, "Invalid result from the remote node"))
2037
2038       for ((inst, _), status) in zip(disks, data):
2039         instdisk.setdefault(inst, {}).setdefault(nname, []).append(status)
2040
2041     # Add empty entries for diskless instances.
2042     for inst in diskless_instances:
2043       assert inst not in instdisk
2044       instdisk[inst] = {}
2045
2046     assert compat.all(len(statuses) == len(instanceinfo[inst].disks) and
2047                       len(nnames) <= len(instanceinfo[inst].all_nodes) and
2048                       compat.all(isinstance(s, (tuple, list)) and
2049                                  len(s) == 2 for s in statuses)
2050                       for inst, nnames in instdisk.items()
2051                       for nname, statuses in nnames.items())
2052     assert set(instdisk) == set(instanceinfo), "instdisk consistency failure"
2053
2054     return instdisk
2055
2056   def _VerifyHVP(self, hvp_data):
2057     """Verifies locally the syntax of the hypervisor parameters.
2058
2059     """
2060     for item, hv_name, hv_params in hvp_data:
2061       msg = ("hypervisor %s parameters syntax check (source %s): %%s" %
2062              (item, hv_name))
2063       try:
2064         hv_class = hypervisor.GetHypervisor(hv_name)
2065         utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
2066         hv_class.CheckParameterSyntax(hv_params)
2067       except errors.GenericError, err:
2068         self._ErrorIf(True, self.ECLUSTERCFG, None, msg % str(err))
2069
2070
2071   def BuildHooksEnv(self):
2072     """Build hooks env.
2073
2074     Cluster-Verify hooks just ran in the post phase and their failure makes
2075     the output be logged in the verify output and the verification to fail.
2076
2077     """
2078     all_nodes = self.cfg.GetNodeList()
2079     env = {
2080       "CLUSTER_TAGS": " ".join(self.cfg.GetClusterInfo().GetTags())
2081       }
2082     for node in self.cfg.GetAllNodesInfo().values():
2083       env["NODE_TAGS_%s" % node.name] = " ".join(node.GetTags())
2084
2085     return env, [], all_nodes
2086
2087   def Exec(self, feedback_fn):
2088     """Verify integrity of cluster, performing various test on nodes.
2089
2090     """
2091     # This method has too many local variables. pylint: disable-msg=R0914
2092     self.bad = False
2093     _ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
2094     verbose = self.op.verbose
2095     self._feedback_fn = feedback_fn
2096     feedback_fn("* Verifying global settings")
2097     for msg in self.cfg.VerifyConfig():
2098       _ErrorIf(True, self.ECLUSTERCFG, None, msg)
2099
2100     # Check the cluster certificates
2101     for cert_filename in constants.ALL_CERT_FILES:
2102       (errcode, msg) = _VerifyCertificate(cert_filename)
2103       _ErrorIf(errcode, self.ECLUSTERCERT, None, msg, code=errcode)
2104
2105     vg_name = self.cfg.GetVGName()
2106     drbd_helper = self.cfg.GetDRBDHelper()
2107     hypervisors = self.cfg.GetClusterInfo().enabled_hypervisors
2108     cluster = self.cfg.GetClusterInfo()
2109     nodelist = utils.NiceSort(self.cfg.GetNodeList())
2110     nodeinfo = [self.cfg.GetNodeInfo(nname) for nname in nodelist]
2111     nodeinfo_byname = dict(zip(nodelist, nodeinfo))
2112     instancelist = utils.NiceSort(self.cfg.GetInstanceList())
2113     instanceinfo = dict((iname, self.cfg.GetInstanceInfo(iname))
2114                         for iname in instancelist)
2115     groupinfo = self.cfg.GetAllNodeGroupsInfo()
2116     i_non_redundant = [] # Non redundant instances
2117     i_non_a_balanced = [] # Non auto-balanced instances
2118     n_offline = 0 # Count of offline nodes
2119     n_drained = 0 # Count of nodes being drained
2120     node_vol_should = {}
2121
2122     # FIXME: verify OS list
2123     # do local checksums
2124     master_files = [constants.CLUSTER_CONF_FILE]
2125     master_node = self.master_node = self.cfg.GetMasterNode()
2126     master_ip = self.cfg.GetMasterIP()
2127
2128     file_names = ssconf.SimpleStore().GetFileList()
2129     file_names.extend(constants.ALL_CERT_FILES)
2130     file_names.extend(master_files)
2131     if cluster.modify_etc_hosts:
2132       file_names.append(constants.ETC_HOSTS)
2133
2134     local_checksums = utils.FingerprintFiles(file_names)
2135
2136     # Compute the set of hypervisor parameters
2137     hvp_data = []
2138     for hv_name in hypervisors:
2139       hvp_data.append(("cluster", hv_name, cluster.GetHVDefaults(hv_name)))
2140     for os_name, os_hvp in cluster.os_hvp.items():
2141       for hv_name, hv_params in os_hvp.items():
2142         if not hv_params:
2143           continue
2144         full_params = cluster.GetHVDefaults(hv_name, os_name=os_name)
2145         hvp_data.append(("os %s" % os_name, hv_name, full_params))
2146     # TODO: collapse identical parameter values in a single one
2147     for instance in instanceinfo.values():
2148       if not instance.hvparams:
2149         continue
2150       hvp_data.append(("instance %s" % instance.name, instance.hypervisor,
2151                        cluster.FillHV(instance)))
2152     # and verify them locally
2153     self._VerifyHVP(hvp_data)
2154
2155     feedback_fn("* Gathering data (%d nodes)" % len(nodelist))
2156     node_verify_param = {
2157       constants.NV_FILELIST: file_names,
2158       constants.NV_NODELIST: [node.name for node in nodeinfo
2159                               if not node.offline],
2160       constants.NV_HYPERVISOR: hypervisors,
2161       constants.NV_HVPARAMS: hvp_data,
2162       constants.NV_NODENETTEST: [(node.name, node.primary_ip,
2163                                   node.secondary_ip) for node in nodeinfo
2164                                  if not node.offline],
2165       constants.NV_INSTANCELIST: hypervisors,
2166       constants.NV_VERSION: None,
2167       constants.NV_HVINFO: self.cfg.GetHypervisorType(),
2168       constants.NV_NODESETUP: None,
2169       constants.NV_TIME: None,
2170       constants.NV_MASTERIP: (master_node, master_ip),
2171       constants.NV_OSLIST: None,
2172       constants.NV_VMNODES: self.cfg.GetNonVmCapableNodeList(),
2173       }
2174
2175     if vg_name is not None:
2176       node_verify_param[constants.NV_VGLIST] = None
2177       node_verify_param[constants.NV_LVLIST] = vg_name
2178       node_verify_param[constants.NV_PVLIST] = [vg_name]
2179       node_verify_param[constants.NV_DRBDLIST] = None
2180
2181     if drbd_helper:
2182       node_verify_param[constants.NV_DRBDHELPER] = drbd_helper
2183
2184     # Build our expected cluster state
2185     node_image = dict((node.name, self.NodeImage(offline=node.offline,
2186                                                  name=node.name,
2187                                                  vm_capable=node.vm_capable))
2188                       for node in nodeinfo)
2189
2190     # Gather OOB paths
2191     oob_paths = []
2192     for node in nodeinfo:
2193       path = _SupportsOob(self.cfg, node)
2194       if path and path not in oob_paths:
2195         oob_paths.append(path)
2196
2197     if oob_paths:
2198       node_verify_param[constants.NV_OOB_PATHS] = oob_paths
2199
2200     for instance in instancelist:
2201       inst_config = instanceinfo[instance]
2202
2203       for nname in inst_config.all_nodes:
2204         if nname not in node_image:
2205           # ghost node
2206           gnode = self.NodeImage(name=nname)
2207           gnode.ghost = True
2208           node_image[nname] = gnode
2209
2210       inst_config.MapLVsByNode(node_vol_should)
2211
2212       pnode = inst_config.primary_node
2213       node_image[pnode].pinst.append(instance)
2214
2215       for snode in inst_config.secondary_nodes:
2216         nimg = node_image[snode]
2217         nimg.sinst.append(instance)
2218         if pnode not in nimg.sbp:
2219           nimg.sbp[pnode] = []
2220         nimg.sbp[pnode].append(instance)
2221
2222     # At this point, we have the in-memory data structures complete,
2223     # except for the runtime information, which we'll gather next
2224
2225     # Due to the way our RPC system works, exact response times cannot be
2226     # guaranteed (e.g. a broken node could run into a timeout). By keeping the
2227     # time before and after executing the request, we can at least have a time
2228     # window.
2229     nvinfo_starttime = time.time()
2230     all_nvinfo = self.rpc.call_node_verify(nodelist, node_verify_param,
2231                                            self.cfg.GetClusterName())
2232     nvinfo_endtime = time.time()
2233
2234     all_drbd_map = self.cfg.ComputeDRBDMap()
2235
2236     feedback_fn("* Gathering disk information (%s nodes)" % len(nodelist))
2237     instdisk = self._CollectDiskInfo(nodelist, node_image, instanceinfo)
2238
2239     feedback_fn("* Verifying node status")
2240
2241     refos_img = None
2242
2243     for node_i in nodeinfo:
2244       node = node_i.name
2245       nimg = node_image[node]
2246
2247       if node_i.offline:
2248         if verbose:
2249           feedback_fn("* Skipping offline node %s" % (node,))
2250         n_offline += 1
2251         continue
2252
2253       if node == master_node:
2254         ntype = "master"
2255       elif node_i.master_candidate:
2256         ntype = "master candidate"
2257       elif node_i.drained:
2258         ntype = "drained"
2259         n_drained += 1
2260       else:
2261         ntype = "regular"
2262       if verbose:
2263         feedback_fn("* Verifying node %s (%s)" % (node, ntype))
2264
2265       msg = all_nvinfo[node].fail_msg
2266       _ErrorIf(msg, self.ENODERPC, node, "while contacting node: %s", msg)
2267       if msg:
2268         nimg.rpc_fail = True
2269         continue
2270
2271       nresult = all_nvinfo[node].payload
2272
2273       nimg.call_ok = self._VerifyNode(node_i, nresult)
2274       self._VerifyNodeTime(node_i, nresult, nvinfo_starttime, nvinfo_endtime)
2275       self._VerifyNodeNetwork(node_i, nresult)
2276       self._VerifyNodeFiles(node_i, nresult, file_names, local_checksums,
2277                             master_files)
2278
2279       self._VerifyOob(node_i, nresult)
2280
2281       if nimg.vm_capable:
2282         self._VerifyNodeLVM(node_i, nresult, vg_name)
2283         self._VerifyNodeDrbd(node_i, nresult, instanceinfo, drbd_helper,
2284                              all_drbd_map)
2285
2286         self._UpdateNodeVolumes(node_i, nresult, nimg, vg_name)
2287         self._UpdateNodeInstances(node_i, nresult, nimg)
2288         self._UpdateNodeInfo(node_i, nresult, nimg, vg_name)
2289         self._UpdateNodeOS(node_i, nresult, nimg)
2290         if not nimg.os_fail:
2291           if refos_img is None:
2292             refos_img = nimg
2293           self._VerifyNodeOS(node_i, nimg, refos_img)
2294
2295     feedback_fn("* Verifying instance status")
2296     for instance in instancelist:
2297       if verbose:
2298         feedback_fn("* Verifying instance %s" % instance)
2299       inst_config = instanceinfo[instance]
2300       self._VerifyInstance(instance, inst_config, node_image,
2301                            instdisk[instance])
2302       inst_nodes_offline = []
2303
2304       pnode = inst_config.primary_node
2305       pnode_img = node_image[pnode]
2306       _ErrorIf(pnode_img.rpc_fail and not pnode_img.offline,
2307                self.ENODERPC, pnode, "instance %s, connection to"
2308                " primary node failed", instance)
2309
2310       _ErrorIf(pnode_img.offline, self.EINSTANCEBADNODE, instance,
2311                "instance lives on offline node %s", inst_config.primary_node)
2312
2313       # If the instance is non-redundant we cannot survive losing its primary
2314       # node, so we are not N+1 compliant. On the other hand we have no disk
2315       # templates with more than one secondary so that situation is not well
2316       # supported either.
2317       # FIXME: does not support file-backed instances
2318       if not inst_config.secondary_nodes:
2319         i_non_redundant.append(instance)
2320
2321       _ErrorIf(len(inst_config.secondary_nodes) > 1, self.EINSTANCELAYOUT,
2322                instance, "instance has multiple secondary nodes: %s",
2323                utils.CommaJoin(inst_config.secondary_nodes),
2324                code=self.ETYPE_WARNING)
2325
2326       if inst_config.disk_template in constants.DTS_NET_MIRROR:
2327         pnode = inst_config.primary_node
2328         instance_nodes = utils.NiceSort(inst_config.all_nodes)
2329         instance_groups = {}
2330
2331         for node in instance_nodes:
2332           instance_groups.setdefault(nodeinfo_byname[node].group,
2333                                      []).append(node)
2334
2335         pretty_list = [
2336           "%s (group %s)" % (utils.CommaJoin(nodes), groupinfo[group].name)
2337           # Sort so that we always list the primary node first.
2338           for group, nodes in sorted(instance_groups.items(),
2339                                      key=lambda (_, nodes): pnode in nodes,
2340                                      reverse=True)]
2341
2342         self._ErrorIf(len(instance_groups) > 1, self.EINSTANCESPLITGROUPS,
2343                       instance, "instance has primary and secondary nodes in"
2344                       " different groups: %s", utils.CommaJoin(pretty_list),
2345                       code=self.ETYPE_WARNING)
2346
2347       if not cluster.FillBE(inst_config)[constants.BE_AUTO_BALANCE]:
2348         i_non_a_balanced.append(instance)
2349
2350       for snode in inst_config.secondary_nodes:
2351         s_img = node_image[snode]
2352         _ErrorIf(s_img.rpc_fail and not s_img.offline, self.ENODERPC, snode,
2353                  "instance %s, connection to secondary node failed", instance)
2354
2355         if s_img.offline:
2356           inst_nodes_offline.append(snode)
2357
2358       # warn that the instance lives on offline nodes
2359       _ErrorIf(inst_nodes_offline, self.EINSTANCEBADNODE, instance,
2360                "instance has offline secondary node(s) %s",
2361                utils.CommaJoin(inst_nodes_offline))
2362       # ... or ghost/non-vm_capable nodes
2363       for node in inst_config.all_nodes:
2364         _ErrorIf(node_image[node].ghost, self.EINSTANCEBADNODE, instance,
2365                  "instance lives on ghost node %s", node)
2366         _ErrorIf(not node_image[node].vm_capable, self.EINSTANCEBADNODE,
2367                  instance, "instance lives on non-vm_capable node %s", node)
2368
2369     feedback_fn("* Verifying orphan volumes")
2370     reserved = utils.FieldSet(*cluster.reserved_lvs)
2371     self._VerifyOrphanVolumes(node_vol_should, node_image, reserved)
2372
2373     feedback_fn("* Verifying orphan instances")
2374     self._VerifyOrphanInstances(instancelist, node_image)
2375
2376     if constants.VERIFY_NPLUSONE_MEM not in self.op.skip_checks:
2377       feedback_fn("* Verifying N+1 Memory redundancy")
2378       self._VerifyNPlusOneMemory(node_image, instanceinfo)
2379
2380     feedback_fn("* Other Notes")
2381     if i_non_redundant:
2382       feedback_fn("  - NOTICE: %d non-redundant instance(s) found."
2383                   % len(i_non_redundant))
2384
2385     if i_non_a_balanced:
2386       feedback_fn("  - NOTICE: %d non-auto-balanced instance(s) found."
2387                   % len(i_non_a_balanced))
2388
2389     if n_offline:
2390       feedback_fn("  - NOTICE: %d offline node(s) found." % n_offline)
2391
2392     if n_drained:
2393       feedback_fn("  - NOTICE: %d drained node(s) found." % n_drained)
2394
2395     return not self.bad
2396
2397   def HooksCallBack(self, phase, hooks_results, feedback_fn, lu_result):
2398     """Analyze the post-hooks' result
2399
2400     This method analyses the hook result, handles it, and sends some
2401     nicely-formatted feedback back to the user.
2402
2403     @param phase: one of L{constants.HOOKS_PHASE_POST} or
2404         L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
2405     @param hooks_results: the results of the multi-node hooks rpc call
2406     @param feedback_fn: function used send feedback back to the caller
2407     @param lu_result: previous Exec result
2408     @return: the new Exec result, based on the previous result
2409         and hook results
2410
2411     """
2412     # We only really run POST phase hooks, and are only interested in
2413     # their results
2414     if phase == constants.HOOKS_PHASE_POST:
2415       # Used to change hooks' output to proper indentation
2416       feedback_fn("* Hooks Results")
2417       assert hooks_results, "invalid result from hooks"
2418
2419       for node_name in hooks_results:
2420         res = hooks_results[node_name]
2421         msg = res.fail_msg
2422         test = msg and not res.offline
2423         self._ErrorIf(test, self.ENODEHOOKS, node_name,
2424                       "Communication failure in hooks execution: %s", msg)
2425         if res.offline or msg:
2426           # No need to investigate payload if node is offline or gave an error.
2427           # override manually lu_result here as _ErrorIf only
2428           # overrides self.bad
2429           lu_result = 1
2430           continue
2431         for script, hkr, output in res.payload:
2432           test = hkr == constants.HKR_FAIL
2433           self._ErrorIf(test, self.ENODEHOOKS, node_name,
2434                         "Script %s failed, output:", script)
2435           if test:
2436             output = self._HOOKS_INDENT_RE.sub('      ', output)
2437             feedback_fn("%s" % output)
2438             lu_result = 0
2439
2440       return lu_result
2441
2442
2443 class LUClusterVerifyDisks(NoHooksLU):
2444   """Verifies the cluster disks status.
2445
2446   """
2447   REQ_BGL = False
2448
2449   def ExpandNames(self):
2450     self.needed_locks = {
2451       locking.LEVEL_NODE: locking.ALL_SET,
2452       locking.LEVEL_INSTANCE: locking.ALL_SET,
2453     }
2454     self.share_locks = dict.fromkeys(locking.LEVELS, 1)
2455
2456   def Exec(self, feedback_fn):
2457     """Verify integrity of cluster disks.
2458
2459     @rtype: tuple of three items
2460     @return: a tuple of (dict of node-to-node_error, list of instances
2461         which need activate-disks, dict of instance: (node, volume) for
2462         missing volumes
2463
2464     """
2465     result = res_nodes, res_instances, res_missing = {}, [], {}
2466
2467     nodes = utils.NiceSort(self.cfg.GetVmCapableNodeList())
2468     instances = self.cfg.GetAllInstancesInfo().values()
2469
2470     nv_dict = {}
2471     for inst in instances:
2472       inst_lvs = {}
2473       if not inst.admin_up:
2474         continue
2475       inst.MapLVsByNode(inst_lvs)
2476       # transform { iname: {node: [vol,],},} to {(node, vol): iname}
2477       for node, vol_list in inst_lvs.iteritems():
2478         for vol in vol_list:
2479           nv_dict[(node, vol)] = inst
2480
2481     if not nv_dict:
2482       return result
2483
2484     node_lvs = self.rpc.call_lv_list(nodes, [])
2485     for node, node_res in node_lvs.items():
2486       if node_res.offline:
2487         continue
2488       msg = node_res.fail_msg
2489       if msg:
2490         logging.warning("Error enumerating LVs on node %s: %s", node, msg)
2491         res_nodes[node] = msg
2492         continue
2493
2494       lvs = node_res.payload
2495       for lv_name, (_, _, lv_online) in lvs.items():
2496         inst = nv_dict.pop((node, lv_name), None)
2497         if (not lv_online and inst is not None
2498             and inst.name not in res_instances):
2499           res_instances.append(inst.name)
2500
2501     # any leftover items in nv_dict are missing LVs, let's arrange the
2502     # data better
2503     for key, inst in nv_dict.iteritems():
2504       if inst.name not in res_missing:
2505         res_missing[inst.name] = []
2506       res_missing[inst.name].append(key)
2507
2508     return result
2509
2510
2511 class LUClusterRepairDiskSizes(NoHooksLU):
2512   """Verifies the cluster disks sizes.
2513
2514   """
2515   REQ_BGL = False
2516
2517   def ExpandNames(self):
2518     if self.op.instances:
2519       self.wanted_names = []
2520       for name in self.op.instances:
2521         full_name = _ExpandInstanceName(self.cfg, name)
2522         self.wanted_names.append(full_name)
2523       self.needed_locks = {
2524         locking.LEVEL_NODE: [],
2525         locking.LEVEL_INSTANCE: self.wanted_names,
2526         }
2527       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
2528     else:
2529       self.wanted_names = None
2530       self.needed_locks = {
2531         locking.LEVEL_NODE: locking.ALL_SET,
2532         locking.LEVEL_INSTANCE: locking.ALL_SET,
2533         }
2534     self.share_locks = dict(((i, 1) for i in locking.LEVELS))
2535
2536   def DeclareLocks(self, level):
2537     if level == locking.LEVEL_NODE and self.wanted_names is not None:
2538       self._LockInstancesNodes(primary_only=True)
2539
2540   def CheckPrereq(self):
2541     """Check prerequisites.
2542
2543     This only checks the optional instance list against the existing names.
2544
2545     """
2546     if self.wanted_names is None:
2547       self.wanted_names = self.acquired_locks[locking.LEVEL_INSTANCE]
2548
2549     self.wanted_instances = [self.cfg.GetInstanceInfo(name) for name
2550                              in self.wanted_names]
2551
2552   def _EnsureChildSizes(self, disk):
2553     """Ensure children of the disk have the needed disk size.
2554
2555     This is valid mainly for DRBD8 and fixes an issue where the
2556     children have smaller disk size.
2557
2558     @param disk: an L{ganeti.objects.Disk} object
2559
2560     """
2561     if disk.dev_type == constants.LD_DRBD8:
2562       assert disk.children, "Empty children for DRBD8?"
2563       fchild = disk.children[0]
2564       mismatch = fchild.size < disk.size
2565       if mismatch:
2566         self.LogInfo("Child disk has size %d, parent %d, fixing",
2567                      fchild.size, disk.size)
2568         fchild.size = disk.size
2569
2570       # and we recurse on this child only, not on the metadev
2571       return self._EnsureChildSizes(fchild) or mismatch
2572     else:
2573       return False
2574
2575   def Exec(self, feedback_fn):
2576     """Verify the size of cluster disks.
2577
2578     """
2579     # TODO: check child disks too
2580     # TODO: check differences in size between primary/secondary nodes
2581     per_node_disks = {}
2582     for instance in self.wanted_instances:
2583       pnode = instance.primary_node
2584       if pnode not in per_node_disks:
2585         per_node_disks[pnode] = []
2586       for idx, disk in enumerate(instance.disks):
2587         per_node_disks[pnode].append((instance, idx, disk))
2588
2589     changed = []
2590     for node, dskl in per_node_disks.items():
2591       newl = [v[2].Copy() for v in dskl]
2592       for dsk in newl:
2593         self.cfg.SetDiskID(dsk, node)
2594       result = self.rpc.call_blockdev_getsize(node, newl)
2595       if result.fail_msg:
2596         self.LogWarning("Failure in blockdev_getsize call to node"
2597                         " %s, ignoring", node)
2598         continue
2599       if len(result.payload) != len(dskl):
2600         logging.warning("Invalid result from node %s: len(dksl)=%d,"
2601                         " result.payload=%s", node, len(dskl), result.payload)
2602         self.LogWarning("Invalid result from node %s, ignoring node results",
2603                         node)
2604         continue
2605       for ((instance, idx, disk), size) in zip(dskl, result.payload):
2606         if size is None:
2607           self.LogWarning("Disk %d of instance %s did not return size"
2608                           " information, ignoring", idx, instance.name)
2609           continue
2610         if not isinstance(size, (int, long)):
2611           self.LogWarning("Disk %d of instance %s did not return valid"
2612                           " size information, ignoring", idx, instance.name)
2613           continue
2614         size = size >> 20
2615         if size != disk.size:
2616           self.LogInfo("Disk %d of instance %s has mismatched size,"
2617                        " correcting: recorded %d, actual %d", idx,
2618                        instance.name, disk.size, size)
2619           disk.size = size
2620           self.cfg.Update(instance, feedback_fn)
2621           changed.append((instance.name, idx, size))
2622         if self._EnsureChildSizes(disk):
2623           self.cfg.Update(instance, feedback_fn)
2624           changed.append((instance.name, idx, disk.size))
2625     return changed
2626
2627
2628 class LUClusterRename(LogicalUnit):
2629   """Rename the cluster.
2630
2631   """
2632   HPATH = "cluster-rename"
2633   HTYPE = constants.HTYPE_CLUSTER
2634
2635   def BuildHooksEnv(self):
2636     """Build hooks env.
2637
2638     """
2639     env = {
2640       "OP_TARGET": self.cfg.GetClusterName(),
2641       "NEW_NAME": self.op.name,
2642       }
2643     mn = self.cfg.GetMasterNode()
2644     all_nodes = self.cfg.GetNodeList()
2645     return env, [mn], all_nodes
2646
2647   def CheckPrereq(self):
2648     """Verify that the passed name is a valid one.
2649
2650     """
2651     hostname = netutils.GetHostname(name=self.op.name,
2652                                     family=self.cfg.GetPrimaryIPFamily())
2653
2654     new_name = hostname.name
2655     self.ip = new_ip = hostname.ip
2656     old_name = self.cfg.GetClusterName()
2657     old_ip = self.cfg.GetMasterIP()
2658     if new_name == old_name and new_ip == old_ip:
2659       raise errors.OpPrereqError("Neither the name nor the IP address of the"
2660                                  " cluster has changed",
2661                                  errors.ECODE_INVAL)
2662     if new_ip != old_ip:
2663       if netutils.TcpPing(new_ip, constants.DEFAULT_NODED_PORT):
2664         raise errors.OpPrereqError("The given cluster IP address (%s) is"
2665                                    " reachable on the network" %
2666                                    new_ip, errors.ECODE_NOTUNIQUE)
2667
2668     self.op.name = new_name
2669
2670   def Exec(self, feedback_fn):
2671     """Rename the cluster.
2672
2673     """
2674     clustername = self.op.name
2675     ip = self.ip
2676
2677     # shutdown the master IP
2678     master = self.cfg.GetMasterNode()
2679     result = self.rpc.call_node_stop_master(master, False)
2680     result.Raise("Could not disable the master role")
2681
2682     try:
2683       cluster = self.cfg.GetClusterInfo()
2684       cluster.cluster_name = clustername
2685       cluster.master_ip = ip
2686       self.cfg.Update(cluster, feedback_fn)
2687
2688       # update the known hosts file
2689       ssh.WriteKnownHostsFile(self.cfg, constants.SSH_KNOWN_HOSTS_FILE)
2690       node_list = self.cfg.GetOnlineNodeList()
2691       try:
2692         node_list.remove(master)
2693       except ValueError:
2694         pass
2695       _UploadHelper(self, node_list, constants.SSH_KNOWN_HOSTS_FILE)
2696     finally:
2697       result = self.rpc.call_node_start_master(master, False, False)
2698       msg = result.fail_msg
2699       if msg:
2700         self.LogWarning("Could not re-enable the master role on"
2701                         " the master, please restart manually: %s", msg)
2702
2703     return clustername
2704
2705
2706 class LUClusterSetParams(LogicalUnit):
2707   """Change the parameters of the cluster.
2708
2709   """
2710   HPATH = "cluster-modify"
2711   HTYPE = constants.HTYPE_CLUSTER
2712   REQ_BGL = False
2713
2714   def CheckArguments(self):
2715     """Check parameters
2716
2717     """
2718     if self.op.uid_pool:
2719       uidpool.CheckUidPool(self.op.uid_pool)
2720
2721     if self.op.add_uids:
2722       uidpool.CheckUidPool(self.op.add_uids)
2723
2724     if self.op.remove_uids:
2725       uidpool.CheckUidPool(self.op.remove_uids)
2726
2727   def ExpandNames(self):
2728     # FIXME: in the future maybe other cluster params won't require checking on
2729     # all nodes to be modified.
2730     self.needed_locks = {
2731       locking.LEVEL_NODE: locking.ALL_SET,
2732     }
2733     self.share_locks[locking.LEVEL_NODE] = 1
2734
2735   def BuildHooksEnv(self):
2736     """Build hooks env.
2737
2738     """
2739     env = {
2740       "OP_TARGET": self.cfg.GetClusterName(),
2741       "NEW_VG_NAME": self.op.vg_name,
2742       }
2743     mn = self.cfg.GetMasterNode()
2744     return env, [mn], [mn]
2745
2746   def CheckPrereq(self):
2747     """Check prerequisites.
2748
2749     This checks whether the given params don't conflict and
2750     if the given volume group is valid.
2751
2752     """
2753     if self.op.vg_name is not None and not self.op.vg_name:
2754       if self.cfg.HasAnyDiskOfType(constants.LD_LV):
2755         raise errors.OpPrereqError("Cannot disable lvm storage while lvm-based"
2756                                    " instances exist", errors.ECODE_INVAL)
2757
2758     if self.op.drbd_helper is not None and not self.op.drbd_helper:
2759       if self.cfg.HasAnyDiskOfType(constants.LD_DRBD8):
2760         raise errors.OpPrereqError("Cannot disable drbd helper while"
2761                                    " drbd-based instances exist",
2762                                    errors.ECODE_INVAL)
2763
2764     node_list = self.acquired_locks[locking.LEVEL_NODE]
2765
2766     # if vg_name not None, checks given volume group on all nodes
2767     if self.op.vg_name:
2768       vglist = self.rpc.call_vg_list(node_list)
2769       for node in node_list:
2770         msg = vglist[node].fail_msg
2771         if msg:
2772           # ignoring down node
2773           self.LogWarning("Error while gathering data on node %s"
2774                           " (ignoring node): %s", node, msg)
2775           continue
2776         vgstatus = utils.CheckVolumeGroupSize(vglist[node].payload,
2777                                               self.op.vg_name,
2778                                               constants.MIN_VG_SIZE)
2779         if vgstatus:
2780           raise errors.OpPrereqError("Error on node '%s': %s" %
2781                                      (node, vgstatus), errors.ECODE_ENVIRON)
2782
2783     if self.op.drbd_helper:
2784       # checks given drbd helper on all nodes
2785       helpers = self.rpc.call_drbd_helper(node_list)
2786       for node in node_list:
2787         ninfo = self.cfg.GetNodeInfo(node)
2788         if ninfo.offline:
2789           self.LogInfo("Not checking drbd helper on offline node %s", node)
2790           continue
2791         msg = helpers[node].fail_msg
2792         if msg:
2793           raise errors.OpPrereqError("Error checking drbd helper on node"
2794                                      " '%s': %s" % (node, msg),
2795                                      errors.ECODE_ENVIRON)
2796         node_helper = helpers[node].payload
2797         if node_helper != self.op.drbd_helper:
2798           raise errors.OpPrereqError("Error on node '%s': drbd helper is %s" %
2799                                      (node, node_helper), errors.ECODE_ENVIRON)
2800
2801     self.cluster = cluster = self.cfg.GetClusterInfo()
2802     # validate params changes
2803     if self.op.beparams:
2804       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
2805       self.new_beparams = cluster.SimpleFillBE(self.op.beparams)
2806
2807     if self.op.ndparams:
2808       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
2809       self.new_ndparams = cluster.SimpleFillND(self.op.ndparams)
2810
2811     if self.op.nicparams:
2812       utils.ForceDictType(self.op.nicparams, constants.NICS_PARAMETER_TYPES)
2813       self.new_nicparams = cluster.SimpleFillNIC(self.op.nicparams)
2814       objects.NIC.CheckParameterSyntax(self.new_nicparams)
2815       nic_errors = []
2816
2817       # check all instances for consistency
2818       for instance in self.cfg.GetAllInstancesInfo().values():
2819         for nic_idx, nic in enumerate(instance.nics):
2820           params_copy = copy.deepcopy(nic.nicparams)
2821           params_filled = objects.FillDict(self.new_nicparams, params_copy)
2822
2823           # check parameter syntax
2824           try:
2825             objects.NIC.CheckParameterSyntax(params_filled)
2826           except errors.ConfigurationError, err:
2827             nic_errors.append("Instance %s, nic/%d: %s" %
2828                               (instance.name, nic_idx, err))
2829
2830           # if we're moving instances to routed, check that they have an ip
2831           target_mode = params_filled[constants.NIC_MODE]
2832           if target_mode == constants.NIC_MODE_ROUTED and not nic.ip:
2833             nic_errors.append("Instance %s, nic/%d: routed nick with no ip" %
2834                               (instance.name, nic_idx))
2835       if nic_errors:
2836         raise errors.OpPrereqError("Cannot apply the change, errors:\n%s" %
2837                                    "\n".join(nic_errors))
2838
2839     # hypervisor list/parameters
2840     self.new_hvparams = new_hvp = objects.FillDict(cluster.hvparams, {})
2841     if self.op.hvparams:
2842       for hv_name, hv_dict in self.op.hvparams.items():
2843         if hv_name not in self.new_hvparams:
2844           self.new_hvparams[hv_name] = hv_dict
2845         else:
2846           self.new_hvparams[hv_name].update(hv_dict)
2847
2848     # os hypervisor parameters
2849     self.new_os_hvp = objects.FillDict(cluster.os_hvp, {})
2850     if self.op.os_hvp:
2851       for os_name, hvs in self.op.os_hvp.items():
2852         if os_name not in self.new_os_hvp:
2853           self.new_os_hvp[os_name] = hvs
2854         else:
2855           for hv_name, hv_dict in hvs.items():
2856             if hv_name not in self.new_os_hvp[os_name]:
2857               self.new_os_hvp[os_name][hv_name] = hv_dict
2858             else:
2859               self.new_os_hvp[os_name][hv_name].update(hv_dict)
2860
2861     # os parameters
2862     self.new_osp = objects.FillDict(cluster.osparams, {})
2863     if self.op.osparams:
2864       for os_name, osp in self.op.osparams.items():
2865         if os_name not in self.new_osp:
2866           self.new_osp[os_name] = {}
2867
2868         self.new_osp[os_name] = _GetUpdatedParams(self.new_osp[os_name], osp,
2869                                                   use_none=True)
2870
2871         if not self.new_osp[os_name]:
2872           # we removed all parameters
2873           del self.new_osp[os_name]
2874         else:
2875           # check the parameter validity (remote check)
2876           _CheckOSParams(self, False, [self.cfg.GetMasterNode()],
2877                          os_name, self.new_osp[os_name])
2878
2879     # changes to the hypervisor list
2880     if self.op.enabled_hypervisors is not None:
2881       self.hv_list = self.op.enabled_hypervisors
2882       for hv in self.hv_list:
2883         # if the hypervisor doesn't already exist in the cluster
2884         # hvparams, we initialize it to empty, and then (in both
2885         # cases) we make sure to fill the defaults, as we might not
2886         # have a complete defaults list if the hypervisor wasn't
2887         # enabled before
2888         if hv not in new_hvp:
2889           new_hvp[hv] = {}
2890         new_hvp[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], new_hvp[hv])
2891         utils.ForceDictType(new_hvp[hv], constants.HVS_PARAMETER_TYPES)
2892     else:
2893       self.hv_list = cluster.enabled_hypervisors
2894
2895     if self.op.hvparams or self.op.enabled_hypervisors is not None:
2896       # either the enabled list has changed, or the parameters have, validate
2897       for hv_name, hv_params in self.new_hvparams.items():
2898         if ((self.op.hvparams and hv_name in self.op.hvparams) or
2899             (self.op.enabled_hypervisors and
2900              hv_name in self.op.enabled_hypervisors)):
2901           # either this is a new hypervisor, or its parameters have changed
2902           hv_class = hypervisor.GetHypervisor(hv_name)
2903           utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
2904           hv_class.CheckParameterSyntax(hv_params)
2905           _CheckHVParams(self, node_list, hv_name, hv_params)
2906
2907     if self.op.os_hvp:
2908       # no need to check any newly-enabled hypervisors, since the
2909       # defaults have already been checked in the above code-block
2910       for os_name, os_hvp in self.new_os_hvp.items():
2911         for hv_name, hv_params in os_hvp.items():
2912           utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
2913           # we need to fill in the new os_hvp on top of the actual hv_p
2914           cluster_defaults = self.new_hvparams.get(hv_name, {})
2915           new_osp = objects.FillDict(cluster_defaults, hv_params)
2916           hv_class = hypervisor.GetHypervisor(hv_name)
2917           hv_class.CheckParameterSyntax(new_osp)
2918           _CheckHVParams(self, node_list, hv_name, new_osp)
2919
2920     if self.op.default_iallocator:
2921       alloc_script = utils.FindFile(self.op.default_iallocator,
2922                                     constants.IALLOCATOR_SEARCH_PATH,
2923                                     os.path.isfile)
2924       if alloc_script is None:
2925         raise errors.OpPrereqError("Invalid default iallocator script '%s'"
2926                                    " specified" % self.op.default_iallocator,
2927                                    errors.ECODE_INVAL)
2928
2929   def Exec(self, feedback_fn):
2930     """Change the parameters of the cluster.
2931
2932     """
2933     if self.op.vg_name is not None:
2934       new_volume = self.op.vg_name
2935       if not new_volume:
2936         new_volume = None
2937       if new_volume != self.cfg.GetVGName():
2938         self.cfg.SetVGName(new_volume)
2939       else:
2940         feedback_fn("Cluster LVM configuration already in desired"
2941                     " state, not changing")
2942     if self.op.drbd_helper is not None:
2943       new_helper = self.op.drbd_helper
2944       if not new_helper:
2945         new_helper = None
2946       if new_helper != self.cfg.GetDRBDHelper():
2947         self.cfg.SetDRBDHelper(new_helper)
2948       else:
2949         feedback_fn("Cluster DRBD helper already in desired state,"
2950                     " not changing")
2951     if self.op.hvparams:
2952       self.cluster.hvparams = self.new_hvparams
2953     if self.op.os_hvp:
2954       self.cluster.os_hvp = self.new_os_hvp
2955     if self.op.enabled_hypervisors is not None:
2956       self.cluster.hvparams = self.new_hvparams
2957       self.cluster.enabled_hypervisors = self.op.enabled_hypervisors
2958     if self.op.beparams:
2959       self.cluster.beparams[constants.PP_DEFAULT] = self.new_beparams
2960     if self.op.nicparams:
2961       self.cluster.nicparams[constants.PP_DEFAULT] = self.new_nicparams
2962     if self.op.osparams:
2963       self.cluster.osparams = self.new_osp
2964     if self.op.ndparams:
2965       self.cluster.ndparams = self.new_ndparams
2966
2967     if self.op.candidate_pool_size is not None:
2968       self.cluster.candidate_pool_size = self.op.candidate_pool_size
2969       # we need to update the pool size here, otherwise the save will fail
2970       _AdjustCandidatePool(self, [])
2971
2972     if self.op.maintain_node_health is not None:
2973       self.cluster.maintain_node_health = self.op.maintain_node_health
2974
2975     if self.op.prealloc_wipe_disks is not None:
2976       self.cluster.prealloc_wipe_disks = self.op.prealloc_wipe_disks
2977
2978     if self.op.add_uids is not None:
2979       uidpool.AddToUidPool(self.cluster.uid_pool, self.op.add_uids)
2980
2981     if self.op.remove_uids is not None:
2982       uidpool.RemoveFromUidPool(self.cluster.uid_pool, self.op.remove_uids)
2983
2984     if self.op.uid_pool is not None:
2985       self.cluster.uid_pool = self.op.uid_pool
2986
2987     if self.op.default_iallocator is not None:
2988       self.cluster.default_iallocator = self.op.default_iallocator
2989
2990     if self.op.reserved_lvs is not None:
2991       self.cluster.reserved_lvs = self.op.reserved_lvs
2992
2993     def helper_os(aname, mods, desc):
2994       desc += " OS list"
2995       lst = getattr(self.cluster, aname)
2996       for key, val in mods:
2997         if key == constants.DDM_ADD:
2998           if val in lst:
2999             feedback_fn("OS %s already in %s, ignoring" % (val, desc))
3000           else:
3001             lst.append(val)
3002         elif key == constants.DDM_REMOVE:
3003           if val in lst:
3004             lst.remove(val)
3005           else:
3006             feedback_fn("OS %s not found in %s, ignoring" % (val, desc))
3007         else:
3008           raise errors.ProgrammerError("Invalid modification '%s'" % key)
3009
3010     if self.op.hidden_os:
3011       helper_os("hidden_os", self.op.hidden_os, "hidden")
3012
3013     if self.op.blacklisted_os:
3014       helper_os("blacklisted_os", self.op.blacklisted_os, "blacklisted")
3015
3016     if self.op.master_netdev:
3017       master = self.cfg.GetMasterNode()
3018       feedback_fn("Shutting down master ip on the current netdev (%s)" %
3019                   self.cluster.master_netdev)
3020       result = self.rpc.call_node_stop_master(master, False)
3021       result.Raise("Could not disable the master ip")
3022       feedback_fn("Changing master_netdev from %s to %s" %
3023                   (self.cluster.master_netdev, self.op.master_netdev))
3024       self.cluster.master_netdev = self.op.master_netdev
3025
3026     self.cfg.Update(self.cluster, feedback_fn)
3027
3028     if self.op.master_netdev:
3029       feedback_fn("Starting the master ip on the new master netdev (%s)" %
3030                   self.op.master_netdev)
3031       result = self.rpc.call_node_start_master(master, False, False)
3032       if result.fail_msg:
3033         self.LogWarning("Could not re-enable the master ip on"
3034                         " the master, please restart manually: %s",
3035                         result.fail_msg)
3036
3037
3038 def _UploadHelper(lu, nodes, fname):
3039   """Helper for uploading a file and showing warnings.
3040
3041   """
3042   if os.path.exists(fname):
3043     result = lu.rpc.call_upload_file(nodes, fname)
3044     for to_node, to_result in result.items():
3045       msg = to_result.fail_msg
3046       if msg:
3047         msg = ("Copy of file %s to node %s failed: %s" %
3048                (fname, to_node, msg))
3049         lu.proc.LogWarning(msg)
3050
3051
3052 def _RedistributeAncillaryFiles(lu, additional_nodes=None, additional_vm=True):
3053   """Distribute additional files which are part of the cluster configuration.
3054
3055   ConfigWriter takes care of distributing the config and ssconf files, but
3056   there are more files which should be distributed to all nodes. This function
3057   makes sure those are copied.
3058
3059   @param lu: calling logical unit
3060   @param additional_nodes: list of nodes not in the config to distribute to
3061   @type additional_vm: boolean
3062   @param additional_vm: whether the additional nodes are vm-capable or not
3063
3064   """
3065   # 1. Gather target nodes
3066   myself = lu.cfg.GetNodeInfo(lu.cfg.GetMasterNode())
3067   dist_nodes = lu.cfg.GetOnlineNodeList()
3068   nvm_nodes = lu.cfg.GetNonVmCapableNodeList()
3069   vm_nodes = [name for name in dist_nodes if name not in nvm_nodes]
3070   if additional_nodes is not None:
3071     dist_nodes.extend(additional_nodes)
3072     if additional_vm:
3073       vm_nodes.extend(additional_nodes)
3074   if myself.name in dist_nodes:
3075     dist_nodes.remove(myself.name)
3076   if myself.name in vm_nodes:
3077     vm_nodes.remove(myself.name)
3078
3079   # 2. Gather files to distribute
3080   dist_files = set([constants.ETC_HOSTS,
3081                     constants.SSH_KNOWN_HOSTS_FILE,
3082                     constants.RAPI_CERT_FILE,
3083                     constants.RAPI_USERS_FILE,
3084                     constants.CONFD_HMAC_KEY,
3085                     constants.CLUSTER_DOMAIN_SECRET_FILE,
3086                    ])
3087
3088   vm_files = set()
3089   enabled_hypervisors = lu.cfg.GetClusterInfo().enabled_hypervisors
3090   for hv_name in enabled_hypervisors:
3091     hv_class = hypervisor.GetHypervisor(hv_name)
3092     vm_files.update(hv_class.GetAncillaryFiles())
3093
3094   # 3. Perform the files upload
3095   for fname in dist_files:
3096     _UploadHelper(lu, dist_nodes, fname)
3097   for fname in vm_files:
3098     _UploadHelper(lu, vm_nodes, fname)
3099
3100
3101 class LUClusterRedistConf(NoHooksLU):
3102   """Force the redistribution of cluster configuration.
3103
3104   This is a very simple LU.
3105
3106   """
3107   REQ_BGL = False
3108
3109   def ExpandNames(self):
3110     self.needed_locks = {
3111       locking.LEVEL_NODE: locking.ALL_SET,
3112     }
3113     self.share_locks[locking.LEVEL_NODE] = 1
3114
3115   def Exec(self, feedback_fn):
3116     """Redistribute the configuration.
3117
3118     """
3119     self.cfg.Update(self.cfg.GetClusterInfo(), feedback_fn)
3120     _RedistributeAncillaryFiles(self)
3121
3122
3123 def _WaitForSync(lu, instance, disks=None, oneshot=False):
3124   """Sleep and poll for an instance's disk to sync.
3125
3126   """
3127   if not instance.disks or disks is not None and not disks:
3128     return True
3129
3130   disks = _ExpandCheckDisks(instance, disks)
3131
3132   if not oneshot:
3133     lu.proc.LogInfo("Waiting for instance %s to sync disks." % instance.name)
3134
3135   node = instance.primary_node
3136
3137   for dev in disks:
3138     lu.cfg.SetDiskID(dev, node)
3139
3140   # TODO: Convert to utils.Retry
3141
3142   retries = 0
3143   degr_retries = 10 # in seconds, as we sleep 1 second each time
3144   while True:
3145     max_time = 0
3146     done = True
3147     cumul_degraded = False
3148     rstats = lu.rpc.call_blockdev_getmirrorstatus(node, disks)
3149     msg = rstats.fail_msg
3150     if msg:
3151       lu.LogWarning("Can't get any data from node %s: %s", node, msg)
3152       retries += 1
3153       if retries >= 10:
3154         raise errors.RemoteError("Can't contact node %s for mirror data,"
3155                                  " aborting." % node)
3156       time.sleep(6)
3157       continue
3158     rstats = rstats.payload
3159     retries = 0
3160     for i, mstat in enumerate(rstats):
3161       if mstat is None:
3162         lu.LogWarning("Can't compute data for node %s/%s",
3163                            node, disks[i].iv_name)
3164         continue
3165
3166       cumul_degraded = (cumul_degraded or
3167                         (mstat.is_degraded and mstat.sync_percent is None))
3168       if mstat.sync_percent is not None:
3169         done = False
3170         if mstat.estimated_time is not None:
3171           rem_time = ("%s remaining (estimated)" %
3172                       utils.FormatSeconds(mstat.estimated_time))
3173           max_time = mstat.estimated_time
3174         else:
3175           rem_time = "no time estimate"
3176         lu.proc.LogInfo("- device %s: %5.2f%% done, %s" %
3177                         (disks[i].iv_name, mstat.sync_percent, rem_time))
3178
3179     # if we're done but degraded, let's do a few small retries, to
3180     # make sure we see a stable and not transient situation; therefore
3181     # we force restart of the loop
3182     if (done or oneshot) and cumul_degraded and degr_retries > 0:
3183       logging.info("Degraded disks found, %d retries left", degr_retries)
3184       degr_retries -= 1
3185       time.sleep(1)
3186       continue
3187
3188     if done or oneshot:
3189       break
3190
3191     time.sleep(min(60, max_time))
3192
3193   if done:
3194     lu.proc.LogInfo("Instance %s's disks are in sync." % instance.name)
3195   return not cumul_degraded
3196
3197
3198 def _CheckDiskConsistency(lu, dev, node, on_primary, ldisk=False):
3199   """Check that mirrors are not degraded.
3200
3201   The ldisk parameter, if True, will change the test from the
3202   is_degraded attribute (which represents overall non-ok status for
3203   the device(s)) to the ldisk (representing the local storage status).
3204
3205   """
3206   lu.cfg.SetDiskID(dev, node)
3207
3208   result = True
3209
3210   if on_primary or dev.AssembleOnSecondary():
3211     rstats = lu.rpc.call_blockdev_find(node, dev)
3212     msg = rstats.fail_msg
3213     if msg:
3214       lu.LogWarning("Can't find disk on node %s: %s", node, msg)
3215       result = False
3216     elif not rstats.payload:
3217       lu.LogWarning("Can't find disk on node %s", node)
3218       result = False
3219     else:
3220       if ldisk:
3221         result = result and rstats.payload.ldisk_status == constants.LDS_OKAY
3222       else:
3223         result = result and not rstats.payload.is_degraded
3224
3225   if dev.children:
3226     for child in dev.children:
3227       result = result and _CheckDiskConsistency(lu, child, node, on_primary)
3228
3229   return result
3230
3231
3232 class LUOobCommand(NoHooksLU):
3233   """Logical unit for OOB handling.
3234
3235   """
3236   REG_BGL = False
3237   _SKIP_MASTER = (constants.OOB_POWER_OFF, constants.OOB_POWER_CYCLE)
3238
3239   def CheckPrereq(self):
3240     """Check prerequisites.
3241
3242     This checks:
3243      - the node exists in the configuration
3244      - OOB is supported
3245
3246     Any errors are signaled by raising errors.OpPrereqError.
3247
3248     """
3249     self.nodes = []
3250     self.master_node = self.cfg.GetMasterNode()
3251
3252     if self.op.node_names:
3253       if self.op.command in self._SKIP_MASTER:
3254         if self.master_node in self.op.node_names:
3255           master_node_obj = self.cfg.GetNodeInfo(self.master_node)
3256           master_oob_handler = _SupportsOob(self.cfg, master_node_obj)
3257
3258           if master_oob_handler:
3259             additional_text = ("Run '%s %s %s' if you want to operate on the"
3260                                " master regardless") % (master_oob_handler,
3261                                                         self.op.command,
3262                                                         self.master_node)
3263           else:
3264             additional_text = "The master node does not support out-of-band"
3265
3266           raise errors.OpPrereqError(("Operating on the master node %s is not"
3267                                       " allowed for %s\n%s") %
3268                                      (self.master_node, self.op.command,
3269                                       additional_text), errors.ECODE_INVAL)
3270     else:
3271       self.op.node_names = self.cfg.GetNodeList()
3272       if self.op.command in self._SKIP_MASTER:
3273         self.op.node_names.remove(self.master_node)
3274
3275     if self.op.command in self._SKIP_MASTER:
3276       assert self.master_node not in self.op.node_names
3277
3278     for node_name in self.op.node_names:
3279       node = self.cfg.GetNodeInfo(node_name)
3280
3281       if node is None:
3282         raise errors.OpPrereqError("Node %s not found" % node_name,
3283                                    errors.ECODE_NOENT)
3284       else:
3285         self.nodes.append(node)
3286
3287       if (not self.op.ignore_status and
3288           (self.op.command == constants.OOB_POWER_OFF and not node.offline)):
3289         raise errors.OpPrereqError(("Cannot power off node %s because it is"
3290                                     " not marked offline") % node_name,
3291                                    errors.ECODE_STATE)
3292
3293   def ExpandNames(self):
3294     """Gather locks we need.
3295
3296     """
3297     if self.op.node_names:
3298       self.op.node_names = [_ExpandNodeName(self.cfg, name)
3299                             for name in self.op.node_names]
3300       lock_names = self.op.node_names
3301     else:
3302       lock_names = locking.ALL_SET
3303
3304     self.needed_locks = {
3305       locking.LEVEL_NODE: lock_names,
3306       }
3307
3308   def Exec(self, feedback_fn):
3309     """Execute OOB and return result if we expect any.
3310
3311     """
3312     master_node = self.master_node
3313     ret = []
3314
3315     for node in self.nodes:
3316       node_entry = [(constants.RS_NORMAL, node.name)]
3317       ret.append(node_entry)
3318
3319       oob_program = _SupportsOob(self.cfg, node)
3320
3321       if not oob_program:
3322         node_entry.append((constants.RS_UNAVAIL, None))
3323         continue
3324
3325       logging.info("Executing out-of-band command '%s' using '%s' on %s",
3326                    self.op.command, oob_program, node.name)
3327       result = self.rpc.call_run_oob(master_node, oob_program,
3328                                      self.op.command, node.name,
3329                                      self.op.timeout)
3330
3331       if result.fail_msg:
3332         self.LogWarning("On node '%s' out-of-band RPC failed with: %s",
3333                         node.name, result.fail_msg)
3334         node_entry.append((constants.RS_NODATA, None))
3335       else:
3336         try:
3337           self._CheckPayload(result)
3338         except errors.OpExecError, err:
3339           self.LogWarning("The payload returned by '%s' is not valid: %s",
3340                           node.name, err)
3341           node_entry.append((constants.RS_NODATA, None))
3342         else:
3343           if self.op.command == constants.OOB_HEALTH:
3344             # For health we should log important events
3345             for item, status in result.payload:
3346               if status in [constants.OOB_STATUS_WARNING,
3347                             constants.OOB_STATUS_CRITICAL]:
3348                 self.LogWarning("On node '%s' item '%s' has status '%s'",
3349                                 node.name, item, status)
3350
3351           if self.op.command == constants.OOB_POWER_ON:
3352             node.powered = True
3353           elif self.op.command == constants.OOB_POWER_OFF:
3354             node.powered = False
3355           elif self.op.command == constants.OOB_POWER_STATUS:
3356             powered = result.payload[constants.OOB_POWER_STATUS_POWERED]
3357             if powered != node.powered:
3358               logging.warning(("Recorded power state (%s) of node '%s' does not"
3359                                " match actual power state (%s)"), node.powered,
3360                               node.name, powered)
3361
3362           # For configuration changing commands we should update the node
3363           if self.op.command in (constants.OOB_POWER_ON,
3364                                  constants.OOB_POWER_OFF):
3365             self.cfg.Update(node, feedback_fn)
3366
3367           node_entry.append((constants.RS_NORMAL, result.payload))
3368
3369     return ret
3370
3371   def _CheckPayload(self, result):
3372     """Checks if the payload is valid.
3373
3374     @param result: RPC result
3375     @raises errors.OpExecError: If payload is not valid
3376
3377     """
3378     errs = []
3379     if self.op.command == constants.OOB_HEALTH:
3380       if not isinstance(result.payload, list):
3381         errs.append("command 'health' is expected to return a list but got %s" %
3382                     type(result.payload))
3383       else:
3384         for item, status in result.payload:
3385           if status not in constants.OOB_STATUSES:
3386             errs.append("health item '%s' has invalid status '%s'" %
3387                         (item, status))
3388
3389     if self.op.command == constants.OOB_POWER_STATUS:
3390       if not isinstance(result.payload, dict):
3391         errs.append("power-status is expected to return a dict but got %s" %
3392                     type(result.payload))
3393
3394     if self.op.command in [
3395         constants.OOB_POWER_ON,
3396         constants.OOB_POWER_OFF,
3397         constants.OOB_POWER_CYCLE,
3398         ]:
3399       if result.payload is not None:
3400         errs.append("%s is expected to not return payload but got '%s'" %
3401                     (self.op.command, result.payload))
3402
3403     if errs:
3404       raise errors.OpExecError("Check of out-of-band payload failed due to %s" %
3405                                utils.CommaJoin(errs))
3406
3407
3408
3409 class LUOsDiagnose(NoHooksLU):
3410   """Logical unit for OS diagnose/query.
3411
3412   """
3413   REQ_BGL = False
3414   _HID = "hidden"
3415   _BLK = "blacklisted"
3416   _VLD = "valid"
3417   _FIELDS_STATIC = utils.FieldSet()
3418   _FIELDS_DYNAMIC = utils.FieldSet("name", _VLD, "node_status", "variants",
3419                                    "parameters", "api_versions", _HID, _BLK)
3420
3421   def CheckArguments(self):
3422     if self.op.names:
3423       raise errors.OpPrereqError("Selective OS query not supported",
3424                                  errors.ECODE_INVAL)
3425
3426     _CheckOutputFields(static=self._FIELDS_STATIC,
3427                        dynamic=self._FIELDS_DYNAMIC,
3428                        selected=self.op.output_fields)
3429
3430   def ExpandNames(self):
3431     # Lock all nodes, in shared mode
3432     # Temporary removal of locks, should be reverted later
3433     # TODO: reintroduce locks when they are lighter-weight
3434     self.needed_locks = {}
3435     #self.share_locks[locking.LEVEL_NODE] = 1
3436     #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
3437
3438   @staticmethod
3439   def _DiagnoseByOS(rlist):
3440     """Remaps a per-node return list into an a per-os per-node dictionary
3441
3442     @param rlist: a map with node names as keys and OS objects as values
3443
3444     @rtype: dict
3445     @return: a dictionary with osnames as keys and as value another
3446         map, with nodes as keys and tuples of (path, status, diagnose,
3447         variants, parameters, api_versions) as values, eg::
3448
3449           {"debian-etch": {"node1": [(/usr/lib/..., True, "", [], []),
3450                                      (/srv/..., False, "invalid api")],
3451                            "node2": [(/srv/..., True, "", [], [])]}
3452           }
3453
3454     """
3455     all_os = {}
3456     # we build here the list of nodes that didn't fail the RPC (at RPC
3457     # level), so that nodes with a non-responding node daemon don't
3458     # make all OSes invalid
3459     good_nodes = [node_name for node_name in rlist
3460                   if not rlist[node_name].fail_msg]
3461     for node_name, nr in rlist.items():
3462       if nr.fail_msg or not nr.payload:
3463         continue
3464       for (name, path, status, diagnose, variants,
3465            params, api_versions) in nr.payload:
3466         if name not in all_os:
3467           # build a list of nodes for this os containing empty lists
3468           # for each node in node_list
3469           all_os[name] = {}
3470           for nname in good_nodes:
3471             all_os[name][nname] = []
3472         # convert params from [name, help] to (name, help)
3473         params = [tuple(v) for v in params]
3474         all_os[name][node_name].append((path, status, diagnose,
3475                                         variants, params, api_versions))
3476     return all_os
3477
3478   def Exec(self, feedback_fn):
3479     """Compute the list of OSes.
3480
3481     """
3482     valid_nodes = [node.name
3483                    for node in self.cfg.GetAllNodesInfo().values()
3484                    if not node.offline and node.vm_capable]
3485     node_data = self.rpc.call_os_diagnose(valid_nodes)
3486     pol = self._DiagnoseByOS(node_data)
3487     output = []
3488     cluster = self.cfg.GetClusterInfo()
3489
3490     for os_name in utils.NiceSort(pol.keys()):
3491       os_data = pol[os_name]
3492       row = []
3493       valid = True
3494       (variants, params, api_versions) = null_state = (set(), set(), set())
3495       for idx, osl in enumerate(os_data.values()):
3496         valid = bool(valid and osl and osl[0][1])
3497         if not valid:
3498           (variants, params, api_versions) = null_state
3499           break
3500         node_variants, node_params, node_api = osl[0][3:6]
3501         if idx == 0: # first entry
3502           variants = set(node_variants)
3503           params = set(node_params)
3504           api_versions = set(node_api)
3505         else: # keep consistency
3506           variants.intersection_update(node_variants)
3507           params.intersection_update(node_params)
3508           api_versions.intersection_update(node_api)
3509
3510       is_hid = os_name in cluster.hidden_os
3511       is_blk = os_name in cluster.blacklisted_os
3512       if ((self._HID not in self.op.output_fields and is_hid) or
3513           (self._BLK not in self.op.output_fields and is_blk) or
3514           (self._VLD not in self.op.output_fields and not valid)):
3515         continue
3516
3517       for field in self.op.output_fields:
3518         if field == "name":
3519           val = os_name
3520         elif field == self._VLD:
3521           val = valid
3522         elif field == "node_status":
3523           # this is just a copy of the dict
3524           val = {}
3525           for node_name, nos_list in os_data.items():
3526             val[node_name] = nos_list
3527         elif field == "variants":
3528           val = utils.NiceSort(list(variants))
3529         elif field == "parameters":
3530           val = list(params)
3531         elif field == "api_versions":
3532           val = list(api_versions)
3533         elif field == self._HID:
3534           val = is_hid
3535         elif field == self._BLK:
3536           val = is_blk
3537         else:
3538           raise errors.ParameterError(field)
3539         row.append(val)
3540       output.append(row)
3541
3542     return output
3543
3544
3545 class LUNodeRemove(LogicalUnit):
3546   """Logical unit for removing a node.
3547
3548   """
3549   HPATH = "node-remove"
3550   HTYPE = constants.HTYPE_NODE
3551
3552   def BuildHooksEnv(self):
3553     """Build hooks env.
3554
3555     This doesn't run on the target node in the pre phase as a failed
3556     node would then be impossible to remove.
3557
3558     """
3559     env = {
3560       "OP_TARGET": self.op.node_name,
3561       "NODE_NAME": self.op.node_name,
3562       }
3563     all_nodes = self.cfg.GetNodeList()
3564     try:
3565       all_nodes.remove(self.op.node_name)
3566     except ValueError:
3567       logging.warning("Node %s which is about to be removed not found"
3568                       " in the all nodes list", self.op.node_name)
3569     return env, all_nodes, all_nodes
3570
3571   def CheckPrereq(self):
3572     """Check prerequisites.
3573
3574     This checks:
3575      - the node exists in the configuration
3576      - it does not have primary or secondary instances
3577      - it's not the master
3578
3579     Any errors are signaled by raising errors.OpPrereqError.
3580
3581     """
3582     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
3583     node = self.cfg.GetNodeInfo(self.op.node_name)
3584     assert node is not None
3585
3586     instance_list = self.cfg.GetInstanceList()
3587
3588     masternode = self.cfg.GetMasterNode()
3589     if node.name == masternode:
3590       raise errors.OpPrereqError("Node is the master node,"
3591                                  " you need to failover first.",
3592                                  errors.ECODE_INVAL)
3593
3594     for instance_name in instance_list:
3595       instance = self.cfg.GetInstanceInfo(instance_name)
3596       if node.name in instance.all_nodes:
3597         raise errors.OpPrereqError("Instance %s is still running on the node,"
3598                                    " please remove first." % instance_name,
3599                                    errors.ECODE_INVAL)
3600     self.op.node_name = node.name
3601     self.node = node
3602
3603   def Exec(self, feedback_fn):
3604     """Removes the node from the cluster.
3605
3606     """
3607     node = self.node
3608     logging.info("Stopping the node daemon and removing configs from node %s",
3609                  node.name)
3610
3611     modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
3612
3613     # Promote nodes to master candidate as needed
3614     _AdjustCandidatePool(self, exceptions=[node.name])
3615     self.context.RemoveNode(node.name)
3616
3617     # Run post hooks on the node before it's removed
3618     hm = self.proc.hmclass(self.rpc.call_hooks_runner, self)
3619     try:
3620       hm.RunPhase(constants.HOOKS_PHASE_POST, [node.name])
3621     except:
3622       # pylint: disable-msg=W0702
3623       self.LogWarning("Errors occurred running hooks on %s" % node.name)
3624
3625     result = self.rpc.call_node_leave_cluster(node.name, modify_ssh_setup)
3626     msg = result.fail_msg
3627     if msg:
3628       self.LogWarning("Errors encountered on the remote node while leaving"
3629                       " the cluster: %s", msg)
3630
3631     # Remove node from our /etc/hosts
3632     if self.cfg.GetClusterInfo().modify_etc_hosts:
3633       master_node = self.cfg.GetMasterNode()
3634       result = self.rpc.call_etc_hosts_modify(master_node,
3635                                               constants.ETC_HOSTS_REMOVE,
3636                                               node.name, None)
3637       result.Raise("Can't update hosts file with new host data")
3638       _RedistributeAncillaryFiles(self)
3639
3640
3641 class _NodeQuery(_QueryBase):
3642   FIELDS = query.NODE_FIELDS
3643
3644   def ExpandNames(self, lu):
3645     lu.needed_locks = {}
3646     lu.share_locks[locking.LEVEL_NODE] = 1
3647
3648     if self.names:
3649       self.wanted = _GetWantedNodes(lu, self.names)
3650     else:
3651       self.wanted = locking.ALL_SET
3652
3653     self.do_locking = (self.use_locking and
3654                        query.NQ_LIVE in self.requested_data)
3655
3656     if self.do_locking:
3657       # if we don't request only static fields, we need to lock the nodes
3658       lu.needed_locks[locking.LEVEL_NODE] = self.wanted
3659
3660   def DeclareLocks(self, lu, level):
3661     pass
3662
3663   def _GetQueryData(self, lu):
3664     """Computes the list of nodes and their attributes.
3665
3666     """
3667     all_info = lu.cfg.GetAllNodesInfo()
3668
3669     nodenames = self._GetNames(lu, all_info.keys(), locking.LEVEL_NODE)
3670
3671     # Gather data as requested
3672     if query.NQ_LIVE in self.requested_data:
3673       # filter out non-vm_capable nodes
3674       toquery_nodes = [name for name in nodenames if all_info[name].vm_capable]
3675
3676       node_data = lu.rpc.call_node_info(toquery_nodes, lu.cfg.GetVGName(),
3677                                         lu.cfg.GetHypervisorType())
3678       live_data = dict((name, nresult.payload)
3679                        for (name, nresult) in node_data.items()
3680                        if not nresult.fail_msg and nresult.payload)
3681     else:
3682       live_data = None
3683
3684     if query.NQ_INST in self.requested_data:
3685       node_to_primary = dict([(name, set()) for name in nodenames])
3686       node_to_secondary = dict([(name, set()) for name in nodenames])
3687
3688       inst_data = lu.cfg.GetAllInstancesInfo()
3689
3690       for inst in inst_data.values():
3691         if inst.primary_node in node_to_primary:
3692           node_to_primary[inst.primary_node].add(inst.name)
3693         for secnode in inst.secondary_nodes:
3694           if secnode in node_to_secondary:
3695             node_to_secondary[secnode].add(inst.name)
3696     else:
3697       node_to_primary = None
3698       node_to_secondary = None
3699
3700     if query.NQ_OOB in self.requested_data:
3701       oob_support = dict((name, bool(_SupportsOob(lu.cfg, node)))
3702                          for name, node in all_info.iteritems())
3703     else:
3704       oob_support = None
3705
3706     if query.NQ_GROUP in self.requested_data:
3707       groups = lu.cfg.GetAllNodeGroupsInfo()
3708     else:
3709       groups = {}
3710
3711     return query.NodeQueryData([all_info[name] for name in nodenames],
3712                                live_data, lu.cfg.GetMasterNode(),
3713                                node_to_primary, node_to_secondary, groups,
3714                                oob_support, lu.cfg.GetClusterInfo())
3715
3716
3717 class LUNodeQuery(NoHooksLU):
3718   """Logical unit for querying nodes.
3719
3720   """
3721   # pylint: disable-msg=W0142
3722   REQ_BGL = False
3723
3724   def CheckArguments(self):
3725     self.nq = _NodeQuery(qlang.MakeSimpleFilter("name", self.op.names),
3726                          self.op.output_fields, self.op.use_locking)
3727
3728   def ExpandNames(self):
3729     self.nq.ExpandNames(self)
3730
3731   def Exec(self, feedback_fn):
3732     return self.nq.OldStyleQuery(self)
3733
3734
3735 class LUNodeQueryvols(NoHooksLU):
3736   """Logical unit for getting volumes on node(s).
3737
3738   """
3739   REQ_BGL = False
3740   _FIELDS_DYNAMIC = utils.FieldSet("phys", "vg", "name", "size", "instance")
3741   _FIELDS_STATIC = utils.FieldSet("node")
3742
3743   def CheckArguments(self):
3744     _CheckOutputFields(static=self._FIELDS_STATIC,
3745                        dynamic=self._FIELDS_DYNAMIC,
3746                        selected=self.op.output_fields)
3747
3748   def ExpandNames(self):
3749     self.needed_locks = {}
3750     self.share_locks[locking.LEVEL_NODE] = 1
3751     if not self.op.nodes:
3752       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
3753     else:
3754       self.needed_locks[locking.LEVEL_NODE] = \
3755         _GetWantedNodes(self, self.op.nodes)
3756
3757   def Exec(self, feedback_fn):
3758     """Computes the list of nodes and their attributes.
3759
3760     """
3761     nodenames = self.acquired_locks[locking.LEVEL_NODE]
3762     volumes = self.rpc.call_node_volumes(nodenames)
3763
3764     ilist = [self.cfg.GetInstanceInfo(iname) for iname
3765              in self.cfg.GetInstanceList()]
3766
3767     lv_by_node = dict([(inst, inst.MapLVsByNode()) for inst in ilist])
3768
3769     output = []
3770     for node in nodenames:
3771       nresult = volumes[node]
3772       if nresult.offline:
3773         continue
3774       msg = nresult.fail_msg
3775       if msg:
3776         self.LogWarning("Can't compute volume data on node %s: %s", node, msg)
3777         continue
3778
3779       node_vols = nresult.payload[:]
3780       node_vols.sort(key=lambda vol: vol['dev'])
3781
3782       for vol in node_vols:
3783         node_output = []
3784         for field in self.op.output_fields:
3785           if field == "node":
3786             val = node
3787           elif field == "phys":
3788             val = vol['dev']
3789           elif field == "vg":
3790             val = vol['vg']
3791           elif field == "name":
3792             val = vol['name']
3793           elif field == "size":
3794             val = int(float(vol['size']))
3795           elif field == "instance":
3796             for inst in ilist:
3797               if node not in lv_by_node[inst]:
3798                 continue
3799               if vol['name'] in lv_by_node[inst][node]:
3800                 val = inst.name
3801                 break
3802             else:
3803               val = '-'
3804           else:
3805             raise errors.ParameterError(field)
3806           node_output.append(str(val))
3807
3808         output.append(node_output)
3809
3810     return output
3811
3812
3813 class LUNodeQueryStorage(NoHooksLU):
3814   """Logical unit for getting information on storage units on node(s).
3815
3816   """
3817   _FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
3818   REQ_BGL = False
3819
3820   def CheckArguments(self):
3821     _CheckOutputFields(static=self._FIELDS_STATIC,
3822                        dynamic=utils.FieldSet(*constants.VALID_STORAGE_FIELDS),
3823                        selected=self.op.output_fields)
3824
3825   def ExpandNames(self):
3826     self.needed_locks = {}
3827     self.share_locks[locking.LEVEL_NODE] = 1
3828
3829     if self.op.nodes:
3830       self.needed_locks[locking.LEVEL_NODE] = \
3831         _GetWantedNodes(self, self.op.nodes)
3832     else:
3833       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
3834
3835   def Exec(self, feedback_fn):
3836     """Computes the list of nodes and their attributes.
3837
3838     """
3839     self.nodes = self.acquired_locks[locking.LEVEL_NODE]
3840
3841     # Always get name to sort by
3842     if constants.SF_NAME in self.op.output_fields:
3843       fields = self.op.output_fields[:]
3844     else:
3845       fields = [constants.SF_NAME] + self.op.output_fields
3846
3847     # Never ask for node or type as it's only known to the LU
3848     for extra in [constants.SF_NODE, constants.SF_TYPE]:
3849       while extra in fields:
3850         fields.remove(extra)
3851
3852     field_idx = dict([(name, idx) for (idx, name) in enumerate(fields)])
3853     name_idx = field_idx[constants.SF_NAME]
3854
3855     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
3856     data = self.rpc.call_storage_list(self.nodes,
3857                                       self.op.storage_type, st_args,
3858                                       self.op.name, fields)
3859
3860     result = []
3861
3862     for node in utils.NiceSort(self.nodes):
3863       nresult = data[node]
3864       if nresult.offline:
3865         continue
3866
3867       msg = nresult.fail_msg
3868       if msg:
3869         self.LogWarning("Can't get storage data from node %s: %s", node, msg)
3870         continue
3871
3872       rows = dict([(row[name_idx], row) for row in nresult.payload])
3873
3874       for name in utils.NiceSort(rows.keys()):
3875         row = rows[name]
3876
3877         out = []
3878
3879         for field in self.op.output_fields:
3880           if field == constants.SF_NODE:
3881             val = node
3882           elif field == constants.SF_TYPE:
3883             val = self.op.storage_type
3884           elif field in field_idx:
3885             val = row[field_idx[field]]
3886           else:
3887             raise errors.ParameterError(field)
3888
3889           out.append(val)
3890
3891         result.append(out)
3892
3893     return result
3894
3895
3896 class _InstanceQuery(_QueryBase):
3897   FIELDS = query.INSTANCE_FIELDS
3898
3899   def ExpandNames(self, lu):
3900     lu.needed_locks = {}
3901     lu.share_locks[locking.LEVEL_INSTANCE] = 1
3902     lu.share_locks[locking.LEVEL_NODE] = 1
3903
3904     if self.names:
3905       self.wanted = _GetWantedInstances(lu, self.names)
3906     else:
3907       self.wanted = locking.ALL_SET
3908
3909     self.do_locking = (self.use_locking and
3910                        query.IQ_LIVE in self.requested_data)
3911     if self.do_locking:
3912       lu.needed_locks[locking.LEVEL_INSTANCE] = self.wanted
3913       lu.needed_locks[locking.LEVEL_NODE] = []
3914       lu.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
3915
3916   def DeclareLocks(self, lu, level):
3917     if level == locking.LEVEL_NODE and self.do_locking:
3918       lu._LockInstancesNodes() # pylint: disable-msg=W0212
3919
3920   def _GetQueryData(self, lu):
3921     """Computes the list of instances and their attributes.
3922
3923     """
3924     cluster = lu.cfg.GetClusterInfo()
3925     all_info = lu.cfg.GetAllInstancesInfo()
3926
3927     instance_names = self._GetNames(lu, all_info.keys(), locking.LEVEL_INSTANCE)
3928
3929     instance_list = [all_info[name] for name in instance_names]
3930     nodes = frozenset(itertools.chain(*(inst.all_nodes
3931                                         for inst in instance_list)))
3932     hv_list = list(set([inst.hypervisor for inst in instance_list]))
3933     bad_nodes = []
3934     offline_nodes = []
3935     wrongnode_inst = set()
3936
3937     # Gather data as requested
3938     if self.requested_data & set([query.IQ_LIVE, query.IQ_CONSOLE]):
3939       live_data = {}
3940       node_data = lu.rpc.call_all_instances_info(nodes, hv_list)
3941       for name in nodes:
3942         result = node_data[name]
3943         if result.offline:
3944           # offline nodes will be in both lists
3945           assert result.fail_msg
3946           offline_nodes.append(name)
3947         if result.fail_msg:
3948           bad_nodes.append(name)
3949         elif result.payload:
3950           for inst in result.payload:
3951             if all_info[inst].primary_node == name:
3952               live_data.update(result.payload)
3953             else:
3954               wrongnode_inst.add(inst)
3955         # else no instance is alive
3956     else:
3957       live_data = {}
3958
3959     if query.IQ_DISKUSAGE in self.requested_data:
3960       disk_usage = dict((inst.name,
3961                          _ComputeDiskSize(inst.disk_template,
3962                                           [{"size": disk.size}
3963                                            for disk in inst.disks]))
3964                         for inst in instance_list)
3965     else:
3966       disk_usage = None
3967
3968     if query.IQ_CONSOLE in self.requested_data:
3969       consinfo = {}
3970       for inst in instance_list:
3971         if inst.name in live_data:
3972           # Instance is running
3973           consinfo[inst.name] = _GetInstanceConsole(cluster, inst)
3974         else:
3975           consinfo[inst.name] = None
3976       assert set(consinfo.keys()) == set(instance_names)
3977     else:
3978       consinfo = None
3979
3980     return query.InstanceQueryData(instance_list, lu.cfg.GetClusterInfo(),
3981                                    disk_usage, offline_nodes, bad_nodes,
3982                                    live_data, wrongnode_inst, consinfo)
3983
3984
3985 class LUQuery(NoHooksLU):
3986   """Query for resources/items of a certain kind.
3987
3988   """
3989   # pylint: disable-msg=W0142
3990   REQ_BGL = False
3991
3992   def CheckArguments(self):
3993     qcls = _GetQueryImplementation(self.op.what)
3994
3995     self.impl = qcls(self.op.filter, self.op.fields, False)
3996
3997   def ExpandNames(self):
3998     self.impl.ExpandNames(self)
3999
4000   def DeclareLocks(self, level):
4001     self.impl.DeclareLocks(self, level)
4002
4003   def Exec(self, feedback_fn):
4004     return self.impl.NewStyleQuery(self)
4005
4006
4007 class LUQueryFields(NoHooksLU):
4008   """Query for resources/items of a certain kind.
4009
4010   """
4011   # pylint: disable-msg=W0142
4012   REQ_BGL = False
4013
4014   def CheckArguments(self):
4015     self.qcls = _GetQueryImplementation(self.op.what)
4016
4017   def ExpandNames(self):
4018     self.needed_locks = {}
4019
4020   def Exec(self, feedback_fn):
4021     return self.qcls.FieldsQuery(self.op.fields)
4022
4023
4024 class LUNodeModifyStorage(NoHooksLU):
4025   """Logical unit for modifying a storage volume on a node.
4026
4027   """
4028   REQ_BGL = False
4029
4030   def CheckArguments(self):
4031     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
4032
4033     storage_type = self.op.storage_type
4034
4035     try:
4036       modifiable = constants.MODIFIABLE_STORAGE_FIELDS[storage_type]
4037     except KeyError:
4038       raise errors.OpPrereqError("Storage units of type '%s' can not be"
4039                                  " modified" % storage_type,
4040                                  errors.ECODE_INVAL)
4041
4042     diff = set(self.op.changes.keys()) - modifiable
4043     if diff:
4044       raise errors.OpPrereqError("The following fields can not be modified for"
4045                                  " storage units of type '%s': %r" %
4046                                  (storage_type, list(diff)),
4047                                  errors.ECODE_INVAL)
4048
4049   def ExpandNames(self):
4050     self.needed_locks = {
4051       locking.LEVEL_NODE: self.op.node_name,
4052       }
4053
4054   def Exec(self, feedback_fn):
4055     """Computes the list of nodes and their attributes.
4056
4057     """
4058     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
4059     result = self.rpc.call_storage_modify(self.op.node_name,
4060                                           self.op.storage_type, st_args,
4061                                           self.op.name, self.op.changes)
4062     result.Raise("Failed to modify storage unit '%s' on %s" %
4063                  (self.op.name, self.op.node_name))
4064
4065
4066 class LUNodeAdd(LogicalUnit):
4067   """Logical unit for adding node to the cluster.
4068
4069   """
4070   HPATH = "node-add"
4071   HTYPE = constants.HTYPE_NODE
4072   _NFLAGS = ["master_capable", "vm_capable"]
4073
4074   def CheckArguments(self):
4075     self.primary_ip_family = self.cfg.GetPrimaryIPFamily()
4076     # validate/normalize the node name
4077     self.hostname = netutils.GetHostname(name=self.op.node_name,
4078                                          family=self.primary_ip_family)
4079     self.op.node_name = self.hostname.name
4080     if self.op.readd and self.op.group:
4081       raise errors.OpPrereqError("Cannot pass a node group when a node is"
4082                                  " being readded", errors.ECODE_INVAL)
4083
4084   def BuildHooksEnv(self):
4085     """Build hooks env.
4086
4087     This will run on all nodes before, and on all nodes + the new node after.
4088
4089     """
4090     env = {
4091       "OP_TARGET": self.op.node_name,
4092       "NODE_NAME": self.op.node_name,
4093       "NODE_PIP": self.op.primary_ip,
4094       "NODE_SIP": self.op.secondary_ip,
4095       "MASTER_CAPABLE": str(self.op.master_capable),
4096       "VM_CAPABLE": str(self.op.vm_capable),
4097       }
4098     nodes_0 = self.cfg.GetNodeList()
4099     nodes_1 = nodes_0 + [self.op.node_name, ]
4100     return env, nodes_0, nodes_1
4101
4102   def CheckPrereq(self):
4103     """Check prerequisites.
4104
4105     This checks:
4106      - the new node is not already in the config
4107      - it is resolvable
4108      - its parameters (single/dual homed) matches the cluster
4109
4110     Any errors are signaled by raising errors.OpPrereqError.
4111
4112     """
4113     cfg = self.cfg
4114     hostname = self.hostname
4115     node = hostname.name
4116     primary_ip = self.op.primary_ip = hostname.ip
4117     if self.op.secondary_ip is None:
4118       if self.primary_ip_family == netutils.IP6Address.family:
4119         raise errors.OpPrereqError("When using a IPv6 primary address, a valid"
4120                                    " IPv4 address must be given as secondary",
4121                                    errors.ECODE_INVAL)
4122       self.op.secondary_ip = primary_ip
4123
4124     secondary_ip = self.op.secondary_ip
4125     if not netutils.IP4Address.IsValid(secondary_ip):
4126       raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
4127                                  " address" % secondary_ip, errors.ECODE_INVAL)
4128
4129     node_list = cfg.GetNodeList()
4130     if not self.op.readd and node in node_list:
4131       raise errors.OpPrereqError("Node %s is already in the configuration" %
4132                                  node, errors.ECODE_EXISTS)
4133     elif self.op.readd and node not in node_list:
4134       raise errors.OpPrereqError("Node %s is not in the configuration" % node,
4135                                  errors.ECODE_NOENT)
4136
4137     self.changed_primary_ip = False
4138
4139     for existing_node_name in node_list:
4140       existing_node = cfg.GetNodeInfo(existing_node_name)
4141
4142       if self.op.readd and node == existing_node_name:
4143         if existing_node.secondary_ip != secondary_ip:
4144           raise errors.OpPrereqError("Readded node doesn't have the same IP"
4145                                      " address configuration as before",
4146                                      errors.ECODE_INVAL)
4147         if existing_node.primary_ip != primary_ip:
4148           self.changed_primary_ip = True
4149
4150         continue
4151
4152       if (existing_node.primary_ip == primary_ip or
4153           existing_node.secondary_ip == primary_ip or
4154           existing_node.primary_ip == secondary_ip or
4155           existing_node.secondary_ip == secondary_ip):
4156         raise errors.OpPrereqError("New node ip address(es) conflict with"
4157                                    " existing node %s" % existing_node.name,
4158                                    errors.ECODE_NOTUNIQUE)
4159
4160     # After this 'if' block, None is no longer a valid value for the
4161     # _capable op attributes
4162     if self.op.readd:
4163       old_node = self.cfg.GetNodeInfo(node)
4164       assert old_node is not None, "Can't retrieve locked node %s" % node
4165       for attr in self._NFLAGS:
4166         if getattr(self.op, attr) is None:
4167           setattr(self.op, attr, getattr(old_node, attr))
4168     else:
4169       for attr in self._NFLAGS:
4170         if getattr(self.op, attr) is None:
4171           setattr(self.op, attr, True)
4172
4173     if self.op.readd and not self.op.vm_capable:
4174       pri, sec = cfg.GetNodeInstances(node)
4175       if pri or sec:
4176         raise errors.OpPrereqError("Node %s being re-added with vm_capable"
4177                                    " flag set to false, but it already holds"
4178                                    " instances" % node,
4179                                    errors.ECODE_STATE)
4180
4181     # check that the type of the node (single versus dual homed) is the
4182     # same as for the master
4183     myself = cfg.GetNodeInfo(self.cfg.GetMasterNode())
4184     master_singlehomed = myself.secondary_ip == myself.primary_ip
4185     newbie_singlehomed = secondary_ip == primary_ip
4186     if master_singlehomed != newbie_singlehomed:
4187       if master_singlehomed:
4188         raise errors.OpPrereqError("The master has no secondary ip but the"
4189                                    " new node has one",
4190                                    errors.ECODE_INVAL)
4191       else:
4192         raise errors.OpPrereqError("The master has a secondary ip but the"
4193                                    " new node doesn't have one",
4194                                    errors.ECODE_INVAL)
4195
4196     # checks reachability
4197     if not netutils.TcpPing(primary_ip, constants.DEFAULT_NODED_PORT):
4198       raise errors.OpPrereqError("Node not reachable by ping",
4199                                  errors.ECODE_ENVIRON)
4200
4201     if not newbie_singlehomed:
4202       # check reachability from my secondary ip to newbie's secondary ip
4203       if not netutils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
4204                            source=myself.secondary_ip):
4205         raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
4206                                    " based ping to node daemon port",
4207                                    errors.ECODE_ENVIRON)
4208
4209     if self.op.readd:
4210       exceptions = [node]
4211     else:
4212       exceptions = []
4213
4214     if self.op.master_capable:
4215       self.master_candidate = _DecideSelfPromotion(self, exceptions=exceptions)
4216     else:
4217       self.master_candidate = False
4218
4219     if self.op.readd:
4220       self.new_node = old_node
4221     else:
4222       node_group = cfg.LookupNodeGroup(self.op.group)
4223       self.new_node = objects.Node(name=node,
4224                                    primary_ip=primary_ip,
4225                                    secondary_ip=secondary_ip,
4226                                    master_candidate=self.master_candidate,
4227                                    offline=False, drained=False,
4228                                    group=node_group)
4229
4230     if self.op.ndparams:
4231       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
4232
4233   def Exec(self, feedback_fn):
4234     """Adds the new node to the cluster.
4235
4236     """
4237     new_node = self.new_node
4238     node = new_node.name
4239
4240     # We adding a new node so we assume it's powered
4241     new_node.powered = True
4242
4243     # for re-adds, reset the offline/drained/master-candidate flags;
4244     # we need to reset here, otherwise offline would prevent RPC calls
4245     # later in the procedure; this also means that if the re-add
4246     # fails, we are left with a non-offlined, broken node
4247     if self.op.readd:
4248       new_node.drained = new_node.offline = False # pylint: disable-msg=W0201
4249       self.LogInfo("Readding a node, the offline/drained flags were reset")
4250       # if we demote the node, we do cleanup later in the procedure
4251       new_node.master_candidate = self.master_candidate
4252       if self.changed_primary_ip:
4253         new_node.primary_ip = self.op.primary_ip
4254
4255     # copy the master/vm_capable flags
4256     for attr in self._NFLAGS:
4257       setattr(new_node, attr, getattr(self.op, attr))
4258
4259     # notify the user about any possible mc promotion
4260     if new_node.master_candidate:
4261       self.LogInfo("Node will be a master candidate")
4262
4263     if self.op.ndparams:
4264       new_node.ndparams = self.op.ndparams
4265     else:
4266       new_node.ndparams = {}
4267
4268     # check connectivity
4269     result = self.rpc.call_version([node])[node]
4270     result.Raise("Can't get version information from node %s" % node)
4271     if constants.PROTOCOL_VERSION == result.payload:
4272       logging.info("Communication to node %s fine, sw version %s match",
4273                    node, result.payload)
4274     else:
4275       raise errors.OpExecError("Version mismatch master version %s,"
4276                                " node version %s" %
4277                                (constants.PROTOCOL_VERSION, result.payload))
4278
4279     # Add node to our /etc/hosts, and add key to known_hosts
4280     if self.cfg.GetClusterInfo().modify_etc_hosts:
4281       master_node = self.cfg.GetMasterNode()
4282       result = self.rpc.call_etc_hosts_modify(master_node,
4283                                               constants.ETC_HOSTS_ADD,
4284                                               self.hostname.name,
4285                                               self.hostname.ip)
4286       result.Raise("Can't update hosts file with new host data")
4287
4288     if new_node.secondary_ip != new_node.primary_ip:
4289       _CheckNodeHasSecondaryIP(self, new_node.name, new_node.secondary_ip,
4290                                False)
4291
4292     node_verify_list = [self.cfg.GetMasterNode()]
4293     node_verify_param = {
4294       constants.NV_NODELIST: [node],
4295       # TODO: do a node-net-test as well?
4296     }
4297
4298     result = self.rpc.call_node_verify(node_verify_list, node_verify_param,
4299                                        self.cfg.GetClusterName())
4300     for verifier in node_verify_list:
4301       result[verifier].Raise("Cannot communicate with node %s" % verifier)
4302       nl_payload = result[verifier].payload[constants.NV_NODELIST]
4303       if nl_payload:
4304         for failed in nl_payload:
4305           feedback_fn("ssh/hostname verification failed"
4306                       " (checking from %s): %s" %
4307                       (verifier, nl_payload[failed]))
4308         raise errors.OpExecError("ssh/hostname verification failed.")
4309
4310     if self.op.readd:
4311       _RedistributeAncillaryFiles(self)
4312       self.context.ReaddNode(new_node)
4313       # make sure we redistribute the config
4314       self.cfg.Update(new_node, feedback_fn)
4315       # and make sure the new node will not have old files around
4316       if not new_node.master_candidate:
4317         result = self.rpc.call_node_demote_from_mc(new_node.name)
4318         msg = result.fail_msg
4319         if msg:
4320           self.LogWarning("Node failed to demote itself from master"
4321                           " candidate status: %s" % msg)
4322     else:
4323       _RedistributeAncillaryFiles(self, additional_nodes=[node],
4324                                   additional_vm=self.op.vm_capable)
4325       self.context.AddNode(new_node, self.proc.GetECId())
4326
4327
4328 class LUNodeSetParams(LogicalUnit):
4329   """Modifies the parameters of a node.
4330
4331   @cvar _F2R: a dictionary from tuples of flags (mc, drained, offline)
4332       to the node role (as _ROLE_*)
4333   @cvar _R2F: a dictionary from node role to tuples of flags
4334   @cvar _FLAGS: a list of attribute names corresponding to the flags
4335
4336   """
4337   HPATH = "node-modify"
4338   HTYPE = constants.HTYPE_NODE
4339   REQ_BGL = False
4340   (_ROLE_CANDIDATE, _ROLE_DRAINED, _ROLE_OFFLINE, _ROLE_REGULAR) = range(4)
4341   _F2R = {
4342     (True, False, False): _ROLE_CANDIDATE,
4343     (False, True, False): _ROLE_DRAINED,
4344     (False, False, True): _ROLE_OFFLINE,
4345     (False, False, False): _ROLE_REGULAR,
4346     }
4347   _R2F = dict((v, k) for k, v in _F2R.items())
4348   _FLAGS = ["master_candidate", "drained", "offline"]
4349
4350   def CheckArguments(self):
4351     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
4352     all_mods = [self.op.offline, self.op.master_candidate, self.op.drained,
4353                 self.op.master_capable, self.op.vm_capable,
4354                 self.op.secondary_ip, self.op.ndparams]
4355     if all_mods.count(None) == len(all_mods):
4356       raise errors.OpPrereqError("Please pass at least one modification",
4357                                  errors.ECODE_INVAL)
4358     if all_mods.count(True) > 1:
4359       raise errors.OpPrereqError("Can't set the node into more than one"
4360                                  " state at the same time",
4361                                  errors.ECODE_INVAL)
4362
4363     # Boolean value that tells us whether we might be demoting from MC
4364     self.might_demote = (self.op.master_candidate == False or
4365                          self.op.offline == True or
4366                          self.op.drained == True or
4367                          self.op.master_capable == False)
4368
4369     if self.op.secondary_ip:
4370       if not netutils.IP4Address.IsValid(self.op.secondary_ip):
4371         raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
4372                                    " address" % self.op.secondary_ip,
4373                                    errors.ECODE_INVAL)
4374
4375     self.lock_all = self.op.auto_promote and self.might_demote
4376     self.lock_instances = self.op.secondary_ip is not None
4377
4378   def ExpandNames(self):
4379     if self.lock_all:
4380       self.needed_locks = {locking.LEVEL_NODE: locking.ALL_SET}
4381     else:
4382       self.needed_locks = {locking.LEVEL_NODE: self.op.node_name}
4383
4384     if self.lock_instances:
4385       self.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET
4386
4387   def DeclareLocks(self, level):
4388     # If we have locked all instances, before waiting to lock nodes, release
4389     # all the ones living on nodes unrelated to the current operation.
4390     if level == locking.LEVEL_NODE and self.lock_instances:
4391       instances_release = []
4392       instances_keep = []
4393       self.affected_instances = []
4394       if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
4395         for instance_name in self.acquired_locks[locking.LEVEL_INSTANCE]:
4396           instance = self.context.cfg.GetInstanceInfo(instance_name)
4397           i_mirrored = instance.disk_template in constants.DTS_NET_MIRROR
4398           if i_mirrored and self.op.node_name in instance.all_nodes:
4399             instances_keep.append(instance_name)
4400             self.affected_instances.append(instance)
4401           else:
4402             instances_release.append(instance_name)
4403         if instances_release:
4404           self.context.glm.release(locking.LEVEL_INSTANCE, instances_release)
4405           self.acquired_locks[locking.LEVEL_INSTANCE] = instances_keep
4406
4407   def BuildHooksEnv(self):
4408     """Build hooks env.
4409
4410     This runs on the master node.
4411
4412     """
4413     env = {
4414       "OP_TARGET": self.op.node_name,
4415       "MASTER_CANDIDATE": str(self.op.master_candidate),
4416       "OFFLINE": str(self.op.offline),
4417       "DRAINED": str(self.op.drained),
4418       "MASTER_CAPABLE": str(self.op.master_capable),
4419       "VM_CAPABLE": str(self.op.vm_capable),
4420       }
4421     nl = [self.cfg.GetMasterNode(),
4422           self.op.node_name]
4423     return env, nl, nl
4424
4425   def CheckPrereq(self):
4426     """Check prerequisites.
4427
4428     This only checks the instance list against the existing names.
4429
4430     """
4431     node = self.node = self.cfg.GetNodeInfo(self.op.node_name)
4432
4433     if (self.op.master_candidate is not None or
4434         self.op.drained is not None or
4435         self.op.offline is not None):
4436       # we can't change the master's node flags
4437       if self.op.node_name == self.cfg.GetMasterNode():
4438         raise errors.OpPrereqError("The master role can be changed"
4439                                    " only via master-failover",
4440                                    errors.ECODE_INVAL)
4441
4442     if self.op.master_candidate and not node.master_capable:
4443       raise errors.OpPrereqError("Node %s is not master capable, cannot make"
4444                                  " it a master candidate" % node.name,
4445                                  errors.ECODE_STATE)
4446
4447     if self.op.vm_capable == False:
4448       (ipri, isec) = self.cfg.GetNodeInstances(self.op.node_name)
4449       if ipri or isec:
4450         raise errors.OpPrereqError("Node %s hosts instances, cannot unset"
4451                                    " the vm_capable flag" % node.name,
4452                                    errors.ECODE_STATE)
4453
4454     if node.master_candidate and self.might_demote and not self.lock_all:
4455       assert not self.op.auto_promote, "auto_promote set but lock_all not"
4456       # check if after removing the current node, we're missing master
4457       # candidates
4458       (mc_remaining, mc_should, _) = \
4459           self.cfg.GetMasterCandidateStats(exceptions=[node.name])
4460       if mc_remaining < mc_should:
4461         raise errors.OpPrereqError("Not enough master candidates, please"
4462                                    " pass auto promote option to allow"
4463                                    " promotion", errors.ECODE_STATE)
4464
4465     self.old_flags = old_flags = (node.master_candidate,
4466                                   node.drained, node.offline)
4467     assert old_flags in self._F2R, "Un-handled old flags  %s" % str(old_flags)
4468     self.old_role = old_role = self._F2R[old_flags]
4469
4470     # Check for ineffective changes
4471     for attr in self._FLAGS:
4472       if (getattr(self.op, attr) == False and getattr(node, attr) == False):
4473         self.LogInfo("Ignoring request to unset flag %s, already unset", attr)
4474         setattr(self.op, attr, None)
4475
4476     # Past this point, any flag change to False means a transition
4477     # away from the respective state, as only real changes are kept
4478
4479     # TODO: We might query the real power state if it supports OOB
4480     if _SupportsOob(self.cfg, node):
4481       if self.op.offline is False and not (node.powered or
4482                                            self.op.powered == True):
4483         raise errors.OpPrereqError(("Please power on node %s first before you"
4484                                     " can reset offline state") %
4485                                    self.op.node_name)
4486     elif self.op.powered is not None:
4487       raise errors.OpPrereqError(("Unable to change powered state for node %s"
4488                                   " which does not support out-of-band"
4489                                   " handling") % self.op.node_name)
4490
4491     # If we're being deofflined/drained, we'll MC ourself if needed
4492     if (self.op.drained == False or self.op.offline == False or
4493         (self.op.master_capable and not node.master_capable)):
4494       if _DecideSelfPromotion(self):
4495         self.op.master_candidate = True
4496         self.LogInfo("Auto-promoting node to master candidate")
4497
4498     # If we're no longer master capable, we'll demote ourselves from MC
4499     if self.op.master_capable == False and node.master_candidate:
4500       self.LogInfo("Demoting from master candidate")
4501       self.op.master_candidate = False
4502
4503     # Compute new role
4504     assert [getattr(self.op, attr) for attr in self._FLAGS].count(True) <= 1
4505     if self.op.master_candidate:
4506       new_role = self._ROLE_CANDIDATE
4507     elif self.op.drained:
4508       new_role = self._ROLE_DRAINED
4509     elif self.op.offline:
4510       new_role = self._ROLE_OFFLINE
4511     elif False in [self.op.master_candidate, self.op.drained, self.op.offline]:
4512       # False is still in new flags, which means we're un-setting (the
4513       # only) True flag
4514       new_role = self._ROLE_REGULAR
4515     else: # no new flags, nothing, keep old role
4516       new_role = old_role
4517
4518     self.new_role = new_role
4519
4520     if old_role == self._ROLE_OFFLINE and new_role != old_role:
4521       # Trying to transition out of offline status
4522       result = self.rpc.call_version([node.name])[node.name]
4523       if result.fail_msg:
4524         raise errors.OpPrereqError("Node %s is being de-offlined but fails"
4525                                    " to report its version: %s" %
4526                                    (node.name, result.fail_msg),
4527                                    errors.ECODE_STATE)
4528       else:
4529         self.LogWarning("Transitioning node from offline to online state"
4530                         " without using re-add. Please make sure the node"
4531                         " is healthy!")
4532
4533     if self.op.secondary_ip:
4534       # Ok even without locking, because this can't be changed by any LU
4535       master = self.cfg.GetNodeInfo(self.cfg.GetMasterNode())
4536       master_singlehomed = master.secondary_ip == master.primary_ip
4537       if master_singlehomed and self.op.secondary_ip:
4538         raise errors.OpPrereqError("Cannot change the secondary ip on a single"
4539                                    " homed cluster", errors.ECODE_INVAL)
4540
4541       if node.offline:
4542         if self.affected_instances:
4543           raise errors.OpPrereqError("Cannot change secondary ip: offline"
4544                                      " node has instances (%s) configured"
4545                                      " to use it" % self.affected_instances)
4546       else:
4547         # On online nodes, check that no instances are running, and that
4548         # the node has the new ip and we can reach it.
4549         for instance in self.affected_instances:
4550           _CheckInstanceDown(self, instance, "cannot change secondary ip")
4551
4552         _CheckNodeHasSecondaryIP(self, node.name, self.op.secondary_ip, True)
4553         if master.name != node.name:
4554           # check reachability from master secondary ip to new secondary ip
4555           if not netutils.TcpPing(self.op.secondary_ip,
4556                                   constants.DEFAULT_NODED_PORT,
4557                                   source=master.secondary_ip):
4558             raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
4559                                        " based ping to node daemon port",
4560                                        errors.ECODE_ENVIRON)
4561
4562     if self.op.ndparams:
4563       new_ndparams = _GetUpdatedParams(self.node.ndparams, self.op.ndparams)
4564       utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES)
4565       self.new_ndparams = new_ndparams
4566
4567   def Exec(self, feedback_fn):
4568     """Modifies a node.
4569
4570     """
4571     node = self.node
4572     old_role = self.old_role
4573     new_role = self.new_role
4574
4575     result = []
4576
4577     if self.op.ndparams:
4578       node.ndparams = self.new_ndparams
4579
4580     if self.op.powered is not None:
4581       node.powered = self.op.powered
4582
4583     for attr in ["master_capable", "vm_capable"]:
4584       val = getattr(self.op, attr)
4585       if val is not None:
4586         setattr(node, attr, val)
4587         result.append((attr, str(val)))
4588
4589     if new_role != old_role:
4590       # Tell the node to demote itself, if no longer MC and not offline
4591       if old_role == self._ROLE_CANDIDATE and new_role != self._ROLE_OFFLINE:
4592         msg = self.rpc.call_node_demote_from_mc(node.name).fail_msg
4593         if msg:
4594           self.LogWarning("Node failed to demote itself: %s", msg)
4595
4596       new_flags = self._R2F[new_role]
4597       for of, nf, desc in zip(self.old_flags, new_flags, self._FLAGS):
4598         if of != nf:
4599           result.append((desc, str(nf)))
4600       (node.master_candidate, node.drained, node.offline) = new_flags
4601
4602       # we locked all nodes, we adjust the CP before updating this node
4603       if self.lock_all:
4604         _AdjustCandidatePool(self, [node.name])
4605
4606     if self.op.secondary_ip:
4607       node.secondary_ip = self.op.secondary_ip
4608       result.append(("secondary_ip", self.op.secondary_ip))
4609
4610     # this will trigger configuration file update, if needed
4611     self.cfg.Update(node, feedback_fn)
4612
4613     # this will trigger job queue propagation or cleanup if the mc
4614     # flag changed
4615     if [old_role, new_role].count(self._ROLE_CANDIDATE) == 1:
4616       self.context.ReaddNode(node)
4617
4618     return result
4619
4620
4621 class LUNodePowercycle(NoHooksLU):
4622   """Powercycles a node.
4623
4624   """
4625   REQ_BGL = False
4626
4627   def CheckArguments(self):
4628     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
4629     if self.op.node_name == self.cfg.GetMasterNode() and not self.op.force:
4630       raise errors.OpPrereqError("The node is the master and the force"
4631                                  " parameter was not set",
4632                                  errors.ECODE_INVAL)
4633
4634   def ExpandNames(self):
4635     """Locking for PowercycleNode.
4636
4637     This is a last-resort option and shouldn't block on other
4638     jobs. Therefore, we grab no locks.
4639
4640     """
4641     self.needed_locks = {}
4642
4643   def Exec(self, feedback_fn):
4644     """Reboots a node.
4645
4646     """
4647     result = self.rpc.call_node_powercycle(self.op.node_name,
4648                                            self.cfg.GetHypervisorType())
4649     result.Raise("Failed to schedule the reboot")
4650     return result.payload
4651
4652
4653 class LUClusterQuery(NoHooksLU):
4654   """Query cluster configuration.
4655
4656   """
4657   REQ_BGL = False
4658
4659   def ExpandNames(self):
4660     self.needed_locks = {}
4661
4662   def Exec(self, feedback_fn):
4663     """Return cluster config.
4664
4665     """
4666     cluster = self.cfg.GetClusterInfo()
4667     os_hvp = {}
4668
4669     # Filter just for enabled hypervisors
4670     for os_name, hv_dict in cluster.os_hvp.items():
4671       os_hvp[os_name] = {}
4672       for hv_name, hv_params in hv_dict.items():
4673         if hv_name in cluster.enabled_hypervisors:
4674           os_hvp[os_name][hv_name] = hv_params
4675
4676     # Convert ip_family to ip_version
4677     primary_ip_version = constants.IP4_VERSION
4678     if cluster.primary_ip_family == netutils.IP6Address.family:
4679       primary_ip_version = constants.IP6_VERSION
4680
4681     result = {
4682       "software_version": constants.RELEASE_VERSION,
4683       "protocol_version": constants.PROTOCOL_VERSION,
4684       "config_version": constants.CONFIG_VERSION,
4685       "os_api_version": max(constants.OS_API_VERSIONS),
4686       "export_version": constants.EXPORT_VERSION,
4687       "architecture": (platform.architecture()[0], platform.machine()),
4688       "name": cluster.cluster_name,
4689       "master": cluster.master_node,
4690       "default_hypervisor": cluster.enabled_hypervisors[0],
4691       "enabled_hypervisors": cluster.enabled_hypervisors,
4692       "hvparams": dict([(hypervisor_name, cluster.hvparams[hypervisor_name])
4693                         for hypervisor_name in cluster.enabled_hypervisors]),
4694       "os_hvp": os_hvp,
4695       "beparams": cluster.beparams,
4696       "osparams": cluster.osparams,
4697       "nicparams": cluster.nicparams,
4698       "ndparams": cluster.ndparams,
4699       "candidate_pool_size": cluster.candidate_pool_size,
4700       "master_netdev": cluster.master_netdev,
4701       "volume_group_name": cluster.volume_group_name,
4702       "drbd_usermode_helper": cluster.drbd_usermode_helper,
4703       "file_storage_dir": cluster.file_storage_dir,
4704       "shared_file_storage_dir": cluster.shared_file_storage_dir,
4705       "maintain_node_health": cluster.maintain_node_health,
4706       "ctime": cluster.ctime,
4707       "mtime": cluster.mtime,
4708       "uuid": cluster.uuid,
4709       "tags": list(cluster.GetTags()),
4710       "uid_pool": cluster.uid_pool,
4711       "default_iallocator": cluster.default_iallocator,
4712       "reserved_lvs": cluster.reserved_lvs,
4713       "primary_ip_version": primary_ip_version,
4714       "prealloc_wipe_disks": cluster.prealloc_wipe_disks,
4715       "hidden_os": cluster.hidden_os,
4716       "blacklisted_os": cluster.blacklisted_os,
4717       }
4718
4719     return result
4720
4721
4722 class LUClusterConfigQuery(NoHooksLU):
4723   """Return configuration values.
4724
4725   """
4726   REQ_BGL = False
4727   _FIELDS_DYNAMIC = utils.FieldSet()
4728   _FIELDS_STATIC = utils.FieldSet("cluster_name", "master_node", "drain_flag",
4729                                   "watcher_pause", "volume_group_name")
4730
4731   def CheckArguments(self):
4732     _CheckOutputFields(static=self._FIELDS_STATIC,
4733                        dynamic=self._FIELDS_DYNAMIC,
4734                        selected=self.op.output_fields)
4735
4736   def ExpandNames(self):
4737     self.needed_locks = {}
4738
4739   def Exec(self, feedback_fn):
4740     """Dump a representation of the cluster config to the standard output.
4741
4742     """
4743     values = []
4744     for field in self.op.output_fields:
4745       if field == "cluster_name":
4746         entry = self.cfg.GetClusterName()
4747       elif field == "master_node":
4748         entry = self.cfg.GetMasterNode()
4749       elif field == "drain_flag":
4750         entry = os.path.exists(constants.JOB_QUEUE_DRAIN_FILE)
4751       elif field == "watcher_pause":
4752         entry = utils.ReadWatcherPauseFile(constants.WATCHER_PAUSEFILE)
4753       elif field == "volume_group_name":
4754         entry = self.cfg.GetVGName()
4755       else:
4756         raise errors.ParameterError(field)
4757       values.append(entry)
4758     return values
4759
4760
4761 class LUInstanceActivateDisks(NoHooksLU):
4762   """Bring up an instance's disks.
4763
4764   """
4765   REQ_BGL = False
4766
4767   def ExpandNames(self):
4768     self._ExpandAndLockInstance()
4769     self.needed_locks[locking.LEVEL_NODE] = []
4770     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
4771
4772   def DeclareLocks(self, level):
4773     if level == locking.LEVEL_NODE:
4774       self._LockInstancesNodes()
4775
4776   def CheckPrereq(self):
4777     """Check prerequisites.
4778
4779     This checks that the instance is in the cluster.
4780
4781     """
4782     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4783     assert self.instance is not None, \
4784       "Cannot retrieve locked instance %s" % self.op.instance_name
4785     _CheckNodeOnline(self, self.instance.primary_node)
4786
4787   def Exec(self, feedback_fn):
4788     """Activate the disks.
4789
4790     """
4791     disks_ok, disks_info = \
4792               _AssembleInstanceDisks(self, self.instance,
4793                                      ignore_size=self.op.ignore_size)
4794     if not disks_ok:
4795       raise errors.OpExecError("Cannot activate block devices")
4796
4797     return disks_info
4798
4799
4800 def _AssembleInstanceDisks(lu, instance, disks=None, ignore_secondaries=False,
4801                            ignore_size=False):
4802   """Prepare the block devices for an instance.
4803
4804   This sets up the block devices on all nodes.
4805
4806   @type lu: L{LogicalUnit}
4807   @param lu: the logical unit on whose behalf we execute
4808   @type instance: L{objects.Instance}
4809   @param instance: the instance for whose disks we assemble
4810   @type disks: list of L{objects.Disk} or None
4811   @param disks: which disks to assemble (or all, if None)
4812   @type ignore_secondaries: boolean
4813   @param ignore_secondaries: if true, errors on secondary nodes
4814       won't result in an error return from the function
4815   @type ignore_size: boolean
4816   @param ignore_size: if true, the current known size of the disk
4817       will not be used during the disk activation, useful for cases
4818       when the size is wrong
4819   @return: False if the operation failed, otherwise a list of
4820       (host, instance_visible_name, node_visible_name)
4821       with the mapping from node devices to instance devices
4822
4823   """
4824   device_info = []
4825   disks_ok = True
4826   iname = instance.name
4827   disks = _ExpandCheckDisks(instance, disks)
4828
4829   # With the two passes mechanism we try to reduce the window of
4830   # opportunity for the race condition of switching DRBD to primary
4831   # before handshaking occured, but we do not eliminate it
4832
4833   # The proper fix would be to wait (with some limits) until the
4834   # connection has been made and drbd transitions from WFConnection
4835   # into any other network-connected state (Connected, SyncTarget,
4836   # SyncSource, etc.)
4837
4838   # 1st pass, assemble on all nodes in secondary mode
4839   for idx, inst_disk in enumerate(disks):
4840     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
4841       if ignore_size:
4842         node_disk = node_disk.Copy()
4843         node_disk.UnsetSize()
4844       lu.cfg.SetDiskID(node_disk, node)
4845       result = lu.rpc.call_blockdev_assemble(node, node_disk, iname, False, idx)
4846       msg = result.fail_msg
4847       if msg:
4848         lu.proc.LogWarning("Could not prepare block device %s on node %s"
4849                            " (is_primary=False, pass=1): %s",
4850                            inst_disk.iv_name, node, msg)
4851         if not ignore_secondaries:
4852           disks_ok = False
4853
4854   # FIXME: race condition on drbd migration to primary
4855
4856   # 2nd pass, do only the primary node
4857   for idx, inst_disk in enumerate(disks):
4858     dev_path = None
4859
4860     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
4861       if node != instance.primary_node:
4862         continue
4863       if ignore_size:
4864         node_disk = node_disk.Copy()
4865         node_disk.UnsetSize()
4866       lu.cfg.SetDiskID(node_disk, node)
4867       result = lu.rpc.call_blockdev_assemble(node, node_disk, iname, True, idx)
4868       msg = result.fail_msg
4869       if msg:
4870         lu.proc.LogWarning("Could not prepare block device %s on node %s"
4871                            " (is_primary=True, pass=2): %s",
4872                            inst_disk.iv_name, node, msg)
4873         disks_ok = False
4874       else:
4875         dev_path = result.payload
4876
4877     device_info.append((instance.primary_node, inst_disk.iv_name, dev_path))
4878
4879   # leave the disks configured for the primary node
4880   # this is a workaround that would be fixed better by
4881   # improving the logical/physical id handling
4882   for disk in disks:
4883     lu.cfg.SetDiskID(disk, instance.primary_node)
4884
4885   return disks_ok, device_info
4886
4887
4888 def _StartInstanceDisks(lu, instance, force):
4889   """Start the disks of an instance.
4890
4891   """
4892   disks_ok, _ = _AssembleInstanceDisks(lu, instance,
4893                                            ignore_secondaries=force)
4894   if not disks_ok:
4895     _ShutdownInstanceDisks(lu, instance)
4896     if force is not None and not force:
4897       lu.proc.LogWarning("", hint="If the message above refers to a"
4898                          " secondary node,"
4899                          " you can retry the operation using '--force'.")
4900     raise errors.OpExecError("Disk consistency error")
4901
4902
4903 class LUInstanceDeactivateDisks(NoHooksLU):
4904   """Shutdown an instance's disks.
4905
4906   """
4907   REQ_BGL = False
4908
4909   def ExpandNames(self):
4910     self._ExpandAndLockInstance()
4911     self.needed_locks[locking.LEVEL_NODE] = []
4912     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
4913
4914   def DeclareLocks(self, level):
4915     if level == locking.LEVEL_NODE:
4916       self._LockInstancesNodes()
4917
4918   def CheckPrereq(self):
4919     """Check prerequisites.
4920
4921     This checks that the instance is in the cluster.
4922
4923     """
4924     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
4925     assert self.instance is not None, \
4926       "Cannot retrieve locked instance %s" % self.op.instance_name
4927
4928   def Exec(self, feedback_fn):
4929     """Deactivate the disks
4930
4931     """
4932     instance = self.instance
4933     if self.op.force:
4934       _ShutdownInstanceDisks(self, instance)
4935     else:
4936       _SafeShutdownInstanceDisks(self, instance)
4937
4938
4939 def _SafeShutdownInstanceDisks(lu, instance, disks=None):
4940   """Shutdown block devices of an instance.
4941
4942   This function checks if an instance is running, before calling
4943   _ShutdownInstanceDisks.
4944
4945   """
4946   _CheckInstanceDown(lu, instance, "cannot shutdown disks")
4947   _ShutdownInstanceDisks(lu, instance, disks=disks)
4948
4949
4950 def _ExpandCheckDisks(instance, disks):
4951   """Return the instance disks selected by the disks list
4952
4953   @type disks: list of L{objects.Disk} or None
4954   @param disks: selected disks
4955   @rtype: list of L{objects.Disk}
4956   @return: selected instance disks to act on
4957
4958   """
4959   if disks is None:
4960     return instance.disks
4961   else:
4962     if not set(disks).issubset(instance.disks):
4963       raise errors.ProgrammerError("Can only act on disks belonging to the"
4964                                    " target instance")
4965     return disks
4966
4967
4968 def _ShutdownInstanceDisks(lu, instance, disks=None, ignore_primary=False):
4969   """Shutdown block devices of an instance.
4970
4971   This does the shutdown on all nodes of the instance.
4972
4973   If the ignore_primary is false, errors on the primary node are
4974   ignored.
4975
4976   """
4977   all_result = True
4978   disks = _ExpandCheckDisks(instance, disks)
4979
4980   for disk in disks:
4981     for node, top_disk in disk.ComputeNodeTree(instance.primary_node):
4982       lu.cfg.SetDiskID(top_disk, node)
4983       result = lu.rpc.call_blockdev_shutdown(node, top_disk)
4984       msg = result.fail_msg
4985       if msg:
4986         lu.LogWarning("Could not shutdown block device %s on node %s: %s",
4987                       disk.iv_name, node, msg)
4988         if ((node == instance.primary_node and not ignore_primary) or
4989             (node != instance.primary_node and not result.offline)):
4990           all_result = False
4991   return all_result
4992
4993
4994 def _CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name):
4995   """Checks if a node has enough free memory.
4996
4997   This function check if a given node has the needed amount of free
4998   memory. In case the node has less memory or we cannot get the
4999   information from the node, this function raise an OpPrereqError
5000   exception.
5001
5002   @type lu: C{LogicalUnit}
5003   @param lu: a logical unit from which we get configuration data
5004   @type node: C{str}
5005   @param node: the node to check
5006   @type reason: C{str}
5007   @param reason: string to use in the error message
5008   @type requested: C{int}
5009   @param requested: the amount of memory in MiB to check for
5010   @type hypervisor_name: C{str}
5011   @param hypervisor_name: the hypervisor to ask for memory stats
5012   @raise errors.OpPrereqError: if the node doesn't have enough memory, or
5013       we cannot check the node
5014
5015   """
5016   nodeinfo = lu.rpc.call_node_info([node], None, hypervisor_name)
5017   nodeinfo[node].Raise("Can't get data from node %s" % node,
5018                        prereq=True, ecode=errors.ECODE_ENVIRON)
5019   free_mem = nodeinfo[node].payload.get('memory_free', None)
5020   if not isinstance(free_mem, int):
5021     raise errors.OpPrereqError("Can't compute free memory on node %s, result"
5022                                " was '%s'" % (node, free_mem),
5023                                errors.ECODE_ENVIRON)
5024   if requested > free_mem:
5025     raise errors.OpPrereqError("Not enough memory on node %s for %s:"
5026                                " needed %s MiB, available %s MiB" %
5027                                (node, reason, requested, free_mem),
5028                                errors.ECODE_NORES)
5029
5030
5031 def _CheckNodesFreeDiskPerVG(lu, nodenames, req_sizes):
5032   """Checks if nodes have enough free disk space in the all VGs.
5033
5034   This function check if all given nodes have the needed amount of
5035   free disk. In case any node has less disk or we cannot get the
5036   information from the node, this function raise an OpPrereqError
5037   exception.
5038
5039   @type lu: C{LogicalUnit}
5040   @param lu: a logical unit from which we get configuration data
5041   @type nodenames: C{list}
5042   @param nodenames: the list of node names to check
5043   @type req_sizes: C{dict}
5044   @param req_sizes: the hash of vg and corresponding amount of disk in
5045       MiB to check for
5046   @raise errors.OpPrereqError: if the node doesn't have enough disk,
5047       or we cannot check the node
5048
5049   """
5050   for vg, req_size in req_sizes.items():
5051     _CheckNodesFreeDiskOnVG(lu, nodenames, vg, req_size)
5052
5053
5054 def _CheckNodesFreeDiskOnVG(lu, nodenames, vg, requested):
5055   """Checks if nodes have enough free disk space in the specified VG.
5056
5057   This function check if all given nodes have the needed amount of
5058   free disk. In case any node has less disk or we cannot get the
5059   information from the node, this function raise an OpPrereqError
5060   exception.
5061
5062   @type lu: C{LogicalUnit}
5063   @param lu: a logical unit from which we get configuration data
5064   @type nodenames: C{list}
5065   @param nodenames: the list of node names to check
5066   @type vg: C{str}
5067   @param vg: the volume group to check
5068   @type requested: C{int}
5069   @param requested: the amount of disk in MiB to check for
5070   @raise errors.OpPrereqError: if the node doesn't have enough disk,
5071       or we cannot check the node
5072
5073   """
5074   nodeinfo = lu.rpc.call_node_info(nodenames, vg, None)
5075   for node in nodenames:
5076     info = nodeinfo[node]
5077     info.Raise("Cannot get current information from node %s" % node,
5078                prereq=True, ecode=errors.ECODE_ENVIRON)
5079     vg_free = info.payload.get("vg_free", None)
5080     if not isinstance(vg_free, int):
5081       raise errors.OpPrereqError("Can't compute free disk space on node"
5082                                  " %s for vg %s, result was '%s'" %
5083                                  (node, vg, vg_free), errors.ECODE_ENVIRON)
5084     if requested > vg_free:
5085       raise errors.OpPrereqError("Not enough disk space on target node %s"
5086                                  " vg %s: required %d MiB, available %d MiB" %
5087                                  (node, vg, requested, vg_free),
5088                                  errors.ECODE_NORES)
5089
5090
5091 class LUInstanceStartup(LogicalUnit):
5092   """Starts an instance.
5093
5094   """
5095   HPATH = "instance-start"
5096   HTYPE = constants.HTYPE_INSTANCE
5097   REQ_BGL = False
5098
5099   def CheckArguments(self):
5100     # extra beparams
5101     if self.op.beparams:
5102       # fill the beparams dict
5103       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
5104
5105   def ExpandNames(self):
5106     self._ExpandAndLockInstance()
5107
5108   def BuildHooksEnv(self):
5109     """Build hooks env.
5110
5111     This runs on master, primary and secondary nodes of the instance.
5112
5113     """
5114     env = {
5115       "FORCE": self.op.force,
5116       }
5117     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
5118     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
5119     return env, nl, nl
5120
5121   def CheckPrereq(self):
5122     """Check prerequisites.
5123
5124     This checks that the instance is in the cluster.
5125
5126     """
5127     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
5128     assert self.instance is not None, \
5129       "Cannot retrieve locked instance %s" % self.op.instance_name
5130
5131     # extra hvparams
5132     if self.op.hvparams:
5133       # check hypervisor parameter syntax (locally)
5134       cluster = self.cfg.GetClusterInfo()
5135       utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
5136       filled_hvp = cluster.FillHV(instance)
5137       filled_hvp.update(self.op.hvparams)
5138       hv_type = hypervisor.GetHypervisor(instance.hypervisor)
5139       hv_type.CheckParameterSyntax(filled_hvp)
5140       _CheckHVParams(self, instance.all_nodes, instance.hypervisor, filled_hvp)
5141
5142     self.primary_offline = self.cfg.GetNodeInfo(instance.primary_node).offline
5143
5144     if self.primary_offline and self.op.ignore_offline_nodes:
5145       self.proc.LogWarning("Ignoring offline primary node")
5146
5147       if self.op.hvparams or self.op.beparams:
5148         self.proc.LogWarning("Overridden parameters are ignored")
5149     else:
5150       _CheckNodeOnline(self, instance.primary_node)
5151
5152       bep = self.cfg.GetClusterInfo().FillBE(instance)
5153
5154       # check bridges existence
5155       _CheckInstanceBridgesExist(self, instance)
5156
5157       remote_info = self.rpc.call_instance_info(instance.primary_node,
5158                                                 instance.name,
5159                                                 instance.hypervisor)
5160       remote_info.Raise("Error checking node %s" % instance.primary_node,
5161                         prereq=True, ecode=errors.ECODE_ENVIRON)
5162       if not remote_info.payload: # not running already
5163         _CheckNodeFreeMemory(self, instance.primary_node,
5164                              "starting instance %s" % instance.name,
5165                              bep[constants.BE_MEMORY], instance.hypervisor)
5166
5167   def Exec(self, feedback_fn):
5168     """Start the instance.
5169
5170     """
5171     instance = self.instance
5172     force = self.op.force
5173
5174     self.cfg.MarkInstanceUp(instance.name)
5175
5176     if self.primary_offline:
5177       assert self.op.ignore_offline_nodes
5178       self.proc.LogInfo("Primary node offline, marked instance as started")
5179     else:
5180       node_current = instance.primary_node
5181
5182       _StartInstanceDisks(self, instance, force)
5183
5184       result = self.rpc.call_instance_start(node_current, instance,
5185                                             self.op.hvparams, self.op.beparams)
5186       msg = result.fail_msg
5187       if msg:
5188         _ShutdownInstanceDisks(self, instance)
5189         raise errors.OpExecError("Could not start instance: %s" % msg)
5190
5191
5192 class LUInstanceReboot(LogicalUnit):
5193   """Reboot an instance.
5194
5195   """
5196   HPATH = "instance-reboot"
5197   HTYPE = constants.HTYPE_INSTANCE
5198   REQ_BGL = False
5199
5200   def ExpandNames(self):
5201     self._ExpandAndLockInstance()
5202
5203   def BuildHooksEnv(self):
5204     """Build hooks env.
5205
5206     This runs on master, primary and secondary nodes of the instance.
5207
5208     """
5209     env = {
5210       "IGNORE_SECONDARIES": self.op.ignore_secondaries,
5211       "REBOOT_TYPE": self.op.reboot_type,
5212       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
5213       }
5214     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
5215     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
5216     return env, nl, nl
5217
5218   def CheckPrereq(self):
5219     """Check prerequisites.
5220
5221     This checks that the instance is in the cluster.
5222
5223     """
5224     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
5225     assert self.instance is not None, \
5226       "Cannot retrieve locked instance %s" % self.op.instance_name
5227
5228     _CheckNodeOnline(self, instance.primary_node)
5229
5230     # check bridges existence
5231     _CheckInstanceBridgesExist(self, instance)
5232
5233   def Exec(self, feedback_fn):
5234     """Reboot the instance.
5235
5236     """
5237     instance = self.instance
5238     ignore_secondaries = self.op.ignore_secondaries
5239     reboot_type = self.op.reboot_type
5240
5241     remote_info = self.rpc.call_instance_info(instance.primary_node,
5242                                               instance.name,
5243                                               instance.hypervisor)
5244     remote_info.Raise("Error checking node %s" % instance.primary_node)
5245     instance_running = bool(remote_info.payload)
5246
5247     node_current = instance.primary_node
5248
5249     if instance_running and reboot_type in [constants.INSTANCE_REBOOT_SOFT,
5250                                             constants.INSTANCE_REBOOT_HARD]:
5251       for disk in instance.disks:
5252         self.cfg.SetDiskID(disk, node_current)
5253       result = self.rpc.call_instance_reboot(node_current, instance,
5254                                              reboot_type,
5255                                              self.op.shutdown_timeout)
5256       result.Raise("Could not reboot instance")
5257     else:
5258       if instance_running:
5259         result = self.rpc.call_instance_shutdown(node_current, instance,
5260                                                  self.op.shutdown_timeout)
5261         result.Raise("Could not shutdown instance for full reboot")
5262         _ShutdownInstanceDisks(self, instance)
5263       else:
5264         self.LogInfo("Instance %s was already stopped, starting now",
5265                      instance.name)
5266       _StartInstanceDisks(self, instance, ignore_secondaries)
5267       result = self.rpc.call_instance_start(node_current, instance, None, None)
5268       msg = result.fail_msg
5269       if msg:
5270         _ShutdownInstanceDisks(self, instance)
5271         raise errors.OpExecError("Could not start instance for"
5272                                  " full reboot: %s" % msg)
5273
5274     self.cfg.MarkInstanceUp(instance.name)
5275
5276
5277 class LUInstanceShutdown(LogicalUnit):
5278   """Shutdown an instance.
5279
5280   """
5281   HPATH = "instance-stop"
5282   HTYPE = constants.HTYPE_INSTANCE
5283   REQ_BGL = False
5284
5285   def ExpandNames(self):
5286     self._ExpandAndLockInstance()
5287
5288   def BuildHooksEnv(self):
5289     """Build hooks env.
5290
5291     This runs on master, primary and secondary nodes of the instance.
5292
5293     """
5294     env = _BuildInstanceHookEnvByObject(self, self.instance)
5295     env["TIMEOUT"] = self.op.timeout
5296     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
5297     return env, nl, nl
5298
5299   def CheckPrereq(self):
5300     """Check prerequisites.
5301
5302     This checks that the instance is in the cluster.
5303
5304     """
5305     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
5306     assert self.instance is not None, \
5307       "Cannot retrieve locked instance %s" % self.op.instance_name
5308
5309     self.primary_offline = \
5310       self.cfg.GetNodeInfo(self.instance.primary_node).offline
5311
5312     if self.primary_offline and self.op.ignore_offline_nodes:
5313       self.proc.LogWarning("Ignoring offline primary node")
5314     else:
5315       _CheckNodeOnline(self, self.instance.primary_node)
5316
5317   def Exec(self, feedback_fn):
5318     """Shutdown the instance.
5319
5320     """
5321     instance = self.instance
5322     node_current = instance.primary_node
5323     timeout = self.op.timeout
5324
5325     self.cfg.MarkInstanceDown(instance.name)
5326
5327     if self.primary_offline:
5328       assert self.op.ignore_offline_nodes
5329       self.proc.LogInfo("Primary node offline, marked instance as stopped")
5330     else:
5331       result = self.rpc.call_instance_shutdown(node_current, instance, timeout)
5332       msg = result.fail_msg
5333       if msg:
5334         self.proc.LogWarning("Could not shutdown instance: %s" % msg)
5335
5336       _ShutdownInstanceDisks(self, instance)
5337
5338
5339 class LUInstanceReinstall(LogicalUnit):
5340   """Reinstall an instance.
5341
5342   """
5343   HPATH = "instance-reinstall"
5344   HTYPE = constants.HTYPE_INSTANCE
5345   REQ_BGL = False
5346
5347   def ExpandNames(self):
5348     self._ExpandAndLockInstance()
5349
5350   def BuildHooksEnv(self):
5351     """Build hooks env.
5352
5353     This runs on master, primary and secondary nodes of the instance.
5354
5355     """
5356     env = _BuildInstanceHookEnvByObject(self, self.instance)
5357     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
5358     return env, nl, nl
5359
5360   def CheckPrereq(self):
5361     """Check prerequisites.
5362
5363     This checks that the instance is in the cluster and is not running.
5364
5365     """
5366     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
5367     assert instance is not None, \
5368       "Cannot retrieve locked instance %s" % self.op.instance_name
5369     _CheckNodeOnline(self, instance.primary_node, "Instance primary node"
5370                      " offline, cannot reinstall")
5371     for node in instance.secondary_nodes:
5372       _CheckNodeOnline(self, node, "Instance secondary node offline,"
5373                        " cannot reinstall")
5374
5375     if instance.disk_template == constants.DT_DISKLESS:
5376       raise errors.OpPrereqError("Instance '%s' has no disks" %
5377                                  self.op.instance_name,
5378                                  errors.ECODE_INVAL)
5379     _CheckInstanceDown(self, instance, "cannot reinstall")
5380
5381     if self.op.os_type is not None:
5382       # OS verification
5383       pnode = _ExpandNodeName(self.cfg, instance.primary_node)
5384       _CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant)
5385       instance_os = self.op.os_type
5386     else:
5387       instance_os = instance.os
5388
5389     nodelist = list(instance.all_nodes)
5390
5391     if self.op.osparams:
5392       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
5393       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
5394       self.os_inst = i_osdict # the new dict (without defaults)
5395     else:
5396       self.os_inst = None
5397
5398     self.instance = instance
5399
5400   def Exec(self, feedback_fn):
5401     """Reinstall the instance.
5402
5403     """
5404     inst = self.instance
5405
5406     if self.op.os_type is not None:
5407       feedback_fn("Changing OS to '%s'..." % self.op.os_type)
5408       inst.os = self.op.os_type
5409       # Write to configuration
5410       self.cfg.Update(inst, feedback_fn)
5411
5412     _StartInstanceDisks(self, inst, None)
5413     try:
5414       feedback_fn("Running the instance OS create scripts...")
5415       # FIXME: pass debug option from opcode to backend
5416       result = self.rpc.call_instance_os_add(inst.primary_node, inst, True,
5417                                              self.op.debug_level,
5418                                              osparams=self.os_inst)
5419       result.Raise("Could not install OS for instance %s on node %s" %
5420                    (inst.name, inst.primary_node))
5421     finally:
5422       _ShutdownInstanceDisks(self, inst)
5423
5424
5425 class LUInstanceRecreateDisks(LogicalUnit):
5426   """Recreate an instance's missing disks.
5427
5428   """
5429   HPATH = "instance-recreate-disks"
5430   HTYPE = constants.HTYPE_INSTANCE
5431   REQ_BGL = False
5432
5433   def ExpandNames(self):
5434     self._ExpandAndLockInstance()
5435
5436   def BuildHooksEnv(self):
5437     """Build hooks env.
5438
5439     This runs on master, primary and secondary nodes of the instance.
5440
5441     """
5442     env = _BuildInstanceHookEnvByObject(self, self.instance)
5443     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
5444     return env, nl, nl
5445
5446   def CheckPrereq(self):
5447     """Check prerequisites.
5448
5449     This checks that the instance is in the cluster and is not running.
5450
5451     """
5452     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
5453     assert instance is not None, \
5454       "Cannot retrieve locked instance %s" % self.op.instance_name
5455     _CheckNodeOnline(self, instance.primary_node)
5456
5457     if instance.disk_template == constants.DT_DISKLESS:
5458       raise errors.OpPrereqError("Instance '%s' has no disks" %
5459                                  self.op.instance_name, errors.ECODE_INVAL)
5460     _CheckInstanceDown(self, instance, "cannot recreate disks")
5461
5462     if not self.op.disks:
5463       self.op.disks = range(len(instance.disks))
5464     else:
5465       for idx in self.op.disks:
5466         if idx >= len(instance.disks):
5467           raise errors.OpPrereqError("Invalid disk index passed '%s'" % idx,
5468                                      errors.ECODE_INVAL)
5469
5470     self.instance = instance
5471
5472   def Exec(self, feedback_fn):
5473     """Recreate the disks.
5474
5475     """
5476     to_skip = []
5477     for idx, _ in enumerate(self.instance.disks):
5478       if idx not in self.op.disks: # disk idx has not been passed in
5479         to_skip.append(idx)
5480         continue
5481
5482     _CreateDisks(self, self.instance, to_skip=to_skip)
5483
5484
5485 class LUInstanceRename(LogicalUnit):
5486   """Rename an instance.
5487
5488   """
5489   HPATH = "instance-rename"
5490   HTYPE = constants.HTYPE_INSTANCE
5491
5492   def CheckArguments(self):
5493     """Check arguments.
5494
5495     """
5496     if self.op.ip_check and not self.op.name_check:
5497       # TODO: make the ip check more flexible and not depend on the name check
5498       raise errors.OpPrereqError("Cannot do ip check without a name check",
5499                                  errors.ECODE_INVAL)
5500
5501   def BuildHooksEnv(self):
5502     """Build hooks env.
5503
5504     This runs on master, primary and secondary nodes of the instance.
5505
5506     """
5507     env = _BuildInstanceHookEnvByObject(self, self.instance)
5508     env["INSTANCE_NEW_NAME"] = self.op.new_name
5509     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
5510     return env, nl, nl
5511
5512   def CheckPrereq(self):
5513     """Check prerequisites.
5514
5515     This checks that the instance is in the cluster and is not running.
5516
5517     """
5518     self.op.instance_name = _ExpandInstanceName(self.cfg,
5519                                                 self.op.instance_name)
5520     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
5521     assert instance is not None
5522     _CheckNodeOnline(self, instance.primary_node)
5523     _CheckInstanceDown(self, instance, "cannot rename")
5524     self.instance = instance
5525
5526     new_name = self.op.new_name
5527     if self.op.name_check:
5528       hostname = netutils.GetHostname(name=new_name)
5529       self.LogInfo("Resolved given name '%s' to '%s'", new_name,
5530                    hostname.name)
5531       if not utils.MatchNameComponent(self.op.new_name, [hostname.name]):
5532         raise errors.OpPrereqError(("Resolved hostname '%s' does not look the"
5533                                     " same as given hostname '%s'") %
5534                                     (hostname.name, self.op.new_name),
5535                                     errors.ECODE_INVAL)
5536       new_name = self.op.new_name = hostname.name
5537       if (self.op.ip_check and
5538           netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
5539         raise errors.OpPrereqError("IP %s of instance %s already in use" %
5540                                    (hostname.ip, new_name),
5541                                    errors.ECODE_NOTUNIQUE)
5542
5543     instance_list = self.cfg.GetInstanceList()
5544     if new_name in instance_list and new_name != instance.name:
5545       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
5546                                  new_name, errors.ECODE_EXISTS)
5547
5548   def Exec(self, feedback_fn):
5549     """Rename the instance.
5550
5551     """
5552     inst = self.instance
5553     old_name = inst.name
5554
5555     rename_file_storage = False
5556     if (inst.disk_template in (constants.DT_FILE, constants.DT_SHARED_FILE) and
5557         self.op.new_name != inst.name):
5558       old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
5559       rename_file_storage = True
5560
5561     self.cfg.RenameInstance(inst.name, self.op.new_name)
5562     # Change the instance lock. This is definitely safe while we hold the BGL
5563     self.context.glm.remove(locking.LEVEL_INSTANCE, old_name)
5564     self.context.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
5565
5566     # re-read the instance from the configuration after rename
5567     inst = self.cfg.GetInstanceInfo(self.op.new_name)
5568
5569     if rename_file_storage:
5570       new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
5571       result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
5572                                                      old_file_storage_dir,
5573                                                      new_file_storage_dir)
5574       result.Raise("Could not rename on node %s directory '%s' to '%s'"
5575                    " (but the instance has been renamed in Ganeti)" %
5576                    (inst.primary_node, old_file_storage_dir,
5577                     new_file_storage_dir))
5578
5579     _StartInstanceDisks(self, inst, None)
5580     try:
5581       result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
5582                                                  old_name, self.op.debug_level)
5583       msg = result.fail_msg
5584       if msg:
5585         msg = ("Could not run OS rename script for instance %s on node %s"
5586                " (but the instance has been renamed in Ganeti): %s" %
5587                (inst.name, inst.primary_node, msg))
5588         self.proc.LogWarning(msg)
5589     finally:
5590       _ShutdownInstanceDisks(self, inst)
5591
5592     return inst.name
5593
5594
5595 class LUInstanceRemove(LogicalUnit):
5596   """Remove an instance.
5597
5598   """
5599   HPATH = "instance-remove"
5600   HTYPE = constants.HTYPE_INSTANCE
5601   REQ_BGL = False
5602
5603   def ExpandNames(self):
5604     self._ExpandAndLockInstance()
5605     self.needed_locks[locking.LEVEL_NODE] = []
5606     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
5607
5608   def DeclareLocks(self, level):
5609     if level == locking.LEVEL_NODE:
5610       self._LockInstancesNodes()
5611
5612   def BuildHooksEnv(self):
5613     """Build hooks env.
5614
5615     This runs on master, primary and secondary nodes of the instance.
5616
5617     """
5618     env = _BuildInstanceHookEnvByObject(self, self.instance)
5619     env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
5620     nl = [self.cfg.GetMasterNode()]
5621     nl_post = list(self.instance.all_nodes) + nl
5622     return env, nl, nl_post
5623
5624   def CheckPrereq(self):
5625     """Check prerequisites.
5626
5627     This checks that the instance is in the cluster.
5628
5629     """
5630     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
5631     assert self.instance is not None, \
5632       "Cannot retrieve locked instance %s" % self.op.instance_name
5633
5634   def Exec(self, feedback_fn):
5635     """Remove the instance.
5636
5637     """
5638     instance = self.instance
5639     logging.info("Shutting down instance %s on node %s",
5640                  instance.name, instance.primary_node)
5641
5642     result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
5643                                              self.op.shutdown_timeout)
5644     msg = result.fail_msg
5645     if msg:
5646       if self.op.ignore_failures:
5647         feedback_fn("Warning: can't shutdown instance: %s" % msg)
5648       else:
5649         raise errors.OpExecError("Could not shutdown instance %s on"
5650                                  " node %s: %s" %
5651                                  (instance.name, instance.primary_node, msg))
5652
5653     _RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
5654
5655
5656 def _RemoveInstance(lu, feedback_fn, instance, ignore_failures):
5657   """Utility function to remove an instance.
5658
5659   """
5660   logging.info("Removing block devices for instance %s", instance.name)
5661
5662   if not _RemoveDisks(lu, instance):
5663     if not ignore_failures:
5664       raise errors.OpExecError("Can't remove instance's disks")
5665     feedback_fn("Warning: can't remove instance's disks")
5666
5667   logging.info("Removing instance %s out of cluster config", instance.name)
5668
5669   lu.cfg.RemoveInstance(instance.name)
5670
5671   assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \
5672     "Instance lock removal conflict"
5673
5674   # Remove lock for the instance
5675   lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
5676
5677
5678 class LUInstanceQuery(NoHooksLU):
5679   """Logical unit for querying instances.
5680
5681   """
5682   # pylint: disable-msg=W0142
5683   REQ_BGL = False
5684
5685   def CheckArguments(self):
5686     self.iq = _InstanceQuery(qlang.MakeSimpleFilter("name", self.op.names),
5687                              self.op.output_fields, self.op.use_locking)
5688
5689   def ExpandNames(self):
5690     self.iq.ExpandNames(self)
5691
5692   def DeclareLocks(self, level):
5693     self.iq.DeclareLocks(self, level)
5694
5695   def Exec(self, feedback_fn):
5696     return self.iq.OldStyleQuery(self)
5697
5698
5699 class LUInstanceFailover(LogicalUnit):
5700   """Failover an instance.
5701
5702   """
5703   HPATH = "instance-failover"
5704   HTYPE = constants.HTYPE_INSTANCE
5705   REQ_BGL = False
5706
5707   def ExpandNames(self):
5708     self._ExpandAndLockInstance()
5709     self.needed_locks[locking.LEVEL_NODE] = []
5710     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
5711
5712   def DeclareLocks(self, level):
5713     if level == locking.LEVEL_NODE:
5714       self._LockInstancesNodes()
5715
5716   def BuildHooksEnv(self):
5717     """Build hooks env.
5718
5719     This runs on master, primary and secondary nodes of the instance.
5720
5721     """
5722     instance = self.instance
5723     source_node = instance.primary_node
5724     target_node = instance.secondary_nodes[0]
5725     env = {
5726       "IGNORE_CONSISTENCY": self.op.ignore_consistency,
5727       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
5728       "OLD_PRIMARY": source_node,
5729       "OLD_SECONDARY": target_node,
5730       "NEW_PRIMARY": target_node,
5731       "NEW_SECONDARY": source_node,
5732       }
5733     env.update(_BuildInstanceHookEnvByObject(self, instance))
5734     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
5735     nl_post = list(nl)
5736     nl_post.append(source_node)
5737     return env, nl, nl_post
5738
5739   def CheckPrereq(self):
5740     """Check prerequisites.
5741
5742     This checks that the instance is in the cluster.
5743
5744     """
5745     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
5746     assert self.instance is not None, \
5747       "Cannot retrieve locked instance %s" % self.op.instance_name
5748
5749     bep = self.cfg.GetClusterInfo().FillBE(instance)
5750     if instance.disk_template not in constants.DTS_NET_MIRROR:
5751       raise errors.OpPrereqError("Instance's disk layout is not"
5752                                  " network mirrored, cannot failover.",
5753                                  errors.ECODE_STATE)
5754
5755     secondary_nodes = instance.secondary_nodes
5756     if not secondary_nodes:
5757       raise errors.ProgrammerError("no secondary node but using "
5758                                    "a mirrored disk template")
5759
5760     target_node = secondary_nodes[0]
5761     _CheckNodeOnline(self, target_node)
5762     _CheckNodeNotDrained(self, target_node)
5763     if instance.admin_up:
5764       # check memory requirements on the secondary node
5765       _CheckNodeFreeMemory(self, target_node, "failing over instance %s" %
5766                            instance.name, bep[constants.BE_MEMORY],
5767                            instance.hypervisor)
5768     else:
5769       self.LogInfo("Not checking memory on the secondary node as"
5770                    " instance will not be started")
5771
5772     # check bridge existance
5773     _CheckInstanceBridgesExist(self, instance, node=target_node)
5774
5775   def Exec(self, feedback_fn):
5776     """Failover an instance.
5777
5778     The failover is done by shutting it down on its present node and
5779     starting it on the secondary.
5780
5781     """
5782     instance = self.instance
5783     primary_node = self.cfg.GetNodeInfo(instance.primary_node)
5784
5785     source_node = instance.primary_node
5786     target_node = instance.secondary_nodes[0]
5787
5788     if instance.admin_up:
5789       feedback_fn("* checking disk consistency between source and target")
5790       for dev in instance.disks:
5791         # for drbd, these are drbd over lvm
5792         if not _CheckDiskConsistency(self, dev, target_node, False):
5793           if not self.op.ignore_consistency:
5794             raise errors.OpExecError("Disk %s is degraded on target node,"
5795                                      " aborting failover." % dev.iv_name)
5796     else:
5797       feedback_fn("* not checking disk consistency as instance is not running")
5798
5799     feedback_fn("* shutting down instance on source node")
5800     logging.info("Shutting down instance %s on node %s",
5801                  instance.name, source_node)
5802
5803     result = self.rpc.call_instance_shutdown(source_node, instance,
5804                                              self.op.shutdown_timeout)
5805     msg = result.fail_msg
5806     if msg:
5807       if self.op.ignore_consistency or primary_node.offline:
5808         self.proc.LogWarning("Could not shutdown instance %s on node %s."
5809                              " Proceeding anyway. Please make sure node"
5810                              " %s is down. Error details: %s",
5811                              instance.name, source_node, source_node, msg)
5812       else:
5813         raise errors.OpExecError("Could not shutdown instance %s on"
5814                                  " node %s: %s" %
5815                                  (instance.name, source_node, msg))
5816
5817     feedback_fn("* deactivating the instance's disks on source node")
5818     if not _ShutdownInstanceDisks(self, instance, ignore_primary=True):
5819       raise errors.OpExecError("Can't shut down the instance's disks.")
5820
5821     instance.primary_node = target_node
5822     # distribute new instance config to the other nodes
5823     self.cfg.Update(instance, feedback_fn)
5824
5825     # Only start the instance if it's marked as up
5826     if instance.admin_up:
5827       feedback_fn("* activating the instance's disks on target node")
5828       logging.info("Starting instance %s on node %s",
5829                    instance.name, target_node)
5830
5831       disks_ok, _ = _AssembleInstanceDisks(self, instance,
5832                                            ignore_secondaries=True)
5833       if not disks_ok:
5834         _ShutdownInstanceDisks(self, instance)
5835         raise errors.OpExecError("Can't activate the instance's disks")
5836
5837       feedback_fn("* starting the instance on the target node")
5838       result = self.rpc.call_instance_start(target_node, instance, None, None)
5839       msg = result.fail_msg
5840       if msg:
5841         _ShutdownInstanceDisks(self, instance)
5842         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
5843                                  (instance.name, target_node, msg))
5844
5845
5846 class LUInstanceMigrate(LogicalUnit):
5847   """Migrate an instance.
5848
5849   This is migration without shutting down, compared to the failover,
5850   which is done with shutdown.
5851
5852   """
5853   HPATH = "instance-migrate"
5854   HTYPE = constants.HTYPE_INSTANCE
5855   REQ_BGL = False
5856
5857   def CheckArguments(self):
5858     _CheckIAllocatorOrNode(self, "iallocator", "target_node")
5859
5860   def ExpandNames(self):
5861     self._ExpandAndLockInstance()
5862
5863     if self.op.target_node is not None:
5864       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
5865
5866     self.needed_locks[locking.LEVEL_NODE] = []
5867     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
5868
5869     self._migrater = TLMigrateInstance(self, self.op.instance_name,
5870                                        self.op.cleanup, self.op.iallocator,
5871                                        self.op.target_node)
5872     self.tasklets = [self._migrater]
5873
5874   def DeclareLocks(self, level):
5875     if level == locking.LEVEL_NODE:
5876       instance = self.context.cfg.GetInstanceInfo(self.op.instance_name)
5877       if instance.disk_template in constants.DTS_EXT_MIRROR:
5878         if self.op.target_node is None:
5879           self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5880         else:
5881           self.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
5882                                                    self.op.target_node]
5883         del self.recalculate_locks[locking.LEVEL_NODE]
5884       else:
5885         self._LockInstancesNodes()
5886
5887   def BuildHooksEnv(self):
5888     """Build hooks env.
5889
5890     This runs on master, primary and secondary nodes of the instance.
5891
5892     """
5893     instance = self._migrater.instance
5894     source_node = instance.primary_node
5895     target_node = self._migrater.target_node
5896     env = _BuildInstanceHookEnvByObject(self, instance)
5897     env["MIGRATE_LIVE"] = self._migrater.live
5898     env["MIGRATE_CLEANUP"] = self.op.cleanup
5899     env.update({
5900         "OLD_PRIMARY": source_node,
5901         "NEW_PRIMARY": target_node,
5902         })
5903
5904     if instance.disk_template in constants.DTS_NET_MIRROR:
5905       env["OLD_SECONDARY"] = target_node
5906       env["NEW_SECONDARY"] = source_node
5907     else:
5908       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = None
5909
5910     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
5911     nl_post = list(nl)
5912     nl_post.append(source_node)
5913     return env, nl, nl_post
5914
5915
5916 class LUInstanceMove(LogicalUnit):
5917   """Move an instance by data-copying.
5918
5919   """
5920   HPATH = "instance-move"
5921   HTYPE = constants.HTYPE_INSTANCE
5922   REQ_BGL = False
5923
5924   def ExpandNames(self):
5925     self._ExpandAndLockInstance()
5926     target_node = _ExpandNodeName(self.cfg, self.op.target_node)
5927     self.op.target_node = target_node
5928     self.needed_locks[locking.LEVEL_NODE] = [target_node]
5929     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
5930
5931   def DeclareLocks(self, level):
5932     if level == locking.LEVEL_NODE:
5933       self._LockInstancesNodes(primary_only=True)
5934
5935   def BuildHooksEnv(self):
5936     """Build hooks env.
5937
5938     This runs on master, primary and secondary nodes of the instance.
5939
5940     """
5941     env = {
5942       "TARGET_NODE": self.op.target_node,
5943       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
5944       }
5945     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
5946     nl = [self.cfg.GetMasterNode()] + [self.instance.primary_node,
5947                                        self.op.target_node]
5948     return env, nl, nl
5949
5950   def CheckPrereq(self):
5951     """Check prerequisites.
5952
5953     This checks that the instance is in the cluster.
5954
5955     """
5956     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
5957     assert self.instance is not None, \
5958       "Cannot retrieve locked instance %s" % self.op.instance_name
5959
5960     node = self.cfg.GetNodeInfo(self.op.target_node)
5961     assert node is not None, \
5962       "Cannot retrieve locked node %s" % self.op.target_node
5963
5964     self.target_node = target_node = node.name
5965
5966     if target_node == instance.primary_node:
5967       raise errors.OpPrereqError("Instance %s is already on the node %s" %
5968                                  (instance.name, target_node),
5969                                  errors.ECODE_STATE)
5970
5971     bep = self.cfg.GetClusterInfo().FillBE(instance)
5972
5973     for idx, dsk in enumerate(instance.disks):
5974       if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
5975         raise errors.OpPrereqError("Instance disk %d has a complex layout,"
5976                                    " cannot copy" % idx, errors.ECODE_STATE)
5977
5978     _CheckNodeOnline(self, target_node)
5979     _CheckNodeNotDrained(self, target_node)
5980     _CheckNodeVmCapable(self, target_node)
5981
5982     if instance.admin_up:
5983       # check memory requirements on the secondary node
5984       _CheckNodeFreeMemory(self, target_node, "failing over instance %s" %
5985                            instance.name, bep[constants.BE_MEMORY],
5986                            instance.hypervisor)
5987     else:
5988       self.LogInfo("Not checking memory on the secondary node as"
5989                    " instance will not be started")
5990
5991     # check bridge existance
5992     _CheckInstanceBridgesExist(self, instance, node=target_node)
5993
5994   def Exec(self, feedback_fn):
5995     """Move an instance.
5996
5997     The move is done by shutting it down on its present node, copying
5998     the data over (slow) and starting it on the new node.
5999
6000     """
6001     instance = self.instance
6002
6003     source_node = instance.primary_node
6004     target_node = self.target_node
6005
6006     self.LogInfo("Shutting down instance %s on source node %s",
6007                  instance.name, source_node)
6008
6009     result = self.rpc.call_instance_shutdown(source_node, instance,
6010                                              self.op.shutdown_timeout)
6011     msg = result.fail_msg
6012     if msg:
6013       if self.op.ignore_consistency:
6014         self.proc.LogWarning("Could not shutdown instance %s on node %s."
6015                              " Proceeding anyway. Please make sure node"
6016                              " %s is down. Error details: %s",
6017                              instance.name, source_node, source_node, msg)
6018       else:
6019         raise errors.OpExecError("Could not shutdown instance %s on"
6020                                  " node %s: %s" %
6021                                  (instance.name, source_node, msg))
6022
6023     # create the target disks
6024     try:
6025       _CreateDisks(self, instance, target_node=target_node)
6026     except errors.OpExecError:
6027       self.LogWarning("Device creation failed, reverting...")
6028       try:
6029         _RemoveDisks(self, instance, target_node=target_node)
6030       finally:
6031         self.cfg.ReleaseDRBDMinors(instance.name)
6032         raise
6033
6034     cluster_name = self.cfg.GetClusterInfo().cluster_name
6035
6036     errs = []
6037     # activate, get path, copy the data over
6038     for idx, disk in enumerate(instance.disks):
6039       self.LogInfo("Copying data for disk %d", idx)
6040       result = self.rpc.call_blockdev_assemble(target_node, disk,
6041                                                instance.name, True, idx)
6042       if result.fail_msg:
6043         self.LogWarning("Can't assemble newly created disk %d: %s",
6044                         idx, result.fail_msg)
6045         errs.append(result.fail_msg)
6046         break
6047       dev_path = result.payload
6048       result = self.rpc.call_blockdev_export(source_node, disk,
6049                                              target_node, dev_path,
6050                                              cluster_name)
6051       if result.fail_msg:
6052         self.LogWarning("Can't copy data over for disk %d: %s",
6053                         idx, result.fail_msg)
6054         errs.append(result.fail_msg)
6055         break
6056
6057     if errs:
6058       self.LogWarning("Some disks failed to copy, aborting")
6059       try:
6060         _RemoveDisks(self, instance, target_node=target_node)
6061       finally:
6062         self.cfg.ReleaseDRBDMinors(instance.name)
6063         raise errors.OpExecError("Errors during disk copy: %s" %
6064                                  (",".join(errs),))
6065
6066     instance.primary_node = target_node
6067     self.cfg.Update(instance, feedback_fn)
6068
6069     self.LogInfo("Removing the disks on the original node")
6070     _RemoveDisks(self, instance, target_node=source_node)
6071
6072     # Only start the instance if it's marked as up
6073     if instance.admin_up:
6074       self.LogInfo("Starting instance %s on node %s",
6075                    instance.name, target_node)
6076
6077       disks_ok, _ = _AssembleInstanceDisks(self, instance,
6078                                            ignore_secondaries=True)
6079       if not disks_ok:
6080         _ShutdownInstanceDisks(self, instance)
6081         raise errors.OpExecError("Can't activate the instance's disks")
6082
6083       result = self.rpc.call_instance_start(target_node, instance, None, None)
6084       msg = result.fail_msg
6085       if msg:
6086         _ShutdownInstanceDisks(self, instance)
6087         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
6088                                  (instance.name, target_node, msg))
6089
6090
6091 class LUNodeMigrate(LogicalUnit):
6092   """Migrate all instances from a node.
6093
6094   """
6095   HPATH = "node-migrate"
6096   HTYPE = constants.HTYPE_NODE
6097   REQ_BGL = False
6098
6099   def CheckArguments(self):
6100     _CheckIAllocatorOrNode(self, "iallocator", "remote_node")
6101
6102   def ExpandNames(self):
6103     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
6104
6105     self.needed_locks = {}
6106
6107     # Create tasklets for migrating instances for all instances on this node
6108     names = []
6109     tasklets = []
6110
6111     self.lock_all_nodes = False
6112
6113     for inst in _GetNodePrimaryInstances(self.cfg, self.op.node_name):
6114       logging.debug("Migrating instance %s", inst.name)
6115       names.append(inst.name)
6116
6117       tasklets.append(TLMigrateInstance(self, inst.name, False,
6118                                         self.op.iallocator, None))
6119
6120       if inst.disk_template in constants.DTS_EXT_MIRROR:
6121         # We need to lock all nodes, as the iallocator will choose the
6122         # destination nodes afterwards
6123         self.lock_all_nodes = True
6124
6125     self.tasklets = tasklets
6126
6127     # Declare node locks
6128     if self.lock_all_nodes:
6129       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
6130     else:
6131       self.needed_locks[locking.LEVEL_NODE] = [self.op.node_name]
6132       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
6133
6134     # Declare instance locks
6135     self.needed_locks[locking.LEVEL_INSTANCE] = names
6136
6137   def DeclareLocks(self, level):
6138     if level == locking.LEVEL_NODE and not self.lock_all_nodes:
6139       self._LockInstancesNodes()
6140
6141   def BuildHooksEnv(self):
6142     """Build hooks env.
6143
6144     This runs on the master, the primary and all the secondaries.
6145
6146     """
6147     env = {
6148       "NODE_NAME": self.op.node_name,
6149       }
6150
6151     nl = [self.cfg.GetMasterNode()]
6152
6153     return (env, nl, nl)
6154
6155
6156 class TLMigrateInstance(Tasklet):
6157   """Tasklet class for instance migration.
6158
6159   @type live: boolean
6160   @ivar live: whether the migration will be done live or non-live;
6161       this variable is initalized only after CheckPrereq has run
6162
6163   """
6164   def __init__(self, lu, instance_name, cleanup,
6165                iallocator=None, target_node=None):
6166     """Initializes this class.
6167
6168     """
6169     Tasklet.__init__(self, lu)
6170
6171     # Parameters
6172     self.instance_name = instance_name
6173     self.cleanup = cleanup
6174     self.live = False # will be overridden later
6175     self.iallocator = iallocator
6176     self.target_node = target_node
6177
6178   def CheckPrereq(self):
6179     """Check prerequisites.
6180
6181     This checks that the instance is in the cluster.
6182
6183     """
6184     instance_name = _ExpandInstanceName(self.lu.cfg, self.instance_name)
6185     instance = self.cfg.GetInstanceInfo(instance_name)
6186     assert instance is not None
6187     self.instance = instance
6188
6189     if instance.disk_template not in constants.DTS_MIRRORED:
6190       raise errors.OpPrereqError("Instance's disk layout '%s' does not allow"
6191                                  " migrations" % instance.disk_template,
6192                                  errors.ECODE_STATE)
6193
6194     if instance.disk_template in constants.DTS_EXT_MIRROR:
6195       if [self.iallocator, self.target_node].count(None) != 1:
6196         raise errors.OpPrereqError("Do not specify both, iallocator and"
6197                                    " target node", errors.ECODE_INVAL)
6198
6199       if self.iallocator:
6200         self._RunAllocator()
6201
6202       # self.target_node is already populated, either directly or by the
6203       # iallocator run
6204       target_node = self.target_node
6205
6206       if len(self.lu.tasklets) == 1:
6207         # It is safe to remove locks only when we're the only tasklet in the LU
6208         nodes_keep = [instance.primary_node, self.target_node]
6209         nodes_rel = [node for node in self.lu.acquired_locks[locking.LEVEL_NODE]
6210                      if node not in nodes_keep]
6211         self.lu.context.glm.release(locking.LEVEL_NODE, nodes_rel)
6212         self.lu.acquired_locks[locking.LEVEL_NODE] = nodes_keep
6213
6214     else:
6215       secondary_nodes = instance.secondary_nodes
6216       if not secondary_nodes:
6217         raise errors.ConfigurationError("No secondary node but using"
6218                                         " %s disk template" %
6219                                         instance.disk_template)
6220       target_node = secondary_nodes[0]
6221
6222     i_be = self.cfg.GetClusterInfo().FillBE(instance)
6223
6224     # check memory requirements on the secondary node
6225     _CheckNodeFreeMemory(self.lu, target_node, "migrating instance %s" %
6226                          instance.name, i_be[constants.BE_MEMORY],
6227                          instance.hypervisor)
6228
6229     # check bridge existance
6230     _CheckInstanceBridgesExist(self.lu, instance, node=target_node)
6231
6232     if not self.cleanup:
6233       _CheckNodeNotDrained(self.lu, target_node)
6234       result = self.rpc.call_instance_migratable(instance.primary_node,
6235                                                  instance)
6236       result.Raise("Can't migrate, please use failover",
6237                    prereq=True, ecode=errors.ECODE_STATE)
6238
6239
6240   def _RunAllocator(self):
6241     """Run the allocator based on input opcode.
6242
6243     """
6244     ial = IAllocator(self.cfg, self.rpc,
6245                      mode=constants.IALLOCATOR_MODE_RELOC,
6246                      name=self.instance_name,
6247                      # TODO See why hail breaks with a single node below
6248                      relocate_from=[self.instance.primary_node,
6249                                     self.instance.primary_node],
6250                      )
6251
6252     ial.Run(self.iallocator)
6253
6254     if not ial.success:
6255       raise errors.OpPrereqError("Can't compute nodes using"
6256                                  " iallocator '%s': %s" %
6257                                  (self.iallocator, ial.info),
6258                                  errors.ECODE_NORES)
6259     if len(ial.result) != ial.required_nodes:
6260       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
6261                                  " of nodes (%s), required %s" %
6262                                  (self.iallocator, len(ial.result),
6263                                   ial.required_nodes), errors.ECODE_FAULT)
6264     self.target_node = ial.result[0]
6265     self.lu.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
6266                  self.instance_name, self.iallocator,
6267                  utils.CommaJoin(ial.result))
6268
6269     if self.lu.op.live is not None and self.lu.op.mode is not None:
6270       raise errors.OpPrereqError("Only one of the 'live' and 'mode'"
6271                                  " parameters are accepted",
6272                                  errors.ECODE_INVAL)
6273     if self.lu.op.live is not None:
6274       if self.lu.op.live:
6275         self.lu.op.mode = constants.HT_MIGRATION_LIVE
6276       else:
6277         self.lu.op.mode = constants.HT_MIGRATION_NONLIVE
6278       # reset the 'live' parameter to None so that repeated
6279       # invocations of CheckPrereq do not raise an exception
6280       self.lu.op.live = None
6281     elif self.lu.op.mode is None:
6282       # read the default value from the hypervisor
6283       i_hv = self.cfg.GetClusterInfo().FillHV(self.instance, skip_globals=False)
6284       self.lu.op.mode = i_hv[constants.HV_MIGRATION_MODE]
6285
6286     self.live = self.lu.op.mode == constants.HT_MIGRATION_LIVE
6287
6288   def _WaitUntilSync(self):
6289     """Poll with custom rpc for disk sync.
6290
6291     This uses our own step-based rpc call.
6292
6293     """
6294     self.feedback_fn("* wait until resync is done")
6295     all_done = False
6296     while not all_done:
6297       all_done = True
6298       result = self.rpc.call_drbd_wait_sync(self.all_nodes,
6299                                             self.nodes_ip,
6300                                             self.instance.disks)
6301       min_percent = 100
6302       for node, nres in result.items():
6303         nres.Raise("Cannot resync disks on node %s" % node)
6304         node_done, node_percent = nres.payload
6305         all_done = all_done and node_done
6306         if node_percent is not None:
6307           min_percent = min(min_percent, node_percent)
6308       if not all_done:
6309         if min_percent < 100:
6310           self.feedback_fn("   - progress: %.1f%%" % min_percent)
6311         time.sleep(2)
6312
6313   def _EnsureSecondary(self, node):
6314     """Demote a node to secondary.
6315
6316     """
6317     self.feedback_fn("* switching node %s to secondary mode" % node)
6318
6319     for dev in self.instance.disks:
6320       self.cfg.SetDiskID(dev, node)
6321
6322     result = self.rpc.call_blockdev_close(node, self.instance.name,
6323                                           self.instance.disks)
6324     result.Raise("Cannot change disk to secondary on node %s" % node)
6325
6326   def _GoStandalone(self):
6327     """Disconnect from the network.
6328
6329     """
6330     self.feedback_fn("* changing into standalone mode")
6331     result = self.rpc.call_drbd_disconnect_net(self.all_nodes, self.nodes_ip,
6332                                                self.instance.disks)
6333     for node, nres in result.items():
6334       nres.Raise("Cannot disconnect disks node %s" % node)
6335
6336   def _GoReconnect(self, multimaster):
6337     """Reconnect to the network.
6338
6339     """
6340     if multimaster:
6341       msg = "dual-master"
6342     else:
6343       msg = "single-master"
6344     self.feedback_fn("* changing disks into %s mode" % msg)
6345     result = self.rpc.call_drbd_attach_net(self.all_nodes, self.nodes_ip,
6346                                            self.instance.disks,
6347                                            self.instance.name, multimaster)
6348     for node, nres in result.items():
6349       nres.Raise("Cannot change disks config on node %s" % node)
6350
6351   def _ExecCleanup(self):
6352     """Try to cleanup after a failed migration.
6353
6354     The cleanup is done by:
6355       - check that the instance is running only on one node
6356         (and update the config if needed)
6357       - change disks on its secondary node to secondary
6358       - wait until disks are fully synchronized
6359       - disconnect from the network
6360       - change disks into single-master mode
6361       - wait again until disks are fully synchronized
6362
6363     """
6364     instance = self.instance
6365     target_node = self.target_node
6366     source_node = self.source_node
6367
6368     # check running on only one node
6369     self.feedback_fn("* checking where the instance actually runs"
6370                      " (if this hangs, the hypervisor might be in"
6371                      " a bad state)")
6372     ins_l = self.rpc.call_instance_list(self.all_nodes, [instance.hypervisor])
6373     for node, result in ins_l.items():
6374       result.Raise("Can't contact node %s" % node)
6375
6376     runningon_source = instance.name in ins_l[source_node].payload
6377     runningon_target = instance.name in ins_l[target_node].payload
6378
6379     if runningon_source and runningon_target:
6380       raise errors.OpExecError("Instance seems to be running on two nodes,"
6381                                " or the hypervisor is confused. You will have"
6382                                " to ensure manually that it runs only on one"
6383                                " and restart this operation.")
6384
6385     if not (runningon_source or runningon_target):
6386       raise errors.OpExecError("Instance does not seem to be running at all."
6387                                " In this case, it's safer to repair by"
6388                                " running 'gnt-instance stop' to ensure disk"
6389                                " shutdown, and then restarting it.")
6390
6391     if runningon_target:
6392       # the migration has actually succeeded, we need to update the config
6393       self.feedback_fn("* instance running on secondary node (%s),"
6394                        " updating config" % target_node)
6395       instance.primary_node = target_node
6396       self.cfg.Update(instance, self.feedback_fn)
6397       demoted_node = source_node
6398     else:
6399       self.feedback_fn("* instance confirmed to be running on its"
6400                        " primary node (%s)" % source_node)
6401       demoted_node = target_node
6402
6403     if instance.disk_template in constants.DTS_NET_MIRROR:
6404       self._EnsureSecondary(demoted_node)
6405       try:
6406         self._WaitUntilSync()
6407       except errors.OpExecError:
6408         # we ignore here errors, since if the device is standalone, it
6409         # won't be able to sync
6410         pass
6411       self._GoStandalone()
6412       self._GoReconnect(False)
6413       self._WaitUntilSync()
6414
6415     self.feedback_fn("* done")
6416
6417   def _RevertDiskStatus(self):
6418     """Try to revert the disk status after a failed migration.
6419
6420     """
6421     target_node = self.target_node
6422     if self.instance.disk_template in constants.DTS_EXT_MIRROR:
6423       return
6424
6425     try:
6426       self._EnsureSecondary(target_node)
6427       self._GoStandalone()
6428       self._GoReconnect(False)
6429       self._WaitUntilSync()
6430     except errors.OpExecError, err:
6431       self.lu.LogWarning("Migration failed and I can't reconnect the"
6432                          " drives: error '%s'\n"
6433                          "Please look and recover the instance status" %
6434                          str(err))
6435
6436   def _AbortMigration(self):
6437     """Call the hypervisor code to abort a started migration.
6438
6439     """
6440     instance = self.instance
6441     target_node = self.target_node
6442     migration_info = self.migration_info
6443
6444     abort_result = self.rpc.call_finalize_migration(target_node,
6445                                                     instance,
6446                                                     migration_info,
6447                                                     False)
6448     abort_msg = abort_result.fail_msg
6449     if abort_msg:
6450       logging.error("Aborting migration failed on target node %s: %s",
6451                     target_node, abort_msg)
6452       # Don't raise an exception here, as we stil have to try to revert the
6453       # disk status, even if this step failed.
6454
6455   def _ExecMigration(self):
6456     """Migrate an instance.
6457
6458     The migrate is done by:
6459       - change the disks into dual-master mode
6460       - wait until disks are fully synchronized again
6461       - migrate the instance
6462       - change disks on the new secondary node (the old primary) to secondary
6463       - wait until disks are fully synchronized
6464       - change disks into single-master mode
6465
6466     """
6467     instance = self.instance
6468     target_node = self.target_node
6469     source_node = self.source_node
6470
6471     self.feedback_fn("* checking disk consistency between source and target")
6472     for dev in instance.disks:
6473       if not _CheckDiskConsistency(self.lu, dev, target_node, False):
6474         raise errors.OpExecError("Disk %s is degraded or not fully"
6475                                  " synchronized on target node,"
6476                                  " aborting migrate." % dev.iv_name)
6477
6478     # First get the migration information from the remote node
6479     result = self.rpc.call_migration_info(source_node, instance)
6480     msg = result.fail_msg
6481     if msg:
6482       log_err = ("Failed fetching source migration information from %s: %s" %
6483                  (source_node, msg))
6484       logging.error(log_err)
6485       raise errors.OpExecError(log_err)
6486
6487     self.migration_info = migration_info = result.payload
6488
6489     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
6490       # Then switch the disks to master/master mode
6491       self._EnsureSecondary(target_node)
6492       self._GoStandalone()
6493       self._GoReconnect(True)
6494       self._WaitUntilSync()
6495
6496     self.feedback_fn("* preparing %s to accept the instance" % target_node)
6497     result = self.rpc.call_accept_instance(target_node,
6498                                            instance,
6499                                            migration_info,
6500                                            self.nodes_ip[target_node])
6501
6502     msg = result.fail_msg
6503     if msg:
6504       logging.error("Instance pre-migration failed, trying to revert"
6505                     " disk status: %s", msg)
6506       self.feedback_fn("Pre-migration failed, aborting")
6507       self._AbortMigration()
6508       self._RevertDiskStatus()
6509       raise errors.OpExecError("Could not pre-migrate instance %s: %s" %
6510                                (instance.name, msg))
6511
6512     self.feedback_fn("* migrating instance to %s" % target_node)
6513     time.sleep(10)
6514     result = self.rpc.call_instance_migrate(source_node, instance,
6515                                             self.nodes_ip[target_node],
6516                                             self.live)
6517     msg = result.fail_msg
6518     if msg:
6519       logging.error("Instance migration failed, trying to revert"
6520                     " disk status: %s", msg)
6521       self.feedback_fn("Migration failed, aborting")
6522       self._AbortMigration()
6523       self._RevertDiskStatus()
6524       raise errors.OpExecError("Could not migrate instance %s: %s" %
6525                                (instance.name, msg))
6526     time.sleep(10)
6527
6528     instance.primary_node = target_node
6529     # distribute new instance config to the other nodes
6530     self.cfg.Update(instance, self.feedback_fn)
6531
6532     result = self.rpc.call_finalize_migration(target_node,
6533                                               instance,
6534                                               migration_info,
6535                                               True)
6536     msg = result.fail_msg
6537     if msg:
6538       logging.error("Instance migration succeeded, but finalization failed:"
6539                     " %s", msg)
6540       raise errors.OpExecError("Could not finalize instance migration: %s" %
6541                                msg)
6542
6543     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
6544       self._EnsureSecondary(source_node)
6545       self._WaitUntilSync()
6546       self._GoStandalone()
6547       self._GoReconnect(False)
6548       self._WaitUntilSync()
6549
6550     self.feedback_fn("* done")
6551
6552   def Exec(self, feedback_fn):
6553     """Perform the migration.
6554
6555     """
6556     feedback_fn("Migrating instance %s" % self.instance.name)
6557
6558     self.feedback_fn = feedback_fn
6559
6560     self.source_node = self.instance.primary_node
6561
6562     # FIXME: if we implement migrate-to-any in DRBD, this needs fixing
6563     if self.instance.disk_template in constants.DTS_NET_MIRROR:
6564       self.target_node = self.instance.secondary_nodes[0]
6565       # Otherwise self.target_node has been populated either
6566       # directly, or through an iallocator.
6567
6568     self.all_nodes = [self.source_node, self.target_node]
6569     self.nodes_ip = {
6570       self.source_node: self.cfg.GetNodeInfo(self.source_node).secondary_ip,
6571       self.target_node: self.cfg.GetNodeInfo(self.target_node).secondary_ip,
6572       }
6573
6574     if self.cleanup:
6575       return self._ExecCleanup()
6576     else:
6577       return self._ExecMigration()
6578
6579
6580 def _CreateBlockDev(lu, node, instance, device, force_create,
6581                     info, force_open):
6582   """Create a tree of block devices on a given node.
6583
6584   If this device type has to be created on secondaries, create it and
6585   all its children.
6586
6587   If not, just recurse to children keeping the same 'force' value.
6588
6589   @param lu: the lu on whose behalf we execute
6590   @param node: the node on which to create the device
6591   @type instance: L{objects.Instance}
6592   @param instance: the instance which owns the device
6593   @type device: L{objects.Disk}
6594   @param device: the device to create
6595   @type force_create: boolean
6596   @param force_create: whether to force creation of this device; this
6597       will be change to True whenever we find a device which has
6598       CreateOnSecondary() attribute
6599   @param info: the extra 'metadata' we should attach to the device
6600       (this will be represented as a LVM tag)
6601   @type force_open: boolean
6602   @param force_open: this parameter will be passes to the
6603       L{backend.BlockdevCreate} function where it specifies
6604       whether we run on primary or not, and it affects both
6605       the child assembly and the device own Open() execution
6606
6607   """
6608   if device.CreateOnSecondary():
6609     force_create = True
6610
6611   if device.children:
6612     for child in device.children:
6613       _CreateBlockDev(lu, node, instance, child, force_create,
6614                       info, force_open)
6615
6616   if not force_create:
6617     return
6618
6619   _CreateSingleBlockDev(lu, node, instance, device, info, force_open)
6620
6621
6622 def _CreateSingleBlockDev(lu, node, instance, device, info, force_open):
6623   """Create a single block device on a given node.
6624
6625   This will not recurse over children of the device, so they must be
6626   created in advance.
6627
6628   @param lu: the lu on whose behalf we execute
6629   @param node: the node on which to create the device
6630   @type instance: L{objects.Instance}
6631   @param instance: the instance which owns the device
6632   @type device: L{objects.Disk}
6633   @param device: the device to create
6634   @param info: the extra 'metadata' we should attach to the device
6635       (this will be represented as a LVM tag)
6636   @type force_open: boolean
6637   @param force_open: this parameter will be passes to the
6638       L{backend.BlockdevCreate} function where it specifies
6639       whether we run on primary or not, and it affects both
6640       the child assembly and the device own Open() execution
6641
6642   """
6643   lu.cfg.SetDiskID(device, node)
6644   result = lu.rpc.call_blockdev_create(node, device, device.size,
6645                                        instance.name, force_open, info)
6646   result.Raise("Can't create block device %s on"
6647                " node %s for instance %s" % (device, node, instance.name))
6648   if device.physical_id is None:
6649     device.physical_id = result.payload
6650
6651
6652 def _GenerateUniqueNames(lu, exts):
6653   """Generate a suitable LV name.
6654
6655   This will generate a logical volume name for the given instance.
6656
6657   """
6658   results = []
6659   for val in exts:
6660     new_id = lu.cfg.GenerateUniqueID(lu.proc.GetECId())
6661     results.append("%s%s" % (new_id, val))
6662   return results
6663
6664
6665 def _GenerateDRBD8Branch(lu, primary, secondary, size, vgname, names, iv_name,
6666                          p_minor, s_minor):
6667   """Generate a drbd8 device complete with its children.
6668
6669   """
6670   port = lu.cfg.AllocatePort()
6671   shared_secret = lu.cfg.GenerateDRBDSecret(lu.proc.GetECId())
6672   dev_data = objects.Disk(dev_type=constants.LD_LV, size=size,
6673                           logical_id=(vgname, names[0]))
6674   dev_meta = objects.Disk(dev_type=constants.LD_LV, size=128,
6675                           logical_id=(vgname, names[1]))
6676   drbd_dev = objects.Disk(dev_type=constants.LD_DRBD8, size=size,
6677                           logical_id=(primary, secondary, port,
6678                                       p_minor, s_minor,
6679                                       shared_secret),
6680                           children=[dev_data, dev_meta],
6681                           iv_name=iv_name)
6682   return drbd_dev
6683
6684
6685 def _GenerateDiskTemplate(lu, template_name,
6686                           instance_name, primary_node,
6687                           secondary_nodes, disk_info,
6688                           file_storage_dir, file_driver,
6689                           base_index, feedback_fn):
6690   """Generate the entire disk layout for a given template type.
6691
6692   """
6693   #TODO: compute space requirements
6694
6695   vgname = lu.cfg.GetVGName()
6696   disk_count = len(disk_info)
6697   disks = []
6698   if template_name == constants.DT_DISKLESS:
6699     pass
6700   elif template_name == constants.DT_PLAIN:
6701     if len(secondary_nodes) != 0:
6702       raise errors.ProgrammerError("Wrong template configuration")
6703
6704     names = _GenerateUniqueNames(lu, [".disk%d" % (base_index + i)
6705                                       for i in range(disk_count)])
6706     for idx, disk in enumerate(disk_info):
6707       disk_index = idx + base_index
6708       vg = disk.get("vg", vgname)
6709       feedback_fn("* disk %i, vg %s, name %s" % (idx, vg, names[idx]))
6710       disk_dev = objects.Disk(dev_type=constants.LD_LV, size=disk["size"],
6711                               logical_id=(vg, names[idx]),
6712                               iv_name="disk/%d" % disk_index,
6713                               mode=disk["mode"])
6714       disks.append(disk_dev)
6715   elif template_name == constants.DT_DRBD8:
6716     if len(secondary_nodes) != 1:
6717       raise errors.ProgrammerError("Wrong template configuration")
6718     remote_node = secondary_nodes[0]
6719     minors = lu.cfg.AllocateDRBDMinor(
6720       [primary_node, remote_node] * len(disk_info), instance_name)
6721
6722     names = []
6723     for lv_prefix in _GenerateUniqueNames(lu, [".disk%d" % (base_index + i)
6724                                                for i in range(disk_count)]):
6725       names.append(lv_prefix + "_data")
6726       names.append(lv_prefix + "_meta")
6727     for idx, disk in enumerate(disk_info):
6728       disk_index = idx + base_index
6729       vg = disk.get("vg", vgname)
6730       disk_dev = _GenerateDRBD8Branch(lu, primary_node, remote_node,
6731                                       disk["size"], vg, names[idx*2:idx*2+2],
6732                                       "disk/%d" % disk_index,
6733                                       minors[idx*2], minors[idx*2+1])
6734       disk_dev.mode = disk["mode"]
6735       disks.append(disk_dev)
6736   elif template_name == constants.DT_FILE:
6737     if len(secondary_nodes) != 0:
6738       raise errors.ProgrammerError("Wrong template configuration")
6739
6740     opcodes.RequireFileStorage()
6741
6742     for idx, disk in enumerate(disk_info):
6743       disk_index = idx + base_index
6744       disk_dev = objects.Disk(dev_type=constants.LD_FILE, size=disk["size"],
6745                               iv_name="disk/%d" % disk_index,
6746                               logical_id=(file_driver,
6747                                           "%s/disk%d" % (file_storage_dir,
6748                                                          disk_index)),
6749                               mode=disk["mode"])
6750       disks.append(disk_dev)
6751   elif template_name == constants.DT_SHARED_FILE:
6752     if len(secondary_nodes) != 0:
6753       raise errors.ProgrammerError("Wrong template configuration")
6754
6755     opcodes.RequireSharedFileStorage()
6756
6757     for idx, disk in enumerate(disk_info):
6758       disk_index = idx + base_index
6759       disk_dev = objects.Disk(dev_type=constants.LD_FILE, size=disk["size"],
6760                               iv_name="disk/%d" % disk_index,
6761                               logical_id=(file_driver,
6762                                           "%s/disk%d" % (file_storage_dir,
6763                                                          disk_index)),
6764                               mode=disk["mode"])
6765       disks.append(disk_dev)
6766   elif template_name == constants.DT_BLOCK:
6767     if len(secondary_nodes) != 0:
6768       raise errors.ProgrammerError("Wrong template configuration")
6769
6770     for idx, disk in enumerate(disk_info):
6771       disk_index = idx + base_index
6772       disk_dev = objects.Disk(dev_type=constants.LD_BLOCKDEV, size=disk["size"],
6773                               logical_id=(constants.BLOCKDEV_DRIVER_MANUAL,
6774                                           disk["adopt"]),
6775                               iv_name="disk/%d" % disk_index,
6776                               mode=disk["mode"])
6777       disks.append(disk_dev)
6778
6779   else:
6780     raise errors.ProgrammerError("Invalid disk template '%s'" % template_name)
6781   return disks
6782
6783
6784 def _GetInstanceInfoText(instance):
6785   """Compute that text that should be added to the disk's metadata.
6786
6787   """
6788   return "originstname+%s" % instance.name
6789
6790
6791 def _CalcEta(time_taken, written, total_size):
6792   """Calculates the ETA based on size written and total size.
6793
6794   @param time_taken: The time taken so far
6795   @param written: amount written so far
6796   @param total_size: The total size of data to be written
6797   @return: The remaining time in seconds
6798
6799   """
6800   avg_time = time_taken / float(written)
6801   return (total_size - written) * avg_time
6802
6803
6804 def _WipeDisks(lu, instance):
6805   """Wipes instance disks.
6806
6807   @type lu: L{LogicalUnit}
6808   @param lu: the logical unit on whose behalf we execute
6809   @type instance: L{objects.Instance}
6810   @param instance: the instance whose disks we should create
6811   @return: the success of the wipe
6812
6813   """
6814   node = instance.primary_node
6815
6816   for device in instance.disks:
6817     lu.cfg.SetDiskID(device, node)
6818
6819   logging.info("Pause sync of instance %s disks", instance.name)
6820   result = lu.rpc.call_blockdev_pause_resume_sync(node, instance.disks, True)
6821
6822   for idx, success in enumerate(result.payload):
6823     if not success:
6824       logging.warn("pause-sync of instance %s for disks %d failed",
6825                    instance.name, idx)
6826
6827   try:
6828     for idx, device in enumerate(instance.disks):
6829       lu.LogInfo("* Wiping disk %d", idx)
6830       logging.info("Wiping disk %d for instance %s, node %s",
6831                    idx, instance.name, node)
6832
6833       # The wipe size is MIN_WIPE_CHUNK_PERCENT % of the instance disk but
6834       # MAX_WIPE_CHUNK at max
6835       wipe_chunk_size = min(constants.MAX_WIPE_CHUNK, device.size / 100.0 *
6836                             constants.MIN_WIPE_CHUNK_PERCENT)
6837
6838       offset = 0
6839       size = device.size
6840       last_output = 0
6841       start_time = time.time()
6842
6843       while offset < size:
6844         wipe_size = min(wipe_chunk_size, size - offset)
6845         result = lu.rpc.call_blockdev_wipe(node, device, offset, wipe_size)
6846         result.Raise("Could not wipe disk %d at offset %d for size %d" %
6847                      (idx, offset, wipe_size))
6848         now = time.time()
6849         offset += wipe_size
6850         if now - last_output >= 60:
6851           eta = _CalcEta(now - start_time, offset, size)
6852           lu.LogInfo(" - done: %.1f%% ETA: %s" %
6853                      (offset / float(size) * 100, utils.FormatSeconds(eta)))
6854           last_output = now
6855   finally:
6856     logging.info("Resume sync of instance %s disks", instance.name)
6857
6858     result = lu.rpc.call_blockdev_pause_resume_sync(node, instance.disks, False)
6859
6860     for idx, success in enumerate(result.payload):
6861       if not success:
6862         lu.LogWarning("Warning: Resume sync of disk %d failed. Please have a"
6863                       " look at the status and troubleshoot the issue.", idx)
6864         logging.warn("resume-sync of instance %s for disks %d failed",
6865                      instance.name, idx)
6866
6867
6868 def _CreateDisks(lu, instance, to_skip=None, target_node=None):
6869   """Create all disks for an instance.
6870
6871   This abstracts away some work from AddInstance.
6872
6873   @type lu: L{LogicalUnit}
6874   @param lu: the logical unit on whose behalf we execute
6875   @type instance: L{objects.Instance}
6876   @param instance: the instance whose disks we should create
6877   @type to_skip: list
6878   @param to_skip: list of indices to skip
6879   @type target_node: string
6880   @param target_node: if passed, overrides the target node for creation
6881   @rtype: boolean
6882   @return: the success of the creation
6883
6884   """
6885   info = _GetInstanceInfoText(instance)
6886   if target_node is None:
6887     pnode = instance.primary_node
6888     all_nodes = instance.all_nodes
6889   else:
6890     pnode = target_node
6891     all_nodes = [pnode]
6892
6893   if instance.disk_template in (constants.DT_FILE, constants.DT_SHARED_FILE):
6894     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
6895     result = lu.rpc.call_file_storage_dir_create(pnode, file_storage_dir)
6896
6897     result.Raise("Failed to create directory '%s' on"
6898                  " node %s" % (file_storage_dir, pnode))
6899
6900   # Note: this needs to be kept in sync with adding of disks in
6901   # LUInstanceSetParams
6902   for idx, device in enumerate(instance.disks):
6903     if to_skip and idx in to_skip:
6904       continue
6905     logging.info("Creating volume %s for instance %s",
6906                  device.iv_name, instance.name)
6907     #HARDCODE
6908     for node in all_nodes:
6909       f_create = node == pnode
6910       _CreateBlockDev(lu, node, instance, device, f_create, info, f_create)
6911
6912
6913 def _RemoveDisks(lu, instance, target_node=None):
6914   """Remove all disks for an instance.
6915
6916   This abstracts away some work from `AddInstance()` and
6917   `RemoveInstance()`. Note that in case some of the devices couldn't
6918   be removed, the removal will continue with the other ones (compare
6919   with `_CreateDisks()`).
6920
6921   @type lu: L{LogicalUnit}
6922   @param lu: the logical unit on whose behalf we execute
6923   @type instance: L{objects.Instance}
6924   @param instance: the instance whose disks we should remove
6925   @type target_node: string
6926   @param target_node: used to override the node on which to remove the disks
6927   @rtype: boolean
6928   @return: the success of the removal
6929
6930   """
6931   logging.info("Removing block devices for instance %s", instance.name)
6932
6933   all_result = True
6934   for device in instance.disks:
6935     if target_node:
6936       edata = [(target_node, device)]
6937     else:
6938       edata = device.ComputeNodeTree(instance.primary_node)
6939     for node, disk in edata:
6940       lu.cfg.SetDiskID(disk, node)
6941       msg = lu.rpc.call_blockdev_remove(node, disk).fail_msg
6942       if msg:
6943         lu.LogWarning("Could not remove block device %s on node %s,"
6944                       " continuing anyway: %s", device.iv_name, node, msg)
6945         all_result = False
6946
6947   if instance.disk_template == constants.DT_FILE:
6948     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
6949     if target_node:
6950       tgt = target_node
6951     else:
6952       tgt = instance.primary_node
6953     result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
6954     if result.fail_msg:
6955       lu.LogWarning("Could not remove directory '%s' on node %s: %s",
6956                     file_storage_dir, instance.primary_node, result.fail_msg)
6957       all_result = False
6958
6959   return all_result
6960
6961
6962 def _ComputeDiskSizePerVG(disk_template, disks):
6963   """Compute disk size requirements in the volume group
6964
6965   """
6966   def _compute(disks, payload):
6967     """Universal algorithm
6968
6969     """
6970     vgs = {}
6971     for disk in disks:
6972       vgs[disk["vg"]] = vgs.get("vg", 0) + disk["size"] + payload
6973
6974     return vgs
6975
6976   # Required free disk space as a function of disk and swap space
6977   req_size_dict = {
6978     constants.DT_DISKLESS: {},
6979     constants.DT_PLAIN: _compute(disks, 0),
6980     # 128 MB are added for drbd metadata for each disk
6981     constants.DT_DRBD8: _compute(disks, 128),
6982     constants.DT_FILE: {},
6983     constants.DT_SHARED_FILE: {},
6984   }
6985
6986   if disk_template not in req_size_dict:
6987     raise errors.ProgrammerError("Disk template '%s' size requirement"
6988                                  " is unknown" %  disk_template)
6989
6990   return req_size_dict[disk_template]
6991
6992
6993 def _ComputeDiskSize(disk_template, disks):
6994   """Compute disk size requirements in the volume group
6995
6996   """
6997   # Required free disk space as a function of disk and swap space
6998   req_size_dict = {
6999     constants.DT_DISKLESS: None,
7000     constants.DT_PLAIN: sum(d["size"] for d in disks),
7001     # 128 MB are added for drbd metadata for each disk
7002     constants.DT_DRBD8: sum(d["size"] + 128 for d in disks),
7003     constants.DT_FILE: None,
7004     constants.DT_SHARED_FILE: 0,
7005     constants.DT_BLOCK: 0,
7006   }
7007
7008   if disk_template not in req_size_dict:
7009     raise errors.ProgrammerError("Disk template '%s' size requirement"
7010                                  " is unknown" %  disk_template)
7011
7012   return req_size_dict[disk_template]
7013
7014
7015 def _FilterVmNodes(lu, nodenames):
7016   """Filters out non-vm_capable nodes from a list.
7017
7018   @type lu: L{LogicalUnit}
7019   @param lu: the logical unit for which we check
7020   @type nodenames: list
7021   @param nodenames: the list of nodes on which we should check
7022   @rtype: list
7023   @return: the list of vm-capable nodes
7024
7025   """
7026   vm_nodes = frozenset(lu.cfg.GetNonVmCapableNodeList())
7027   return [name for name in nodenames if name not in vm_nodes]
7028
7029
7030 def _CheckHVParams(lu, nodenames, hvname, hvparams):
7031   """Hypervisor parameter validation.
7032
7033   This function abstract the hypervisor parameter validation to be
7034   used in both instance create and instance modify.
7035
7036   @type lu: L{LogicalUnit}
7037   @param lu: the logical unit for which we check
7038   @type nodenames: list
7039   @param nodenames: the list of nodes on which we should check
7040   @type hvname: string
7041   @param hvname: the name of the hypervisor we should use
7042   @type hvparams: dict
7043   @param hvparams: the parameters which we need to check
7044   @raise errors.OpPrereqError: if the parameters are not valid
7045
7046   """
7047   nodenames = _FilterVmNodes(lu, nodenames)
7048   hvinfo = lu.rpc.call_hypervisor_validate_params(nodenames,
7049                                                   hvname,
7050                                                   hvparams)
7051   for node in nodenames:
7052     info = hvinfo[node]
7053     if info.offline:
7054       continue
7055     info.Raise("Hypervisor parameter validation failed on node %s" % node)
7056
7057
7058 def _CheckOSParams(lu, required, nodenames, osname, osparams):
7059   """OS parameters validation.
7060
7061   @type lu: L{LogicalUnit}
7062   @param lu: the logical unit for which we check
7063   @type required: boolean
7064   @param required: whether the validation should fail if the OS is not
7065       found
7066   @type nodenames: list
7067   @param nodenames: the list of nodes on which we should check
7068   @type osname: string
7069   @param osname: the name of the hypervisor we should use
7070   @type osparams: dict
7071   @param osparams: the parameters which we need to check
7072   @raise errors.OpPrereqError: if the parameters are not valid
7073
7074   """
7075   nodenames = _FilterVmNodes(lu, nodenames)
7076   result = lu.rpc.call_os_validate(required, nodenames, osname,
7077                                    [constants.OS_VALIDATE_PARAMETERS],
7078                                    osparams)
7079   for node, nres in result.items():
7080     # we don't check for offline cases since this should be run only
7081     # against the master node and/or an instance's nodes
7082     nres.Raise("OS Parameters validation failed on node %s" % node)
7083     if not nres.payload:
7084       lu.LogInfo("OS %s not found on node %s, validation skipped",
7085                  osname, node)
7086
7087
7088 class LUInstanceCreate(LogicalUnit):
7089   """Create an instance.
7090
7091   """
7092   HPATH = "instance-add"
7093   HTYPE = constants.HTYPE_INSTANCE
7094   REQ_BGL = False
7095
7096   def CheckArguments(self):
7097     """Check arguments.
7098
7099     """
7100     # do not require name_check to ease forward/backward compatibility
7101     # for tools
7102     if self.op.no_install and self.op.start:
7103       self.LogInfo("No-installation mode selected, disabling startup")
7104       self.op.start = False
7105     # validate/normalize the instance name
7106     self.op.instance_name = \
7107       netutils.Hostname.GetNormalizedName(self.op.instance_name)
7108
7109     if self.op.ip_check and not self.op.name_check:
7110       # TODO: make the ip check more flexible and not depend on the name check
7111       raise errors.OpPrereqError("Cannot do ip check without a name check",
7112                                  errors.ECODE_INVAL)
7113
7114     # check nics' parameter names
7115     for nic in self.op.nics:
7116       utils.ForceDictType(nic, constants.INIC_PARAMS_TYPES)
7117
7118     # check disks. parameter names and consistent adopt/no-adopt strategy
7119     has_adopt = has_no_adopt = False
7120     for disk in self.op.disks:
7121       utils.ForceDictType(disk, constants.IDISK_PARAMS_TYPES)
7122       if "adopt" in disk:
7123         has_adopt = True
7124       else:
7125         has_no_adopt = True
7126     if has_adopt and has_no_adopt:
7127       raise errors.OpPrereqError("Either all disks are adopted or none is",
7128                                  errors.ECODE_INVAL)
7129     if has_adopt:
7130       if self.op.disk_template not in constants.DTS_MAY_ADOPT:
7131         raise errors.OpPrereqError("Disk adoption is not supported for the"
7132                                    " '%s' disk template" %
7133                                    self.op.disk_template,
7134                                    errors.ECODE_INVAL)
7135       if self.op.iallocator is not None:
7136         raise errors.OpPrereqError("Disk adoption not allowed with an"
7137                                    " iallocator script", errors.ECODE_INVAL)
7138       if self.op.mode == constants.INSTANCE_IMPORT:
7139         raise errors.OpPrereqError("Disk adoption not allowed for"
7140                                    " instance import", errors.ECODE_INVAL)
7141     else:
7142       if self.op.disk_template in constants.DTS_MUST_ADOPT:
7143         raise errors.OpPrereqError("Disk template %s requires disk adoption,"
7144                                    " but no 'adopt' parameter given" %
7145                                    self.op.disk_template,
7146                                    errors.ECODE_INVAL)
7147
7148     self.adopt_disks = has_adopt
7149
7150     # instance name verification
7151     if self.op.name_check:
7152       self.hostname1 = netutils.GetHostname(name=self.op.instance_name)
7153       self.op.instance_name = self.hostname1.name
7154       # used in CheckPrereq for ip ping check
7155       self.check_ip = self.hostname1.ip
7156     else:
7157       self.check_ip = None
7158
7159     # file storage checks
7160     if (self.op.file_driver and
7161         not self.op.file_driver in constants.FILE_DRIVER):
7162       raise errors.OpPrereqError("Invalid file driver name '%s'" %
7163                                  self.op.file_driver, errors.ECODE_INVAL)
7164
7165     if self.op.file_storage_dir and os.path.isabs(self.op.file_storage_dir):
7166       raise errors.OpPrereqError("File storage directory path not absolute",
7167                                  errors.ECODE_INVAL)
7168
7169     ### Node/iallocator related checks
7170     _CheckIAllocatorOrNode(self, "iallocator", "pnode")
7171
7172     if self.op.pnode is not None:
7173       if self.op.disk_template in constants.DTS_NET_MIRROR:
7174         if self.op.snode is None:
7175           raise errors.OpPrereqError("The networked disk templates need"
7176                                      " a mirror node", errors.ECODE_INVAL)
7177       elif self.op.snode:
7178         self.LogWarning("Secondary node will be ignored on non-mirrored disk"
7179                         " template")
7180         self.op.snode = None
7181
7182     self._cds = _GetClusterDomainSecret()
7183
7184     if self.op.mode == constants.INSTANCE_IMPORT:
7185       # On import force_variant must be True, because if we forced it at
7186       # initial install, our only chance when importing it back is that it
7187       # works again!
7188       self.op.force_variant = True
7189
7190       if self.op.no_install:
7191         self.LogInfo("No-installation mode has no effect during import")
7192
7193     elif self.op.mode == constants.INSTANCE_CREATE:
7194       if self.op.os_type is None:
7195         raise errors.OpPrereqError("No guest OS specified",
7196                                    errors.ECODE_INVAL)
7197       if self.op.os_type in self.cfg.GetClusterInfo().blacklisted_os:
7198         raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
7199                                    " installation" % self.op.os_type,
7200                                    errors.ECODE_STATE)
7201       if self.op.disk_template is None:
7202         raise errors.OpPrereqError("No disk template specified",
7203                                    errors.ECODE_INVAL)
7204
7205     elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
7206       # Check handshake to ensure both clusters have the same domain secret
7207       src_handshake = self.op.source_handshake
7208       if not src_handshake:
7209         raise errors.OpPrereqError("Missing source handshake",
7210                                    errors.ECODE_INVAL)
7211
7212       errmsg = masterd.instance.CheckRemoteExportHandshake(self._cds,
7213                                                            src_handshake)
7214       if errmsg:
7215         raise errors.OpPrereqError("Invalid handshake: %s" % errmsg,
7216                                    errors.ECODE_INVAL)
7217
7218       # Load and check source CA
7219       self.source_x509_ca_pem = self.op.source_x509_ca
7220       if not self.source_x509_ca_pem:
7221         raise errors.OpPrereqError("Missing source X509 CA",
7222                                    errors.ECODE_INVAL)
7223
7224       try:
7225         (cert, _) = utils.LoadSignedX509Certificate(self.source_x509_ca_pem,
7226                                                     self._cds)
7227       except OpenSSL.crypto.Error, err:
7228         raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
7229                                    (err, ), errors.ECODE_INVAL)
7230
7231       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
7232       if errcode is not None:
7233         raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
7234                                    errors.ECODE_INVAL)
7235
7236       self.source_x509_ca = cert
7237
7238       src_instance_name = self.op.source_instance_name
7239       if not src_instance_name:
7240         raise errors.OpPrereqError("Missing source instance name",
7241                                    errors.ECODE_INVAL)
7242
7243       self.source_instance_name = \
7244           netutils.GetHostname(name=src_instance_name).name
7245
7246     else:
7247       raise errors.OpPrereqError("Invalid instance creation mode %r" %
7248                                  self.op.mode, errors.ECODE_INVAL)
7249
7250   def ExpandNames(self):
7251     """ExpandNames for CreateInstance.
7252
7253     Figure out the right locks for instance creation.
7254
7255     """
7256     self.needed_locks = {}
7257
7258     instance_name = self.op.instance_name
7259     # this is just a preventive check, but someone might still add this
7260     # instance in the meantime, and creation will fail at lock-add time
7261     if instance_name in self.cfg.GetInstanceList():
7262       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
7263                                  instance_name, errors.ECODE_EXISTS)
7264
7265     self.add_locks[locking.LEVEL_INSTANCE] = instance_name
7266
7267     if self.op.iallocator:
7268       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7269     else:
7270       self.op.pnode = _ExpandNodeName(self.cfg, self.op.pnode)
7271       nodelist = [self.op.pnode]
7272       if self.op.snode is not None:
7273         self.op.snode = _ExpandNodeName(self.cfg, self.op.snode)
7274         nodelist.append(self.op.snode)
7275       self.needed_locks[locking.LEVEL_NODE] = nodelist
7276
7277     # in case of import lock the source node too
7278     if self.op.mode == constants.INSTANCE_IMPORT:
7279       src_node = self.op.src_node
7280       src_path = self.op.src_path
7281
7282       if src_path is None:
7283         self.op.src_path = src_path = self.op.instance_name
7284
7285       if src_node is None:
7286         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7287         self.op.src_node = None
7288         if os.path.isabs(src_path):
7289           raise errors.OpPrereqError("Importing an instance from an absolute"
7290                                      " path requires a source node option.",
7291                                      errors.ECODE_INVAL)
7292       else:
7293         self.op.src_node = src_node = _ExpandNodeName(self.cfg, src_node)
7294         if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
7295           self.needed_locks[locking.LEVEL_NODE].append(src_node)
7296         if not os.path.isabs(src_path):
7297           self.op.src_path = src_path = \
7298             utils.PathJoin(constants.EXPORT_DIR, src_path)
7299
7300   def _RunAllocator(self):
7301     """Run the allocator based on input opcode.
7302
7303     """
7304     nics = [n.ToDict() for n in self.nics]
7305     ial = IAllocator(self.cfg, self.rpc,
7306                      mode=constants.IALLOCATOR_MODE_ALLOC,
7307                      name=self.op.instance_name,
7308                      disk_template=self.op.disk_template,
7309                      tags=[],
7310                      os=self.op.os_type,
7311                      vcpus=self.be_full[constants.BE_VCPUS],
7312                      mem_size=self.be_full[constants.BE_MEMORY],
7313                      disks=self.disks,
7314                      nics=nics,
7315                      hypervisor=self.op.hypervisor,
7316                      )
7317
7318     ial.Run(self.op.iallocator)
7319
7320     if not ial.success:
7321       raise errors.OpPrereqError("Can't compute nodes using"
7322                                  " iallocator '%s': %s" %
7323                                  (self.op.iallocator, ial.info),
7324                                  errors.ECODE_NORES)
7325     if len(ial.result) != ial.required_nodes:
7326       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
7327                                  " of nodes (%s), required %s" %
7328                                  (self.op.iallocator, len(ial.result),
7329                                   ial.required_nodes), errors.ECODE_FAULT)
7330     self.op.pnode = ial.result[0]
7331     self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
7332                  self.op.instance_name, self.op.iallocator,
7333                  utils.CommaJoin(ial.result))
7334     if ial.required_nodes == 2:
7335       self.op.snode = ial.result[1]
7336
7337   def BuildHooksEnv(self):
7338     """Build hooks env.
7339
7340     This runs on master, primary and secondary nodes of the instance.
7341
7342     """
7343     env = {
7344       "ADD_MODE": self.op.mode,
7345       }
7346     if self.op.mode == constants.INSTANCE_IMPORT:
7347       env["SRC_NODE"] = self.op.src_node
7348       env["SRC_PATH"] = self.op.src_path
7349       env["SRC_IMAGES"] = self.src_images
7350
7351     env.update(_BuildInstanceHookEnv(
7352       name=self.op.instance_name,
7353       primary_node=self.op.pnode,
7354       secondary_nodes=self.secondaries,
7355       status=self.op.start,
7356       os_type=self.op.os_type,
7357       memory=self.be_full[constants.BE_MEMORY],
7358       vcpus=self.be_full[constants.BE_VCPUS],
7359       nics=_NICListToTuple(self, self.nics),
7360       disk_template=self.op.disk_template,
7361       disks=[(d["size"], d["mode"]) for d in self.disks],
7362       bep=self.be_full,
7363       hvp=self.hv_full,
7364       hypervisor_name=self.op.hypervisor,
7365     ))
7366
7367     nl = ([self.cfg.GetMasterNode(), self.op.pnode] +
7368           self.secondaries)
7369     return env, nl, nl
7370
7371   def _ReadExportInfo(self):
7372     """Reads the export information from disk.
7373
7374     It will override the opcode source node and path with the actual
7375     information, if these two were not specified before.
7376
7377     @return: the export information
7378
7379     """
7380     assert self.op.mode == constants.INSTANCE_IMPORT
7381
7382     src_node = self.op.src_node
7383     src_path = self.op.src_path
7384
7385     if src_node is None:
7386       locked_nodes = self.acquired_locks[locking.LEVEL_NODE]
7387       exp_list = self.rpc.call_export_list(locked_nodes)
7388       found = False
7389       for node in exp_list:
7390         if exp_list[node].fail_msg:
7391           continue
7392         if src_path in exp_list[node].payload:
7393           found = True
7394           self.op.src_node = src_node = node
7395           self.op.src_path = src_path = utils.PathJoin(constants.EXPORT_DIR,
7396                                                        src_path)
7397           break
7398       if not found:
7399         raise errors.OpPrereqError("No export found for relative path %s" %
7400                                     src_path, errors.ECODE_INVAL)
7401
7402     _CheckNodeOnline(self, src_node)
7403     result = self.rpc.call_export_info(src_node, src_path)
7404     result.Raise("No export or invalid export found in dir %s" % src_path)
7405
7406     export_info = objects.SerializableConfigParser.Loads(str(result.payload))
7407     if not export_info.has_section(constants.INISECT_EXP):
7408       raise errors.ProgrammerError("Corrupted export config",
7409                                    errors.ECODE_ENVIRON)
7410
7411     ei_version = export_info.get(constants.INISECT_EXP, "version")
7412     if (int(ei_version) != constants.EXPORT_VERSION):
7413       raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
7414                                  (ei_version, constants.EXPORT_VERSION),
7415                                  errors.ECODE_ENVIRON)
7416     return export_info
7417
7418   def _ReadExportParams(self, einfo):
7419     """Use export parameters as defaults.
7420
7421     In case the opcode doesn't specify (as in override) some instance
7422     parameters, then try to use them from the export information, if
7423     that declares them.
7424
7425     """
7426     self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
7427
7428     if self.op.disk_template is None:
7429       if einfo.has_option(constants.INISECT_INS, "disk_template"):
7430         self.op.disk_template = einfo.get(constants.INISECT_INS,
7431                                           "disk_template")
7432       else:
7433         raise errors.OpPrereqError("No disk template specified and the export"
7434                                    " is missing the disk_template information",
7435                                    errors.ECODE_INVAL)
7436
7437     if not self.op.disks:
7438       if einfo.has_option(constants.INISECT_INS, "disk_count"):
7439         disks = []
7440         # TODO: import the disk iv_name too
7441         for idx in range(einfo.getint(constants.INISECT_INS, "disk_count")):
7442           disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
7443           disks.append({"size": disk_sz})
7444         self.op.disks = disks
7445       else:
7446         raise errors.OpPrereqError("No disk info specified and the export"
7447                                    " is missing the disk information",
7448                                    errors.ECODE_INVAL)
7449
7450     if (not self.op.nics and
7451         einfo.has_option(constants.INISECT_INS, "nic_count")):
7452       nics = []
7453       for idx in range(einfo.getint(constants.INISECT_INS, "nic_count")):
7454         ndict = {}
7455         for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
7456           v = einfo.get(constants.INISECT_INS, "nic%d_%s" % (idx, name))
7457           ndict[name] = v
7458         nics.append(ndict)
7459       self.op.nics = nics
7460
7461     if (self.op.hypervisor is None and
7462         einfo.has_option(constants.INISECT_INS, "hypervisor")):
7463       self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
7464     if einfo.has_section(constants.INISECT_HYP):
7465       # use the export parameters but do not override the ones
7466       # specified by the user
7467       for name, value in einfo.items(constants.INISECT_HYP):
7468         if name not in self.op.hvparams:
7469           self.op.hvparams[name] = value
7470
7471     if einfo.has_section(constants.INISECT_BEP):
7472       # use the parameters, without overriding
7473       for name, value in einfo.items(constants.INISECT_BEP):
7474         if name not in self.op.beparams:
7475           self.op.beparams[name] = value
7476     else:
7477       # try to read the parameters old style, from the main section
7478       for name in constants.BES_PARAMETERS:
7479         if (name not in self.op.beparams and
7480             einfo.has_option(constants.INISECT_INS, name)):
7481           self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
7482
7483     if einfo.has_section(constants.INISECT_OSP):
7484       # use the parameters, without overriding
7485       for name, value in einfo.items(constants.INISECT_OSP):
7486         if name not in self.op.osparams:
7487           self.op.osparams[name] = value
7488
7489   def _RevertToDefaults(self, cluster):
7490     """Revert the instance parameters to the default values.
7491
7492     """
7493     # hvparams
7494     hv_defs = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type, {})
7495     for name in self.op.hvparams.keys():
7496       if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
7497         del self.op.hvparams[name]
7498     # beparams
7499     be_defs = cluster.SimpleFillBE({})
7500     for name in self.op.beparams.keys():
7501       if name in be_defs and be_defs[name] == self.op.beparams[name]:
7502         del self.op.beparams[name]
7503     # nic params
7504     nic_defs = cluster.SimpleFillNIC({})
7505     for nic in self.op.nics:
7506       for name in constants.NICS_PARAMETERS:
7507         if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
7508           del nic[name]
7509     # osparams
7510     os_defs = cluster.SimpleFillOS(self.op.os_type, {})
7511     for name in self.op.osparams.keys():
7512       if name in os_defs and os_defs[name] == self.op.osparams[name]:
7513         del self.op.osparams[name]
7514
7515   def CheckPrereq(self):
7516     """Check prerequisites.
7517
7518     """
7519     if self.op.mode == constants.INSTANCE_IMPORT:
7520       export_info = self._ReadExportInfo()
7521       self._ReadExportParams(export_info)
7522
7523     if (not self.cfg.GetVGName() and
7524         self.op.disk_template not in constants.DTS_NOT_LVM):
7525       raise errors.OpPrereqError("Cluster does not support lvm-based"
7526                                  " instances", errors.ECODE_STATE)
7527
7528     if self.op.hypervisor is None:
7529       self.op.hypervisor = self.cfg.GetHypervisorType()
7530
7531     cluster = self.cfg.GetClusterInfo()
7532     enabled_hvs = cluster.enabled_hypervisors
7533     if self.op.hypervisor not in enabled_hvs:
7534       raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
7535                                  " cluster (%s)" % (self.op.hypervisor,
7536                                   ",".join(enabled_hvs)),
7537                                  errors.ECODE_STATE)
7538
7539     # check hypervisor parameter syntax (locally)
7540     utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
7541     filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type,
7542                                       self.op.hvparams)
7543     hv_type = hypervisor.GetHypervisor(self.op.hypervisor)
7544     hv_type.CheckParameterSyntax(filled_hvp)
7545     self.hv_full = filled_hvp
7546     # check that we don't specify global parameters on an instance
7547     _CheckGlobalHvParams(self.op.hvparams)
7548
7549     # fill and remember the beparams dict
7550     utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
7551     self.be_full = cluster.SimpleFillBE(self.op.beparams)
7552
7553     # build os parameters
7554     self.os_full = cluster.SimpleFillOS(self.op.os_type, self.op.osparams)
7555
7556     # now that hvp/bep are in final format, let's reset to defaults,
7557     # if told to do so
7558     if self.op.identify_defaults:
7559       self._RevertToDefaults(cluster)
7560
7561     # NIC buildup
7562     self.nics = []
7563     for idx, nic in enumerate(self.op.nics):
7564       nic_mode_req = nic.get("mode", None)
7565       nic_mode = nic_mode_req
7566       if nic_mode is None:
7567         nic_mode = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_MODE]
7568
7569       # in routed mode, for the first nic, the default ip is 'auto'
7570       if nic_mode == constants.NIC_MODE_ROUTED and idx == 0:
7571         default_ip_mode = constants.VALUE_AUTO
7572       else:
7573         default_ip_mode = constants.VALUE_NONE
7574
7575       # ip validity checks
7576       ip = nic.get("ip", default_ip_mode)
7577       if ip is None or ip.lower() == constants.VALUE_NONE:
7578         nic_ip = None
7579       elif ip.lower() == constants.VALUE_AUTO:
7580         if not self.op.name_check:
7581           raise errors.OpPrereqError("IP address set to auto but name checks"
7582                                      " have been skipped",
7583                                      errors.ECODE_INVAL)
7584         nic_ip = self.hostname1.ip
7585       else:
7586         if not netutils.IPAddress.IsValid(ip):
7587           raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
7588                                      errors.ECODE_INVAL)
7589         nic_ip = ip
7590
7591       # TODO: check the ip address for uniqueness
7592       if nic_mode == constants.NIC_MODE_ROUTED and not nic_ip:
7593         raise errors.OpPrereqError("Routed nic mode requires an ip address",
7594                                    errors.ECODE_INVAL)
7595
7596       # MAC address verification
7597       mac = nic.get("mac", constants.VALUE_AUTO)
7598       if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
7599         mac = utils.NormalizeAndValidateMac(mac)
7600
7601         try:
7602           self.cfg.ReserveMAC(mac, self.proc.GetECId())
7603         except errors.ReservationError:
7604           raise errors.OpPrereqError("MAC address %s already in use"
7605                                      " in cluster" % mac,
7606                                      errors.ECODE_NOTUNIQUE)
7607
7608       #  Build nic parameters
7609       link = nic.get(constants.INIC_LINK, None)
7610       nicparams = {}
7611       if nic_mode_req:
7612         nicparams[constants.NIC_MODE] = nic_mode_req
7613       if link:
7614         nicparams[constants.NIC_LINK] = link
7615
7616       check_params = cluster.SimpleFillNIC(nicparams)
7617       objects.NIC.CheckParameterSyntax(check_params)
7618       self.nics.append(objects.NIC(mac=mac, ip=nic_ip, nicparams=nicparams))
7619
7620     # disk checks/pre-build
7621     self.disks = []
7622     for disk in self.op.disks:
7623       mode = disk.get("mode", constants.DISK_RDWR)
7624       if mode not in constants.DISK_ACCESS_SET:
7625         raise errors.OpPrereqError("Invalid disk access mode '%s'" %
7626                                    mode, errors.ECODE_INVAL)
7627       size = disk.get("size", None)
7628       if size is None:
7629         raise errors.OpPrereqError("Missing disk size", errors.ECODE_INVAL)
7630       try:
7631         size = int(size)
7632       except (TypeError, ValueError):
7633         raise errors.OpPrereqError("Invalid disk size '%s'" % size,
7634                                    errors.ECODE_INVAL)
7635       vg = disk.get("vg", self.cfg.GetVGName())
7636       new_disk = {"size": size, "mode": mode, "vg": vg}
7637       if "adopt" in disk:
7638         new_disk["adopt"] = disk["adopt"]
7639       self.disks.append(new_disk)
7640
7641     if self.op.mode == constants.INSTANCE_IMPORT:
7642
7643       # Check that the new instance doesn't have less disks than the export
7644       instance_disks = len(self.disks)
7645       export_disks = export_info.getint(constants.INISECT_INS, 'disk_count')
7646       if instance_disks < export_disks:
7647         raise errors.OpPrereqError("Not enough disks to import."
7648                                    " (instance: %d, export: %d)" %
7649                                    (instance_disks, export_disks),
7650                                    errors.ECODE_INVAL)
7651
7652       disk_images = []
7653       for idx in range(export_disks):
7654         option = 'disk%d_dump' % idx
7655         if export_info.has_option(constants.INISECT_INS, option):
7656           # FIXME: are the old os-es, disk sizes, etc. useful?
7657           export_name = export_info.get(constants.INISECT_INS, option)
7658           image = utils.PathJoin(self.op.src_path, export_name)
7659           disk_images.append(image)
7660         else:
7661           disk_images.append(False)
7662
7663       self.src_images = disk_images
7664
7665       old_name = export_info.get(constants.INISECT_INS, 'name')
7666       try:
7667         exp_nic_count = export_info.getint(constants.INISECT_INS, 'nic_count')
7668       except (TypeError, ValueError), err:
7669         raise errors.OpPrereqError("Invalid export file, nic_count is not"
7670                                    " an integer: %s" % str(err),
7671                                    errors.ECODE_STATE)
7672       if self.op.instance_name == old_name:
7673         for idx, nic in enumerate(self.nics):
7674           if nic.mac == constants.VALUE_AUTO and exp_nic_count >= idx:
7675             nic_mac_ini = 'nic%d_mac' % idx
7676             nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
7677
7678     # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
7679
7680     # ip ping checks (we use the same ip that was resolved in ExpandNames)
7681     if self.op.ip_check:
7682       if netutils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
7683         raise errors.OpPrereqError("IP %s of instance %s already in use" %
7684                                    (self.check_ip, self.op.instance_name),
7685                                    errors.ECODE_NOTUNIQUE)
7686
7687     #### mac address generation
7688     # By generating here the mac address both the allocator and the hooks get
7689     # the real final mac address rather than the 'auto' or 'generate' value.
7690     # There is a race condition between the generation and the instance object
7691     # creation, which means that we know the mac is valid now, but we're not
7692     # sure it will be when we actually add the instance. If things go bad
7693     # adding the instance will abort because of a duplicate mac, and the
7694     # creation job will fail.
7695     for nic in self.nics:
7696       if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
7697         nic.mac = self.cfg.GenerateMAC(self.proc.GetECId())
7698
7699     #### allocator run
7700
7701     if self.op.iallocator is not None:
7702       self._RunAllocator()
7703
7704     #### node related checks
7705
7706     # check primary node
7707     self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode)
7708     assert self.pnode is not None, \
7709       "Cannot retrieve locked node %s" % self.op.pnode
7710     if pnode.offline:
7711       raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
7712                                  pnode.name, errors.ECODE_STATE)
7713     if pnode.drained:
7714       raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
7715                                  pnode.name, errors.ECODE_STATE)
7716     if not pnode.vm_capable:
7717       raise errors.OpPrereqError("Cannot use non-vm_capable primary node"
7718                                  " '%s'" % pnode.name, errors.ECODE_STATE)
7719
7720     self.secondaries = []
7721
7722     # mirror node verification
7723     if self.op.disk_template in constants.DTS_NET_MIRROR:
7724       if self.op.snode == pnode.name:
7725         raise errors.OpPrereqError("The secondary node cannot be the"
7726                                    " primary node.", errors.ECODE_INVAL)
7727       _CheckNodeOnline(self, self.op.snode)
7728       _CheckNodeNotDrained(self, self.op.snode)
7729       _CheckNodeVmCapable(self, self.op.snode)
7730       self.secondaries.append(self.op.snode)
7731
7732     nodenames = [pnode.name] + self.secondaries
7733
7734     if not self.adopt_disks:
7735       # Check lv size requirements, if not adopting
7736       req_sizes = _ComputeDiskSizePerVG(self.op.disk_template, self.disks)
7737       _CheckNodesFreeDiskPerVG(self, nodenames, req_sizes)
7738
7739     elif self.op.disk_template == constants.DT_PLAIN: # Check the adoption data
7740       all_lvs = set([i["vg"] + "/" + i["adopt"] for i in self.disks])
7741       if len(all_lvs) != len(self.disks):
7742         raise errors.OpPrereqError("Duplicate volume names given for adoption",
7743                                    errors.ECODE_INVAL)
7744       for lv_name in all_lvs:
7745         try:
7746           # FIXME: lv_name here is "vg/lv" need to ensure that other calls
7747           # to ReserveLV uses the same syntax
7748           self.cfg.ReserveLV(lv_name, self.proc.GetECId())
7749         except errors.ReservationError:
7750           raise errors.OpPrereqError("LV named %s used by another instance" %
7751                                      lv_name, errors.ECODE_NOTUNIQUE)
7752
7753       vg_names = self.rpc.call_vg_list([pnode.name])[pnode.name]
7754       vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
7755
7756       node_lvs = self.rpc.call_lv_list([pnode.name],
7757                                        vg_names.payload.keys())[pnode.name]
7758       node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
7759       node_lvs = node_lvs.payload
7760
7761       delta = all_lvs.difference(node_lvs.keys())
7762       if delta:
7763         raise errors.OpPrereqError("Missing logical volume(s): %s" %
7764                                    utils.CommaJoin(delta),
7765                                    errors.ECODE_INVAL)
7766       online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
7767       if online_lvs:
7768         raise errors.OpPrereqError("Online logical volumes found, cannot"
7769                                    " adopt: %s" % utils.CommaJoin(online_lvs),
7770                                    errors.ECODE_STATE)
7771       # update the size of disk based on what is found
7772       for dsk in self.disks:
7773         dsk["size"] = int(float(node_lvs[dsk["vg"] + "/" + dsk["adopt"]][0]))
7774
7775     elif self.op.disk_template == constants.DT_BLOCK:
7776       # Normalize and de-duplicate device paths
7777       all_disks = set([os.path.abspath(i["adopt"]) for i in self.disks])
7778       if len(all_disks) != len(self.disks):
7779         raise errors.OpPrereqError("Duplicate disk names given for adoption",
7780                                    errors.ECODE_INVAL)
7781       baddisks = [d for d in all_disks
7782                   if not d.startswith(constants.ADOPTABLE_BLOCKDEV_ROOT)]
7783       if baddisks:
7784         raise errors.OpPrereqError("Device node(s) %s lie outside %s and"
7785                                    " cannot be adopted" %
7786                                    (", ".join(baddisks),
7787                                     constants.ADOPTABLE_BLOCKDEV_ROOT),
7788                                    errors.ECODE_INVAL)
7789
7790       node_disks = self.rpc.call_bdev_sizes([pnode.name],
7791                                             list(all_disks))[pnode.name]
7792       node_disks.Raise("Cannot get block device information from node %s" %
7793                        pnode.name)
7794       node_disks = node_disks.payload
7795       delta = all_disks.difference(node_disks.keys())
7796       if delta:
7797         raise errors.OpPrereqError("Missing block device(s): %s" %
7798                                    utils.CommaJoin(delta),
7799                                    errors.ECODE_INVAL)
7800       for dsk in self.disks:
7801         dsk["size"] = int(float(node_disks[dsk["adopt"]]))
7802
7803     _CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
7804
7805     _CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant)
7806     # check OS parameters (remotely)
7807     _CheckOSParams(self, True, nodenames, self.op.os_type, self.os_full)
7808
7809     _CheckNicsBridgesExist(self, self.nics, self.pnode.name)
7810
7811     # memory check on primary node
7812     if self.op.start:
7813       _CheckNodeFreeMemory(self, self.pnode.name,
7814                            "creating instance %s" % self.op.instance_name,
7815                            self.be_full[constants.BE_MEMORY],
7816                            self.op.hypervisor)
7817
7818     self.dry_run_result = list(nodenames)
7819
7820   def Exec(self, feedback_fn):
7821     """Create and add the instance to the cluster.
7822
7823     """
7824     instance = self.op.instance_name
7825     pnode_name = self.pnode.name
7826
7827     ht_kind = self.op.hypervisor
7828     if ht_kind in constants.HTS_REQ_PORT:
7829       network_port = self.cfg.AllocatePort()
7830     else:
7831       network_port = None
7832
7833     if constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE:
7834       # this is needed because os.path.join does not accept None arguments
7835       if self.op.file_storage_dir is None:
7836         string_file_storage_dir = ""
7837       else:
7838         string_file_storage_dir = self.op.file_storage_dir
7839
7840       # build the full file storage dir path
7841       if self.op.disk_template == constants.DT_SHARED_FILE:
7842         get_fsd_fn = self.cfg.GetSharedFileStorageDir
7843       else:
7844         get_fsd_fn = self.cfg.GetFileStorageDir
7845
7846       file_storage_dir = utils.PathJoin(get_fsd_fn(),
7847                                         string_file_storage_dir, instance)
7848     else:
7849       file_storage_dir = ""
7850
7851     disks = _GenerateDiskTemplate(self,
7852                                   self.op.disk_template,
7853                                   instance, pnode_name,
7854                                   self.secondaries,
7855                                   self.disks,
7856                                   file_storage_dir,
7857                                   self.op.file_driver,
7858                                   0,
7859                                   feedback_fn)
7860
7861     iobj = objects.Instance(name=instance, os=self.op.os_type,
7862                             primary_node=pnode_name,
7863                             nics=self.nics, disks=disks,
7864                             disk_template=self.op.disk_template,
7865                             admin_up=False,
7866                             network_port=network_port,
7867                             beparams=self.op.beparams,
7868                             hvparams=self.op.hvparams,
7869                             hypervisor=self.op.hypervisor,
7870                             osparams=self.op.osparams,
7871                             )
7872
7873     if self.adopt_disks:
7874       if self.op.disk_template == constants.DT_PLAIN:
7875         # rename LVs to the newly-generated names; we need to construct
7876         # 'fake' LV disks with the old data, plus the new unique_id
7877         tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
7878         rename_to = []
7879         for t_dsk, a_dsk in zip (tmp_disks, self.disks):
7880           rename_to.append(t_dsk.logical_id)
7881           t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk["adopt"])
7882           self.cfg.SetDiskID(t_dsk, pnode_name)
7883         result = self.rpc.call_blockdev_rename(pnode_name,
7884                                                zip(tmp_disks, rename_to))
7885         result.Raise("Failed to rename adoped LVs")
7886     else:
7887       feedback_fn("* creating instance disks...")
7888       try:
7889         _CreateDisks(self, iobj)
7890       except errors.OpExecError:
7891         self.LogWarning("Device creation failed, reverting...")
7892         try:
7893           _RemoveDisks(self, iobj)
7894         finally:
7895           self.cfg.ReleaseDRBDMinors(instance)
7896           raise
7897
7898       if self.cfg.GetClusterInfo().prealloc_wipe_disks:
7899         feedback_fn("* wiping instance disks...")
7900         try:
7901           _WipeDisks(self, iobj)
7902         except errors.OpExecError:
7903           self.LogWarning("Device wiping failed, reverting...")
7904           try:
7905             _RemoveDisks(self, iobj)
7906           finally:
7907             self.cfg.ReleaseDRBDMinors(instance)
7908             raise
7909
7910     feedback_fn("adding instance %s to cluster config" % instance)
7911
7912     self.cfg.AddInstance(iobj, self.proc.GetECId())
7913
7914     # Declare that we don't want to remove the instance lock anymore, as we've
7915     # added the instance to the config
7916     del self.remove_locks[locking.LEVEL_INSTANCE]
7917     # Unlock all the nodes
7918     if self.op.mode == constants.INSTANCE_IMPORT:
7919       nodes_keep = [self.op.src_node]
7920       nodes_release = [node for node in self.acquired_locks[locking.LEVEL_NODE]
7921                        if node != self.op.src_node]
7922       self.context.glm.release(locking.LEVEL_NODE, nodes_release)
7923       self.acquired_locks[locking.LEVEL_NODE] = nodes_keep
7924     else:
7925       self.context.glm.release(locking.LEVEL_NODE)
7926       del self.acquired_locks[locking.LEVEL_NODE]
7927
7928     if self.op.wait_for_sync:
7929       disk_abort = not _WaitForSync(self, iobj)
7930     elif iobj.disk_template in constants.DTS_NET_MIRROR:
7931       # make sure the disks are not degraded (still sync-ing is ok)
7932       time.sleep(15)
7933       feedback_fn("* checking mirrors status")
7934       disk_abort = not _WaitForSync(self, iobj, oneshot=True)
7935     else:
7936       disk_abort = False
7937
7938     if disk_abort:
7939       _RemoveDisks(self, iobj)
7940       self.cfg.RemoveInstance(iobj.name)
7941       # Make sure the instance lock gets removed
7942       self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
7943       raise errors.OpExecError("There are some degraded disks for"
7944                                " this instance")
7945
7946     if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
7947       if self.op.mode == constants.INSTANCE_CREATE:
7948         if not self.op.no_install:
7949           feedback_fn("* running the instance OS create scripts...")
7950           # FIXME: pass debug option from opcode to backend
7951           result = self.rpc.call_instance_os_add(pnode_name, iobj, False,
7952                                                  self.op.debug_level)
7953           result.Raise("Could not add os for instance %s"
7954                        " on node %s" % (instance, pnode_name))
7955
7956       elif self.op.mode == constants.INSTANCE_IMPORT:
7957         feedback_fn("* running the instance OS import scripts...")
7958
7959         transfers = []
7960
7961         for idx, image in enumerate(self.src_images):
7962           if not image:
7963             continue
7964
7965           # FIXME: pass debug option from opcode to backend
7966           dt = masterd.instance.DiskTransfer("disk/%s" % idx,
7967                                              constants.IEIO_FILE, (image, ),
7968                                              constants.IEIO_SCRIPT,
7969                                              (iobj.disks[idx], idx),
7970                                              None)
7971           transfers.append(dt)
7972
7973         import_result = \
7974           masterd.instance.TransferInstanceData(self, feedback_fn,
7975                                                 self.op.src_node, pnode_name,
7976                                                 self.pnode.secondary_ip,
7977                                                 iobj, transfers)
7978         if not compat.all(import_result):
7979           self.LogWarning("Some disks for instance %s on node %s were not"
7980                           " imported successfully" % (instance, pnode_name))
7981
7982       elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
7983         feedback_fn("* preparing remote import...")
7984         # The source cluster will stop the instance before attempting to make a
7985         # connection. In some cases stopping an instance can take a long time,
7986         # hence the shutdown timeout is added to the connection timeout.
7987         connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
7988                            self.op.source_shutdown_timeout)
7989         timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
7990
7991         assert iobj.primary_node == self.pnode.name
7992         disk_results = \
7993           masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
7994                                         self.source_x509_ca,
7995                                         self._cds, timeouts)
7996         if not compat.all(disk_results):
7997           # TODO: Should the instance still be started, even if some disks
7998           # failed to import (valid for local imports, too)?
7999           self.LogWarning("Some disks for instance %s on node %s were not"
8000                           " imported successfully" % (instance, pnode_name))
8001
8002         # Run rename script on newly imported instance
8003         assert iobj.name == instance
8004         feedback_fn("Running rename script for %s" % instance)
8005         result = self.rpc.call_instance_run_rename(pnode_name, iobj,
8006                                                    self.source_instance_name,
8007                                                    self.op.debug_level)
8008         if result.fail_msg:
8009           self.LogWarning("Failed to run rename script for %s on node"
8010                           " %s: %s" % (instance, pnode_name, result.fail_msg))
8011
8012       else:
8013         # also checked in the prereq part
8014         raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
8015                                      % self.op.mode)
8016
8017     if self.op.start:
8018       iobj.admin_up = True
8019       self.cfg.Update(iobj, feedback_fn)
8020       logging.info("Starting instance %s on node %s", instance, pnode_name)
8021       feedback_fn("* starting instance...")
8022       result = self.rpc.call_instance_start(pnode_name, iobj, None, None)
8023       result.Raise("Could not start instance")
8024
8025     return list(iobj.all_nodes)
8026
8027
8028 class LUInstanceConsole(NoHooksLU):
8029   """Connect to an instance's console.
8030
8031   This is somewhat special in that it returns the command line that
8032   you need to run on the master node in order to connect to the
8033   console.
8034
8035   """
8036   REQ_BGL = False
8037
8038   def ExpandNames(self):
8039     self._ExpandAndLockInstance()
8040
8041   def CheckPrereq(self):
8042     """Check prerequisites.
8043
8044     This checks that the instance is in the cluster.
8045
8046     """
8047     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
8048     assert self.instance is not None, \
8049       "Cannot retrieve locked instance %s" % self.op.instance_name
8050     _CheckNodeOnline(self, self.instance.primary_node)
8051
8052   def Exec(self, feedback_fn):
8053     """Connect to the console of an instance
8054
8055     """
8056     instance = self.instance
8057     node = instance.primary_node
8058
8059     node_insts = self.rpc.call_instance_list([node],
8060                                              [instance.hypervisor])[node]
8061     node_insts.Raise("Can't get node information from %s" % node)
8062
8063     if instance.name not in node_insts.payload:
8064       if instance.admin_up:
8065         state = constants.INSTST_ERRORDOWN
8066       else:
8067         state = constants.INSTST_ADMINDOWN
8068       raise errors.OpExecError("Instance %s is not running (state %s)" %
8069                                (instance.name, state))
8070
8071     logging.debug("Connecting to console of %s on %s", instance.name, node)
8072
8073     return _GetInstanceConsole(self.cfg.GetClusterInfo(), instance)
8074
8075
8076 def _GetInstanceConsole(cluster, instance):
8077   """Returns console information for an instance.
8078
8079   @type cluster: L{objects.Cluster}
8080   @type instance: L{objects.Instance}
8081   @rtype: dict
8082
8083   """
8084   hyper = hypervisor.GetHypervisor(instance.hypervisor)
8085   # beparams and hvparams are passed separately, to avoid editing the
8086   # instance and then saving the defaults in the instance itself.
8087   hvparams = cluster.FillHV(instance)
8088   beparams = cluster.FillBE(instance)
8089   console = hyper.GetInstanceConsole(instance, hvparams, beparams)
8090
8091   assert console.instance == instance.name
8092   assert console.Validate()
8093
8094   return console.ToDict()
8095
8096
8097 class LUInstanceReplaceDisks(LogicalUnit):
8098   """Replace the disks of an instance.
8099
8100   """
8101   HPATH = "mirrors-replace"
8102   HTYPE = constants.HTYPE_INSTANCE
8103   REQ_BGL = False
8104
8105   def CheckArguments(self):
8106     TLReplaceDisks.CheckArguments(self.op.mode, self.op.remote_node,
8107                                   self.op.iallocator)
8108
8109   def ExpandNames(self):
8110     self._ExpandAndLockInstance()
8111
8112     if self.op.iallocator is not None:
8113       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
8114
8115     elif self.op.remote_node is not None:
8116       remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
8117       self.op.remote_node = remote_node
8118
8119       # Warning: do not remove the locking of the new secondary here
8120       # unless DRBD8.AddChildren is changed to work in parallel;
8121       # currently it doesn't since parallel invocations of
8122       # FindUnusedMinor will conflict
8123       self.needed_locks[locking.LEVEL_NODE] = [remote_node]
8124       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
8125
8126     else:
8127       self.needed_locks[locking.LEVEL_NODE] = []
8128       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
8129
8130     self.replacer = TLReplaceDisks(self, self.op.instance_name, self.op.mode,
8131                                    self.op.iallocator, self.op.remote_node,
8132                                    self.op.disks, False, self.op.early_release)
8133
8134     self.tasklets = [self.replacer]
8135
8136   def DeclareLocks(self, level):
8137     # If we're not already locking all nodes in the set we have to declare the
8138     # instance's primary/secondary nodes.
8139     if (level == locking.LEVEL_NODE and
8140         self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET):
8141       self._LockInstancesNodes()
8142
8143   def BuildHooksEnv(self):
8144     """Build hooks env.
8145
8146     This runs on the master, the primary and all the secondaries.
8147
8148     """
8149     instance = self.replacer.instance
8150     env = {
8151       "MODE": self.op.mode,
8152       "NEW_SECONDARY": self.op.remote_node,
8153       "OLD_SECONDARY": instance.secondary_nodes[0],
8154       }
8155     env.update(_BuildInstanceHookEnvByObject(self, instance))
8156     nl = [
8157       self.cfg.GetMasterNode(),
8158       instance.primary_node,
8159       ]
8160     if self.op.remote_node is not None:
8161       nl.append(self.op.remote_node)
8162     return env, nl, nl
8163
8164
8165 class TLReplaceDisks(Tasklet):
8166   """Replaces disks for an instance.
8167
8168   Note: Locking is not within the scope of this class.
8169
8170   """
8171   def __init__(self, lu, instance_name, mode, iallocator_name, remote_node,
8172                disks, delay_iallocator, early_release):
8173     """Initializes this class.
8174
8175     """
8176     Tasklet.__init__(self, lu)
8177
8178     # Parameters
8179     self.instance_name = instance_name
8180     self.mode = mode
8181     self.iallocator_name = iallocator_name
8182     self.remote_node = remote_node
8183     self.disks = disks
8184     self.delay_iallocator = delay_iallocator
8185     self.early_release = early_release
8186
8187     # Runtime data
8188     self.instance = None
8189     self.new_node = None
8190     self.target_node = None
8191     self.other_node = None
8192     self.remote_node_info = None
8193     self.node_secondary_ip = None
8194
8195   @staticmethod
8196   def CheckArguments(mode, remote_node, iallocator):
8197     """Helper function for users of this class.
8198
8199     """
8200     # check for valid parameter combination
8201     if mode == constants.REPLACE_DISK_CHG:
8202       if remote_node is None and iallocator is None:
8203         raise errors.OpPrereqError("When changing the secondary either an"
8204                                    " iallocator script must be used or the"
8205                                    " new node given", errors.ECODE_INVAL)
8206
8207       if remote_node is not None and iallocator is not None:
8208         raise errors.OpPrereqError("Give either the iallocator or the new"
8209                                    " secondary, not both", errors.ECODE_INVAL)
8210
8211     elif remote_node is not None or iallocator is not None:
8212       # Not replacing the secondary
8213       raise errors.OpPrereqError("The iallocator and new node options can"
8214                                  " only be used when changing the"
8215                                  " secondary node", errors.ECODE_INVAL)
8216
8217   @staticmethod
8218   def _RunAllocator(lu, iallocator_name, instance_name, relocate_from):
8219     """Compute a new secondary node using an IAllocator.
8220
8221     """
8222     ial = IAllocator(lu.cfg, lu.rpc,
8223                      mode=constants.IALLOCATOR_MODE_RELOC,
8224                      name=instance_name,
8225                      relocate_from=relocate_from)
8226
8227     ial.Run(iallocator_name)
8228
8229     if not ial.success:
8230       raise errors.OpPrereqError("Can't compute nodes using iallocator '%s':"
8231                                  " %s" % (iallocator_name, ial.info),
8232                                  errors.ECODE_NORES)
8233
8234     if len(ial.result) != ial.required_nodes:
8235       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
8236                                  " of nodes (%s), required %s" %
8237                                  (iallocator_name,
8238                                   len(ial.result), ial.required_nodes),
8239                                  errors.ECODE_FAULT)
8240
8241     remote_node_name = ial.result[0]
8242
8243     lu.LogInfo("Selected new secondary for instance '%s': %s",
8244                instance_name, remote_node_name)
8245
8246     return remote_node_name
8247
8248   def _FindFaultyDisks(self, node_name):
8249     return _FindFaultyInstanceDisks(self.cfg, self.rpc, self.instance,
8250                                     node_name, True)
8251
8252   def CheckPrereq(self):
8253     """Check prerequisites.
8254
8255     This checks that the instance is in the cluster.
8256
8257     """
8258     self.instance = instance = self.cfg.GetInstanceInfo(self.instance_name)
8259     assert instance is not None, \
8260       "Cannot retrieve locked instance %s" % self.instance_name
8261
8262     if instance.disk_template != constants.DT_DRBD8:
8263       raise errors.OpPrereqError("Can only run replace disks for DRBD8-based"
8264                                  " instances", errors.ECODE_INVAL)
8265
8266     if len(instance.secondary_nodes) != 1:
8267       raise errors.OpPrereqError("The instance has a strange layout,"
8268                                  " expected one secondary but found %d" %
8269                                  len(instance.secondary_nodes),
8270                                  errors.ECODE_FAULT)
8271
8272     if not self.delay_iallocator:
8273       self._CheckPrereq2()
8274
8275   def _CheckPrereq2(self):
8276     """Check prerequisites, second part.
8277
8278     This function should always be part of CheckPrereq. It was separated and is
8279     now called from Exec because during node evacuation iallocator was only
8280     called with an unmodified cluster model, not taking planned changes into
8281     account.
8282
8283     """
8284     instance = self.instance
8285     secondary_node = instance.secondary_nodes[0]
8286
8287     if self.iallocator_name is None:
8288       remote_node = self.remote_node
8289     else:
8290       remote_node = self._RunAllocator(self.lu, self.iallocator_name,
8291                                        instance.name, instance.secondary_nodes)
8292
8293     if remote_node is not None:
8294       self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
8295       assert self.remote_node_info is not None, \
8296         "Cannot retrieve locked node %s" % remote_node
8297     else:
8298       self.remote_node_info = None
8299
8300     if remote_node == self.instance.primary_node:
8301       raise errors.OpPrereqError("The specified node is the primary node of"
8302                                  " the instance.", errors.ECODE_INVAL)
8303
8304     if remote_node == secondary_node:
8305       raise errors.OpPrereqError("The specified node is already the"
8306                                  " secondary node of the instance.",
8307                                  errors.ECODE_INVAL)
8308
8309     if self.disks and self.mode in (constants.REPLACE_DISK_AUTO,
8310                                     constants.REPLACE_DISK_CHG):
8311       raise errors.OpPrereqError("Cannot specify disks to be replaced",
8312                                  errors.ECODE_INVAL)
8313
8314     if self.mode == constants.REPLACE_DISK_AUTO:
8315       faulty_primary = self._FindFaultyDisks(instance.primary_node)
8316       faulty_secondary = self._FindFaultyDisks(secondary_node)
8317
8318       if faulty_primary and faulty_secondary:
8319         raise errors.OpPrereqError("Instance %s has faulty disks on more than"
8320                                    " one node and can not be repaired"
8321                                    " automatically" % self.instance_name,
8322                                    errors.ECODE_STATE)
8323
8324       if faulty_primary:
8325         self.disks = faulty_primary
8326         self.target_node = instance.primary_node
8327         self.other_node = secondary_node
8328         check_nodes = [self.target_node, self.other_node]
8329       elif faulty_secondary:
8330         self.disks = faulty_secondary
8331         self.target_node = secondary_node
8332         self.other_node = instance.primary_node
8333         check_nodes = [self.target_node, self.other_node]
8334       else:
8335         self.disks = []
8336         check_nodes = []
8337
8338     else:
8339       # Non-automatic modes
8340       if self.mode == constants.REPLACE_DISK_PRI:
8341         self.target_node = instance.primary_node
8342         self.other_node = secondary_node
8343         check_nodes = [self.target_node, self.other_node]
8344
8345       elif self.mode == constants.REPLACE_DISK_SEC:
8346         self.target_node = secondary_node
8347         self.other_node = instance.primary_node
8348         check_nodes = [self.target_node, self.other_node]
8349
8350       elif self.mode == constants.REPLACE_DISK_CHG:
8351         self.new_node = remote_node
8352         self.other_node = instance.primary_node
8353         self.target_node = secondary_node
8354         check_nodes = [self.new_node, self.other_node]
8355
8356         _CheckNodeNotDrained(self.lu, remote_node)
8357         _CheckNodeVmCapable(self.lu, remote_node)
8358
8359         old_node_info = self.cfg.GetNodeInfo(secondary_node)
8360         assert old_node_info is not None
8361         if old_node_info.offline and not self.early_release:
8362           # doesn't make sense to delay the release
8363           self.early_release = True
8364           self.lu.LogInfo("Old secondary %s is offline, automatically enabling"
8365                           " early-release mode", secondary_node)
8366
8367       else:
8368         raise errors.ProgrammerError("Unhandled disk replace mode (%s)" %
8369                                      self.mode)
8370
8371       # If not specified all disks should be replaced
8372       if not self.disks:
8373         self.disks = range(len(self.instance.disks))
8374
8375     for node in check_nodes:
8376       _CheckNodeOnline(self.lu, node)
8377
8378     # Check whether disks are valid
8379     for disk_idx in self.disks:
8380       instance.FindDisk(disk_idx)
8381
8382     # Get secondary node IP addresses
8383     node_2nd_ip = {}
8384
8385     for node_name in [self.target_node, self.other_node, self.new_node]:
8386       if node_name is not None:
8387         node_2nd_ip[node_name] = self.cfg.GetNodeInfo(node_name).secondary_ip
8388
8389     self.node_secondary_ip = node_2nd_ip
8390
8391   def Exec(self, feedback_fn):
8392     """Execute disk replacement.
8393
8394     This dispatches the disk replacement to the appropriate handler.
8395
8396     """
8397     if self.delay_iallocator:
8398       self._CheckPrereq2()
8399
8400     if not self.disks:
8401       feedback_fn("No disks need replacement")
8402       return
8403
8404     feedback_fn("Replacing disk(s) %s for %s" %
8405                 (utils.CommaJoin(self.disks), self.instance.name))
8406
8407     activate_disks = (not self.instance.admin_up)
8408
8409     # Activate the instance disks if we're replacing them on a down instance
8410     if activate_disks:
8411       _StartInstanceDisks(self.lu, self.instance, True)
8412
8413     try:
8414       # Should we replace the secondary node?
8415       if self.new_node is not None:
8416         fn = self._ExecDrbd8Secondary
8417       else:
8418         fn = self._ExecDrbd8DiskOnly
8419
8420       return fn(feedback_fn)
8421
8422     finally:
8423       # Deactivate the instance disks if we're replacing them on a
8424       # down instance
8425       if activate_disks:
8426         _SafeShutdownInstanceDisks(self.lu, self.instance)
8427
8428   def _CheckVolumeGroup(self, nodes):
8429     self.lu.LogInfo("Checking volume groups")
8430
8431     vgname = self.cfg.GetVGName()
8432
8433     # Make sure volume group exists on all involved nodes
8434     results = self.rpc.call_vg_list(nodes)
8435     if not results:
8436       raise errors.OpExecError("Can't list volume groups on the nodes")
8437
8438     for node in nodes:
8439       res = results[node]
8440       res.Raise("Error checking node %s" % node)
8441       if vgname not in res.payload:
8442         raise errors.OpExecError("Volume group '%s' not found on node %s" %
8443                                  (vgname, node))
8444
8445   def _CheckDisksExistence(self, nodes):
8446     # Check disk existence
8447     for idx, dev in enumerate(self.instance.disks):
8448       if idx not in self.disks:
8449         continue
8450
8451       for node in nodes:
8452         self.lu.LogInfo("Checking disk/%d on %s" % (idx, node))
8453         self.cfg.SetDiskID(dev, node)
8454
8455         result = self.rpc.call_blockdev_find(node, dev)
8456
8457         msg = result.fail_msg
8458         if msg or not result.payload:
8459           if not msg:
8460             msg = "disk not found"
8461           raise errors.OpExecError("Can't find disk/%d on node %s: %s" %
8462                                    (idx, node, msg))
8463
8464   def _CheckDisksConsistency(self, node_name, on_primary, ldisk):
8465     for idx, dev in enumerate(self.instance.disks):
8466       if idx not in self.disks:
8467         continue
8468
8469       self.lu.LogInfo("Checking disk/%d consistency on node %s" %
8470                       (idx, node_name))
8471
8472       if not _CheckDiskConsistency(self.lu, dev, node_name, on_primary,
8473                                    ldisk=ldisk):
8474         raise errors.OpExecError("Node %s has degraded storage, unsafe to"
8475                                  " replace disks for instance %s" %
8476                                  (node_name, self.instance.name))
8477
8478   def _CreateNewStorage(self, node_name):
8479     vgname = self.cfg.GetVGName()
8480     iv_names = {}
8481
8482     for idx, dev in enumerate(self.instance.disks):
8483       if idx not in self.disks:
8484         continue
8485
8486       self.lu.LogInfo("Adding storage on %s for disk/%d" % (node_name, idx))
8487
8488       self.cfg.SetDiskID(dev, node_name)
8489
8490       lv_names = [".disk%d_%s" % (idx, suffix) for suffix in ["data", "meta"]]
8491       names = _GenerateUniqueNames(self.lu, lv_names)
8492
8493       lv_data = objects.Disk(dev_type=constants.LD_LV, size=dev.size,
8494                              logical_id=(vgname, names[0]))
8495       lv_meta = objects.Disk(dev_type=constants.LD_LV, size=128,
8496                              logical_id=(vgname, names[1]))
8497
8498       new_lvs = [lv_data, lv_meta]
8499       old_lvs = dev.children
8500       iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
8501
8502       # we pass force_create=True to force the LVM creation
8503       for new_lv in new_lvs:
8504         _CreateBlockDev(self.lu, node_name, self.instance, new_lv, True,
8505                         _GetInstanceInfoText(self.instance), False)
8506
8507     return iv_names
8508
8509   def _CheckDevices(self, node_name, iv_names):
8510     for name, (dev, _, _) in iv_names.iteritems():
8511       self.cfg.SetDiskID(dev, node_name)
8512
8513       result = self.rpc.call_blockdev_find(node_name, dev)
8514
8515       msg = result.fail_msg
8516       if msg or not result.payload:
8517         if not msg:
8518           msg = "disk not found"
8519         raise errors.OpExecError("Can't find DRBD device %s: %s" %
8520                                  (name, msg))
8521
8522       if result.payload.is_degraded:
8523         raise errors.OpExecError("DRBD device %s is degraded!" % name)
8524
8525   def _RemoveOldStorage(self, node_name, iv_names):
8526     for name, (_, old_lvs, _) in iv_names.iteritems():
8527       self.lu.LogInfo("Remove logical volumes for %s" % name)
8528
8529       for lv in old_lvs:
8530         self.cfg.SetDiskID(lv, node_name)
8531
8532         msg = self.rpc.call_blockdev_remove(node_name, lv).fail_msg
8533         if msg:
8534           self.lu.LogWarning("Can't remove old LV: %s" % msg,
8535                              hint="remove unused LVs manually")
8536
8537   def _ReleaseNodeLock(self, node_name):
8538     """Releases the lock for a given node."""
8539     self.lu.context.glm.release(locking.LEVEL_NODE, node_name)
8540
8541   def _ExecDrbd8DiskOnly(self, feedback_fn):
8542     """Replace a disk on the primary or secondary for DRBD 8.
8543
8544     The algorithm for replace is quite complicated:
8545
8546       1. for each disk to be replaced:
8547
8548         1. create new LVs on the target node with unique names
8549         1. detach old LVs from the drbd device
8550         1. rename old LVs to name_replaced.<time_t>
8551         1. rename new LVs to old LVs
8552         1. attach the new LVs (with the old names now) to the drbd device
8553
8554       1. wait for sync across all devices
8555
8556       1. for each modified disk:
8557
8558         1. remove old LVs (which have the name name_replaces.<time_t>)
8559
8560     Failures are not very well handled.
8561
8562     """
8563     steps_total = 6
8564
8565     # Step: check device activation
8566     self.lu.LogStep(1, steps_total, "Check device existence")
8567     self._CheckDisksExistence([self.other_node, self.target_node])
8568     self._CheckVolumeGroup([self.target_node, self.other_node])
8569
8570     # Step: check other node consistency
8571     self.lu.LogStep(2, steps_total, "Check peer consistency")
8572     self._CheckDisksConsistency(self.other_node,
8573                                 self.other_node == self.instance.primary_node,
8574                                 False)
8575
8576     # Step: create new storage
8577     self.lu.LogStep(3, steps_total, "Allocate new storage")
8578     iv_names = self._CreateNewStorage(self.target_node)
8579
8580     # Step: for each lv, detach+rename*2+attach
8581     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
8582     for dev, old_lvs, new_lvs in iv_names.itervalues():
8583       self.lu.LogInfo("Detaching %s drbd from local storage" % dev.iv_name)
8584
8585       result = self.rpc.call_blockdev_removechildren(self.target_node, dev,
8586                                                      old_lvs)
8587       result.Raise("Can't detach drbd from local storage on node"
8588                    " %s for device %s" % (self.target_node, dev.iv_name))
8589       #dev.children = []
8590       #cfg.Update(instance)
8591
8592       # ok, we created the new LVs, so now we know we have the needed
8593       # storage; as such, we proceed on the target node to rename
8594       # old_lv to _old, and new_lv to old_lv; note that we rename LVs
8595       # using the assumption that logical_id == physical_id (which in
8596       # turn is the unique_id on that node)
8597
8598       # FIXME(iustin): use a better name for the replaced LVs
8599       temp_suffix = int(time.time())
8600       ren_fn = lambda d, suff: (d.physical_id[0],
8601                                 d.physical_id[1] + "_replaced-%s" % suff)
8602
8603       # Build the rename list based on what LVs exist on the node
8604       rename_old_to_new = []
8605       for to_ren in old_lvs:
8606         result = self.rpc.call_blockdev_find(self.target_node, to_ren)
8607         if not result.fail_msg and result.payload:
8608           # device exists
8609           rename_old_to_new.append((to_ren, ren_fn(to_ren, temp_suffix)))
8610
8611       self.lu.LogInfo("Renaming the old LVs on the target node")
8612       result = self.rpc.call_blockdev_rename(self.target_node,
8613                                              rename_old_to_new)
8614       result.Raise("Can't rename old LVs on node %s" % self.target_node)
8615
8616       # Now we rename the new LVs to the old LVs
8617       self.lu.LogInfo("Renaming the new LVs on the target node")
8618       rename_new_to_old = [(new, old.physical_id)
8619                            for old, new in zip(old_lvs, new_lvs)]
8620       result = self.rpc.call_blockdev_rename(self.target_node,
8621                                              rename_new_to_old)
8622       result.Raise("Can't rename new LVs on node %s" % self.target_node)
8623
8624       for old, new in zip(old_lvs, new_lvs):
8625         new.logical_id = old.logical_id
8626         self.cfg.SetDiskID(new, self.target_node)
8627
8628       for disk in old_lvs:
8629         disk.logical_id = ren_fn(disk, temp_suffix)
8630         self.cfg.SetDiskID(disk, self.target_node)
8631
8632       # Now that the new lvs have the old name, we can add them to the device
8633       self.lu.LogInfo("Adding new mirror component on %s" % self.target_node)
8634       result = self.rpc.call_blockdev_addchildren(self.target_node, dev,
8635                                                   new_lvs)
8636       msg = result.fail_msg
8637       if msg:
8638         for new_lv in new_lvs:
8639           msg2 = self.rpc.call_blockdev_remove(self.target_node,
8640                                                new_lv).fail_msg
8641           if msg2:
8642             self.lu.LogWarning("Can't rollback device %s: %s", dev, msg2,
8643                                hint=("cleanup manually the unused logical"
8644                                      "volumes"))
8645         raise errors.OpExecError("Can't add local storage to drbd: %s" % msg)
8646
8647       dev.children = new_lvs
8648
8649       self.cfg.Update(self.instance, feedback_fn)
8650
8651     cstep = 5
8652     if self.early_release:
8653       self.lu.LogStep(cstep, steps_total, "Removing old storage")
8654       cstep += 1
8655       self._RemoveOldStorage(self.target_node, iv_names)
8656       # WARNING: we release both node locks here, do not do other RPCs
8657       # than WaitForSync to the primary node
8658       self._ReleaseNodeLock([self.target_node, self.other_node])
8659
8660     # Wait for sync
8661     # This can fail as the old devices are degraded and _WaitForSync
8662     # does a combined result over all disks, so we don't check its return value
8663     self.lu.LogStep(cstep, steps_total, "Sync devices")
8664     cstep += 1
8665     _WaitForSync(self.lu, self.instance)
8666
8667     # Check all devices manually
8668     self._CheckDevices(self.instance.primary_node, iv_names)
8669
8670     # Step: remove old storage
8671     if not self.early_release:
8672       self.lu.LogStep(cstep, steps_total, "Removing old storage")
8673       cstep += 1
8674       self._RemoveOldStorage(self.target_node, iv_names)
8675
8676   def _ExecDrbd8Secondary(self, feedback_fn):
8677     """Replace the secondary node for DRBD 8.
8678
8679     The algorithm for replace is quite complicated:
8680       - for all disks of the instance:
8681         - create new LVs on the new node with same names
8682         - shutdown the drbd device on the old secondary
8683         - disconnect the drbd network on the primary
8684         - create the drbd device on the new secondary
8685         - network attach the drbd on the primary, using an artifice:
8686           the drbd code for Attach() will connect to the network if it
8687           finds a device which is connected to the good local disks but
8688           not network enabled
8689       - wait for sync across all devices
8690       - remove all disks from the old secondary
8691
8692     Failures are not very well handled.
8693
8694     """
8695     steps_total = 6
8696
8697     # Step: check device activation
8698     self.lu.LogStep(1, steps_total, "Check device existence")
8699     self._CheckDisksExistence([self.instance.primary_node])
8700     self._CheckVolumeGroup([self.instance.primary_node])
8701
8702     # Step: check other node consistency
8703     self.lu.LogStep(2, steps_total, "Check peer consistency")
8704     self._CheckDisksConsistency(self.instance.primary_node, True, True)
8705
8706     # Step: create new storage
8707     self.lu.LogStep(3, steps_total, "Allocate new storage")
8708     for idx, dev in enumerate(self.instance.disks):
8709       self.lu.LogInfo("Adding new local storage on %s for disk/%d" %
8710                       (self.new_node, idx))
8711       # we pass force_create=True to force LVM creation
8712       for new_lv in dev.children:
8713         _CreateBlockDev(self.lu, self.new_node, self.instance, new_lv, True,
8714                         _GetInstanceInfoText(self.instance), False)
8715
8716     # Step 4: dbrd minors and drbd setups changes
8717     # after this, we must manually remove the drbd minors on both the
8718     # error and the success paths
8719     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
8720     minors = self.cfg.AllocateDRBDMinor([self.new_node
8721                                          for dev in self.instance.disks],
8722                                         self.instance.name)
8723     logging.debug("Allocated minors %r", minors)
8724
8725     iv_names = {}
8726     for idx, (dev, new_minor) in enumerate(zip(self.instance.disks, minors)):
8727       self.lu.LogInfo("activating a new drbd on %s for disk/%d" %
8728                       (self.new_node, idx))
8729       # create new devices on new_node; note that we create two IDs:
8730       # one without port, so the drbd will be activated without
8731       # networking information on the new node at this stage, and one
8732       # with network, for the latter activation in step 4
8733       (o_node1, o_node2, o_port, o_minor1, o_minor2, o_secret) = dev.logical_id
8734       if self.instance.primary_node == o_node1:
8735         p_minor = o_minor1
8736       else:
8737         assert self.instance.primary_node == o_node2, "Three-node instance?"
8738         p_minor = o_minor2
8739
8740       new_alone_id = (self.instance.primary_node, self.new_node, None,
8741                       p_minor, new_minor, o_secret)
8742       new_net_id = (self.instance.primary_node, self.new_node, o_port,
8743                     p_minor, new_minor, o_secret)
8744
8745       iv_names[idx] = (dev, dev.children, new_net_id)
8746       logging.debug("Allocated new_minor: %s, new_logical_id: %s", new_minor,
8747                     new_net_id)
8748       new_drbd = objects.Disk(dev_type=constants.LD_DRBD8,
8749                               logical_id=new_alone_id,
8750                               children=dev.children,
8751                               size=dev.size)
8752       try:
8753         _CreateSingleBlockDev(self.lu, self.new_node, self.instance, new_drbd,
8754                               _GetInstanceInfoText(self.instance), False)
8755       except errors.GenericError:
8756         self.cfg.ReleaseDRBDMinors(self.instance.name)
8757         raise
8758
8759     # We have new devices, shutdown the drbd on the old secondary
8760     for idx, dev in enumerate(self.instance.disks):
8761       self.lu.LogInfo("Shutting down drbd for disk/%d on old node" % idx)
8762       self.cfg.SetDiskID(dev, self.target_node)
8763       msg = self.rpc.call_blockdev_shutdown(self.target_node, dev).fail_msg
8764       if msg:
8765         self.lu.LogWarning("Failed to shutdown drbd for disk/%d on old"
8766                            "node: %s" % (idx, msg),
8767                            hint=("Please cleanup this device manually as"
8768                                  " soon as possible"))
8769
8770     self.lu.LogInfo("Detaching primary drbds from the network (=> standalone)")
8771     result = self.rpc.call_drbd_disconnect_net([self.instance.primary_node],
8772                                                self.node_secondary_ip,
8773                                                self.instance.disks)\
8774                                               [self.instance.primary_node]
8775
8776     msg = result.fail_msg
8777     if msg:
8778       # detaches didn't succeed (unlikely)
8779       self.cfg.ReleaseDRBDMinors(self.instance.name)
8780       raise errors.OpExecError("Can't detach the disks from the network on"
8781                                " old node: %s" % (msg,))
8782
8783     # if we managed to detach at least one, we update all the disks of
8784     # the instance to point to the new secondary
8785     self.lu.LogInfo("Updating instance configuration")
8786     for dev, _, new_logical_id in iv_names.itervalues():
8787       dev.logical_id = new_logical_id
8788       self.cfg.SetDiskID(dev, self.instance.primary_node)
8789
8790     self.cfg.Update(self.instance, feedback_fn)
8791
8792     # and now perform the drbd attach
8793     self.lu.LogInfo("Attaching primary drbds to new secondary"
8794                     " (standalone => connected)")
8795     result = self.rpc.call_drbd_attach_net([self.instance.primary_node,
8796                                             self.new_node],
8797                                            self.node_secondary_ip,
8798                                            self.instance.disks,
8799                                            self.instance.name,
8800                                            False)
8801     for to_node, to_result in result.items():
8802       msg = to_result.fail_msg
8803       if msg:
8804         self.lu.LogWarning("Can't attach drbd disks on node %s: %s",
8805                            to_node, msg,
8806                            hint=("please do a gnt-instance info to see the"
8807                                  " status of disks"))
8808     cstep = 5
8809     if self.early_release:
8810       self.lu.LogStep(cstep, steps_total, "Removing old storage")
8811       cstep += 1
8812       self._RemoveOldStorage(self.target_node, iv_names)
8813       # WARNING: we release all node locks here, do not do other RPCs
8814       # than WaitForSync to the primary node
8815       self._ReleaseNodeLock([self.instance.primary_node,
8816                              self.target_node,
8817                              self.new_node])
8818
8819     # Wait for sync
8820     # This can fail as the old devices are degraded and _WaitForSync
8821     # does a combined result over all disks, so we don't check its return value
8822     self.lu.LogStep(cstep, steps_total, "Sync devices")
8823     cstep += 1
8824     _WaitForSync(self.lu, self.instance)
8825
8826     # Check all devices manually
8827     self._CheckDevices(self.instance.primary_node, iv_names)
8828
8829     # Step: remove old storage
8830     if not self.early_release:
8831       self.lu.LogStep(cstep, steps_total, "Removing old storage")
8832       self._RemoveOldStorage(self.target_node, iv_names)
8833
8834
8835 class LURepairNodeStorage(NoHooksLU):
8836   """Repairs the volume group on a node.
8837
8838   """
8839   REQ_BGL = False
8840
8841   def CheckArguments(self):
8842     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
8843
8844     storage_type = self.op.storage_type
8845
8846     if (constants.SO_FIX_CONSISTENCY not in
8847         constants.VALID_STORAGE_OPERATIONS.get(storage_type, [])):
8848       raise errors.OpPrereqError("Storage units of type '%s' can not be"
8849                                  " repaired" % storage_type,
8850                                  errors.ECODE_INVAL)
8851
8852   def ExpandNames(self):
8853     self.needed_locks = {
8854       locking.LEVEL_NODE: [self.op.node_name],
8855       }
8856
8857   def _CheckFaultyDisks(self, instance, node_name):
8858     """Ensure faulty disks abort the opcode or at least warn."""
8859     try:
8860       if _FindFaultyInstanceDisks(self.cfg, self.rpc, instance,
8861                                   node_name, True):
8862         raise errors.OpPrereqError("Instance '%s' has faulty disks on"
8863                                    " node '%s'" % (instance.name, node_name),
8864                                    errors.ECODE_STATE)
8865     except errors.OpPrereqError, err:
8866       if self.op.ignore_consistency:
8867         self.proc.LogWarning(str(err.args[0]))
8868       else:
8869         raise
8870
8871   def CheckPrereq(self):
8872     """Check prerequisites.
8873
8874     """
8875     # Check whether any instance on this node has faulty disks
8876     for inst in _GetNodeInstances(self.cfg, self.op.node_name):
8877       if not inst.admin_up:
8878         continue
8879       check_nodes = set(inst.all_nodes)
8880       check_nodes.discard(self.op.node_name)
8881       for inst_node_name in check_nodes:
8882         self._CheckFaultyDisks(inst, inst_node_name)
8883
8884   def Exec(self, feedback_fn):
8885     feedback_fn("Repairing storage unit '%s' on %s ..." %
8886                 (self.op.name, self.op.node_name))
8887
8888     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
8889     result = self.rpc.call_storage_execute(self.op.node_name,
8890                                            self.op.storage_type, st_args,
8891                                            self.op.name,
8892                                            constants.SO_FIX_CONSISTENCY)
8893     result.Raise("Failed to repair storage unit '%s' on %s" %
8894                  (self.op.name, self.op.node_name))
8895
8896
8897 class LUNodeEvacStrategy(NoHooksLU):
8898   """Computes the node evacuation strategy.
8899
8900   """
8901   REQ_BGL = False
8902
8903   def CheckArguments(self):
8904     _CheckIAllocatorOrNode(self, "iallocator", "remote_node")
8905
8906   def ExpandNames(self):
8907     self.op.nodes = _GetWantedNodes(self, self.op.nodes)
8908     self.needed_locks = locks = {}
8909     if self.op.remote_node is None:
8910       locks[locking.LEVEL_NODE] = locking.ALL_SET
8911     else:
8912       self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
8913       locks[locking.LEVEL_NODE] = self.op.nodes + [self.op.remote_node]
8914
8915   def Exec(self, feedback_fn):
8916     if self.op.remote_node is not None:
8917       instances = []
8918       for node in self.op.nodes:
8919         instances.extend(_GetNodeSecondaryInstances(self.cfg, node))
8920       result = []
8921       for i in instances:
8922         if i.primary_node == self.op.remote_node:
8923           raise errors.OpPrereqError("Node %s is the primary node of"
8924                                      " instance %s, cannot use it as"
8925                                      " secondary" %
8926                                      (self.op.remote_node, i.name),
8927                                      errors.ECODE_INVAL)
8928         result.append([i.name, self.op.remote_node])
8929     else:
8930       ial = IAllocator(self.cfg, self.rpc,
8931                        mode=constants.IALLOCATOR_MODE_MEVAC,
8932                        evac_nodes=self.op.nodes)
8933       ial.Run(self.op.iallocator, validate=True)
8934       if not ial.success:
8935         raise errors.OpExecError("No valid evacuation solution: %s" % ial.info,
8936                                  errors.ECODE_NORES)
8937       result = ial.result
8938     return result
8939
8940
8941 class LUInstanceGrowDisk(LogicalUnit):
8942   """Grow a disk of an instance.
8943
8944   """
8945   HPATH = "disk-grow"
8946   HTYPE = constants.HTYPE_INSTANCE
8947   REQ_BGL = False
8948
8949   def ExpandNames(self):
8950     self._ExpandAndLockInstance()
8951     self.needed_locks[locking.LEVEL_NODE] = []
8952     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
8953
8954   def DeclareLocks(self, level):
8955     if level == locking.LEVEL_NODE:
8956       self._LockInstancesNodes()
8957
8958   def BuildHooksEnv(self):
8959     """Build hooks env.
8960
8961     This runs on the master, the primary and all the secondaries.
8962
8963     """
8964     env = {
8965       "DISK": self.op.disk,
8966       "AMOUNT": self.op.amount,
8967       }
8968     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
8969     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
8970     return env, nl, nl
8971
8972   def CheckPrereq(self):
8973     """Check prerequisites.
8974
8975     This checks that the instance is in the cluster.
8976
8977     """
8978     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
8979     assert instance is not None, \
8980       "Cannot retrieve locked instance %s" % self.op.instance_name
8981     nodenames = list(instance.all_nodes)
8982     for node in nodenames:
8983       _CheckNodeOnline(self, node)
8984
8985     self.instance = instance
8986
8987     if instance.disk_template not in constants.DTS_GROWABLE:
8988       raise errors.OpPrereqError("Instance's disk layout does not support"
8989                                  " growing.", errors.ECODE_INVAL)
8990
8991     self.disk = instance.FindDisk(self.op.disk)
8992
8993     if instance.disk_template not in (constants.DT_FILE,
8994                                       constants.DT_SHARED_FILE):
8995       # TODO: check the free disk space for file, when that feature will be
8996       # supported
8997       _CheckNodesFreeDiskPerVG(self, nodenames,
8998                                self.disk.ComputeGrowth(self.op.amount))
8999
9000   def Exec(self, feedback_fn):
9001     """Execute disk grow.
9002
9003     """
9004     instance = self.instance
9005     disk = self.disk
9006
9007     disks_ok, _ = _AssembleInstanceDisks(self, self.instance, disks=[disk])
9008     if not disks_ok:
9009       raise errors.OpExecError("Cannot activate block device to grow")
9010
9011     for node in instance.all_nodes:
9012       self.cfg.SetDiskID(disk, node)
9013       result = self.rpc.call_blockdev_grow(node, disk, self.op.amount)
9014       result.Raise("Grow request failed to node %s" % node)
9015
9016       # TODO: Rewrite code to work properly
9017       # DRBD goes into sync mode for a short amount of time after executing the
9018       # "resize" command. DRBD 8.x below version 8.0.13 contains a bug whereby
9019       # calling "resize" in sync mode fails. Sleeping for a short amount of
9020       # time is a work-around.
9021       time.sleep(5)
9022
9023     disk.RecordGrow(self.op.amount)
9024     self.cfg.Update(instance, feedback_fn)
9025     if self.op.wait_for_sync:
9026       disk_abort = not _WaitForSync(self, instance, disks=[disk])
9027       if disk_abort:
9028         self.proc.LogWarning("Warning: disk sync-ing has not returned a good"
9029                              " status.\nPlease check the instance.")
9030       if not instance.admin_up:
9031         _SafeShutdownInstanceDisks(self, instance, disks=[disk])
9032     elif not instance.admin_up:
9033       self.proc.LogWarning("Not shutting down the disk even if the instance is"
9034                            " not supposed to be running because no wait for"
9035                            " sync mode was requested.")
9036
9037
9038 class LUInstanceQueryData(NoHooksLU):
9039   """Query runtime instance data.
9040
9041   """
9042   REQ_BGL = False
9043
9044   def ExpandNames(self):
9045     self.needed_locks = {}
9046     self.share_locks = dict.fromkeys(locking.LEVELS, 1)
9047
9048     if self.op.instances:
9049       self.wanted_names = []
9050       for name in self.op.instances:
9051         full_name = _ExpandInstanceName(self.cfg, name)
9052         self.wanted_names.append(full_name)
9053       self.needed_locks[locking.LEVEL_INSTANCE] = self.wanted_names
9054     else:
9055       self.wanted_names = None
9056       self.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET
9057
9058     self.needed_locks[locking.LEVEL_NODE] = []
9059     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
9060
9061   def DeclareLocks(self, level):
9062     if level == locking.LEVEL_NODE:
9063       self._LockInstancesNodes()
9064
9065   def CheckPrereq(self):
9066     """Check prerequisites.
9067
9068     This only checks the optional instance list against the existing names.
9069
9070     """
9071     if self.wanted_names is None:
9072       self.wanted_names = self.acquired_locks[locking.LEVEL_INSTANCE]
9073
9074     self.wanted_instances = [self.cfg.GetInstanceInfo(name) for name
9075                              in self.wanted_names]
9076
9077   def _ComputeBlockdevStatus(self, node, instance_name, dev):
9078     """Returns the status of a block device
9079
9080     """
9081     if self.op.static or not node:
9082       return None
9083
9084     self.cfg.SetDiskID(dev, node)
9085
9086     result = self.rpc.call_blockdev_find(node, dev)
9087     if result.offline:
9088       return None
9089
9090     result.Raise("Can't compute disk status for %s" % instance_name)
9091
9092     status = result.payload
9093     if status is None:
9094       return None
9095
9096     return (status.dev_path, status.major, status.minor,
9097             status.sync_percent, status.estimated_time,
9098             status.is_degraded, status.ldisk_status)
9099
9100   def _ComputeDiskStatus(self, instance, snode, dev):
9101     """Compute block device status.
9102
9103     """
9104     if dev.dev_type in constants.LDS_DRBD:
9105       # we change the snode then (otherwise we use the one passed in)
9106       if dev.logical_id[0] == instance.primary_node:
9107         snode = dev.logical_id[1]
9108       else:
9109         snode = dev.logical_id[0]
9110
9111     dev_pstatus = self._ComputeBlockdevStatus(instance.primary_node,
9112                                               instance.name, dev)
9113     dev_sstatus = self._ComputeBlockdevStatus(snode, instance.name, dev)
9114
9115     if dev.children:
9116       dev_children = [self._ComputeDiskStatus(instance, snode, child)
9117                       for child in dev.children]
9118     else:
9119       dev_children = []
9120
9121     data = {
9122       "iv_name": dev.iv_name,
9123       "dev_type": dev.dev_type,
9124       "logical_id": dev.logical_id,
9125       "physical_id": dev.physical_id,
9126       "pstatus": dev_pstatus,
9127       "sstatus": dev_sstatus,
9128       "children": dev_children,
9129       "mode": dev.mode,
9130       "size": dev.size,
9131       }
9132
9133     return data
9134
9135   def Exec(self, feedback_fn):
9136     """Gather and return data"""
9137     result = {}
9138
9139     cluster = self.cfg.GetClusterInfo()
9140
9141     for instance in self.wanted_instances:
9142       if not self.op.static:
9143         remote_info = self.rpc.call_instance_info(instance.primary_node,
9144                                                   instance.name,
9145                                                   instance.hypervisor)
9146         remote_info.Raise("Error checking node %s" % instance.primary_node)
9147         remote_info = remote_info.payload
9148         if remote_info and "state" in remote_info:
9149           remote_state = "up"
9150         else:
9151           remote_state = "down"
9152       else:
9153         remote_state = None
9154       if instance.admin_up:
9155         config_state = "up"
9156       else:
9157         config_state = "down"
9158
9159       disks = [self._ComputeDiskStatus(instance, None, device)
9160                for device in instance.disks]
9161
9162       idict = {
9163         "name": instance.name,
9164         "config_state": config_state,
9165         "run_state": remote_state,
9166         "pnode": instance.primary_node,
9167         "snodes": instance.secondary_nodes,
9168         "os": instance.os,
9169         # this happens to be the same format used for hooks
9170         "nics": _NICListToTuple(self, instance.nics),
9171         "disk_template": instance.disk_template,
9172         "disks": disks,
9173         "hypervisor": instance.hypervisor,
9174         "network_port": instance.network_port,
9175         "hv_instance": instance.hvparams,
9176         "hv_actual": cluster.FillHV(instance, skip_globals=True),
9177         "be_instance": instance.beparams,
9178         "be_actual": cluster.FillBE(instance),
9179         "os_instance": instance.osparams,
9180         "os_actual": cluster.SimpleFillOS(instance.os, instance.osparams),
9181         "serial_no": instance.serial_no,
9182         "mtime": instance.mtime,
9183         "ctime": instance.ctime,
9184         "uuid": instance.uuid,
9185         }
9186
9187       result[instance.name] = idict
9188
9189     return result
9190
9191
9192 class LUInstanceSetParams(LogicalUnit):
9193   """Modifies an instances's parameters.
9194
9195   """
9196   HPATH = "instance-modify"
9197   HTYPE = constants.HTYPE_INSTANCE
9198   REQ_BGL = False
9199
9200   def CheckArguments(self):
9201     if not (self.op.nics or self.op.disks or self.op.disk_template or
9202             self.op.hvparams or self.op.beparams or self.op.os_name):
9203       raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
9204
9205     if self.op.hvparams:
9206       _CheckGlobalHvParams(self.op.hvparams)
9207
9208     # Disk validation
9209     disk_addremove = 0
9210     for disk_op, disk_dict in self.op.disks:
9211       utils.ForceDictType(disk_dict, constants.IDISK_PARAMS_TYPES)
9212       if disk_op == constants.DDM_REMOVE:
9213         disk_addremove += 1
9214         continue
9215       elif disk_op == constants.DDM_ADD:
9216         disk_addremove += 1
9217       else:
9218         if not isinstance(disk_op, int):
9219           raise errors.OpPrereqError("Invalid disk index", errors.ECODE_INVAL)
9220         if not isinstance(disk_dict, dict):
9221           msg = "Invalid disk value: expected dict, got '%s'" % disk_dict
9222           raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
9223
9224       if disk_op == constants.DDM_ADD:
9225         mode = disk_dict.setdefault('mode', constants.DISK_RDWR)
9226         if mode not in constants.DISK_ACCESS_SET:
9227           raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
9228                                      errors.ECODE_INVAL)
9229         size = disk_dict.get('size', None)
9230         if size is None:
9231           raise errors.OpPrereqError("Required disk parameter size missing",
9232                                      errors.ECODE_INVAL)
9233         try:
9234           size = int(size)
9235         except (TypeError, ValueError), err:
9236           raise errors.OpPrereqError("Invalid disk size parameter: %s" %
9237                                      str(err), errors.ECODE_INVAL)
9238         disk_dict['size'] = size
9239       else:
9240         # modification of disk
9241         if 'size' in disk_dict:
9242           raise errors.OpPrereqError("Disk size change not possible, use"
9243                                      " grow-disk", errors.ECODE_INVAL)
9244
9245     if disk_addremove > 1:
9246       raise errors.OpPrereqError("Only one disk add or remove operation"
9247                                  " supported at a time", errors.ECODE_INVAL)
9248
9249     if self.op.disks and self.op.disk_template is not None:
9250       raise errors.OpPrereqError("Disk template conversion and other disk"
9251                                  " changes not supported at the same time",
9252                                  errors.ECODE_INVAL)
9253
9254     if (self.op.disk_template and
9255         self.op.disk_template in constants.DTS_NET_MIRROR and
9256         self.op.remote_node is None):
9257       raise errors.OpPrereqError("Changing the disk template to a mirrored"
9258                                  " one requires specifying a secondary node",
9259                                  errors.ECODE_INVAL)
9260
9261     # NIC validation
9262     nic_addremove = 0
9263     for nic_op, nic_dict in self.op.nics:
9264       utils.ForceDictType(nic_dict, constants.INIC_PARAMS_TYPES)
9265       if nic_op == constants.DDM_REMOVE:
9266         nic_addremove += 1
9267         continue
9268       elif nic_op == constants.DDM_ADD:
9269         nic_addremove += 1
9270       else:
9271         if not isinstance(nic_op, int):
9272           raise errors.OpPrereqError("Invalid nic index", errors.ECODE_INVAL)
9273         if not isinstance(nic_dict, dict):
9274           msg = "Invalid nic value: expected dict, got '%s'" % nic_dict
9275           raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
9276
9277       # nic_dict should be a dict
9278       nic_ip = nic_dict.get('ip', None)
9279       if nic_ip is not None:
9280         if nic_ip.lower() == constants.VALUE_NONE:
9281           nic_dict['ip'] = None
9282         else:
9283           if not netutils.IPAddress.IsValid(nic_ip):
9284             raise errors.OpPrereqError("Invalid IP address '%s'" % nic_ip,
9285                                        errors.ECODE_INVAL)
9286
9287       nic_bridge = nic_dict.get('bridge', None)
9288       nic_link = nic_dict.get('link', None)
9289       if nic_bridge and nic_link:
9290         raise errors.OpPrereqError("Cannot pass 'bridge' and 'link'"
9291                                    " at the same time", errors.ECODE_INVAL)
9292       elif nic_bridge and nic_bridge.lower() == constants.VALUE_NONE:
9293         nic_dict['bridge'] = None
9294       elif nic_link and nic_link.lower() == constants.VALUE_NONE:
9295         nic_dict['link'] = None
9296
9297       if nic_op == constants.DDM_ADD:
9298         nic_mac = nic_dict.get('mac', None)
9299         if nic_mac is None:
9300           nic_dict['mac'] = constants.VALUE_AUTO
9301
9302       if 'mac' in nic_dict:
9303         nic_mac = nic_dict['mac']
9304         if nic_mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
9305           nic_mac = utils.NormalizeAndValidateMac(nic_mac)
9306
9307         if nic_op != constants.DDM_ADD and nic_mac == constants.VALUE_AUTO:
9308           raise errors.OpPrereqError("'auto' is not a valid MAC address when"
9309                                      " modifying an existing nic",
9310                                      errors.ECODE_INVAL)
9311
9312     if nic_addremove > 1:
9313       raise errors.OpPrereqError("Only one NIC add or remove operation"
9314                                  " supported at a time", errors.ECODE_INVAL)
9315
9316   def ExpandNames(self):
9317     self._ExpandAndLockInstance()
9318     self.needed_locks[locking.LEVEL_NODE] = []
9319     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
9320
9321   def DeclareLocks(self, level):
9322     if level == locking.LEVEL_NODE:
9323       self._LockInstancesNodes()
9324       if self.op.disk_template and self.op.remote_node:
9325         self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
9326         self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
9327
9328   def BuildHooksEnv(self):
9329     """Build hooks env.
9330
9331     This runs on the master, primary and secondaries.
9332
9333     """
9334     args = dict()
9335     if constants.BE_MEMORY in self.be_new:
9336       args['memory'] = self.be_new[constants.BE_MEMORY]
9337     if constants.BE_VCPUS in self.be_new:
9338       args['vcpus'] = self.be_new[constants.BE_VCPUS]
9339     # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
9340     # information at all.
9341     if self.op.nics:
9342       args['nics'] = []
9343       nic_override = dict(self.op.nics)
9344       for idx, nic in enumerate(self.instance.nics):
9345         if idx in nic_override:
9346           this_nic_override = nic_override[idx]
9347         else:
9348           this_nic_override = {}
9349         if 'ip' in this_nic_override:
9350           ip = this_nic_override['ip']
9351         else:
9352           ip = nic.ip
9353         if 'mac' in this_nic_override:
9354           mac = this_nic_override['mac']
9355         else:
9356           mac = nic.mac
9357         if idx in self.nic_pnew:
9358           nicparams = self.nic_pnew[idx]
9359         else:
9360           nicparams = self.cluster.SimpleFillNIC(nic.nicparams)
9361         mode = nicparams[constants.NIC_MODE]
9362         link = nicparams[constants.NIC_LINK]
9363         args['nics'].append((ip, mac, mode, link))
9364       if constants.DDM_ADD in nic_override:
9365         ip = nic_override[constants.DDM_ADD].get('ip', None)
9366         mac = nic_override[constants.DDM_ADD]['mac']
9367         nicparams = self.nic_pnew[constants.DDM_ADD]
9368         mode = nicparams[constants.NIC_MODE]
9369         link = nicparams[constants.NIC_LINK]
9370         args['nics'].append((ip, mac, mode, link))
9371       elif constants.DDM_REMOVE in nic_override:
9372         del args['nics'][-1]
9373
9374     env = _BuildInstanceHookEnvByObject(self, self.instance, override=args)
9375     if self.op.disk_template:
9376       env["NEW_DISK_TEMPLATE"] = self.op.disk_template
9377     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
9378     return env, nl, nl
9379
9380   def CheckPrereq(self):
9381     """Check prerequisites.
9382
9383     This only checks the instance list against the existing names.
9384
9385     """
9386     # checking the new params on the primary/secondary nodes
9387
9388     instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
9389     cluster = self.cluster = self.cfg.GetClusterInfo()
9390     assert self.instance is not None, \
9391       "Cannot retrieve locked instance %s" % self.op.instance_name
9392     pnode = instance.primary_node
9393     nodelist = list(instance.all_nodes)
9394
9395     # OS change
9396     if self.op.os_name and not self.op.force:
9397       _CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
9398                       self.op.force_variant)
9399       instance_os = self.op.os_name
9400     else:
9401       instance_os = instance.os
9402
9403     if self.op.disk_template:
9404       if instance.disk_template == self.op.disk_template:
9405         raise errors.OpPrereqError("Instance already has disk template %s" %
9406                                    instance.disk_template, errors.ECODE_INVAL)
9407
9408       if (instance.disk_template,
9409           self.op.disk_template) not in self._DISK_CONVERSIONS:
9410         raise errors.OpPrereqError("Unsupported disk template conversion from"
9411                                    " %s to %s" % (instance.disk_template,
9412                                                   self.op.disk_template),
9413                                    errors.ECODE_INVAL)
9414       _CheckInstanceDown(self, instance, "cannot change disk template")
9415       if self.op.disk_template in constants.DTS_NET_MIRROR:
9416         if self.op.remote_node == pnode:
9417           raise errors.OpPrereqError("Given new secondary node %s is the same"
9418                                      " as the primary node of the instance" %
9419                                      self.op.remote_node, errors.ECODE_STATE)
9420         _CheckNodeOnline(self, self.op.remote_node)
9421         _CheckNodeNotDrained(self, self.op.remote_node)
9422         # FIXME: here we assume that the old instance type is DT_PLAIN
9423         assert instance.disk_template == constants.DT_PLAIN
9424         disks = [{"size": d.size, "vg": d.logical_id[0]}
9425                  for d in instance.disks]
9426         required = _ComputeDiskSizePerVG(self.op.disk_template, disks)
9427         _CheckNodesFreeDiskPerVG(self, [self.op.remote_node], required)
9428
9429     # hvparams processing
9430     if self.op.hvparams:
9431       hv_type = instance.hypervisor
9432       i_hvdict = _GetUpdatedParams(instance.hvparams, self.op.hvparams)
9433       utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
9434       hv_new = cluster.SimpleFillHV(hv_type, instance.os, i_hvdict)
9435
9436       # local check
9437       hypervisor.GetHypervisor(hv_type).CheckParameterSyntax(hv_new)
9438       _CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
9439       self.hv_new = hv_new # the new actual values
9440       self.hv_inst = i_hvdict # the new dict (without defaults)
9441     else:
9442       self.hv_new = self.hv_inst = {}
9443
9444     # beparams processing
9445     if self.op.beparams:
9446       i_bedict = _GetUpdatedParams(instance.beparams, self.op.beparams,
9447                                    use_none=True)
9448       utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
9449       be_new = cluster.SimpleFillBE(i_bedict)
9450       self.be_new = be_new # the new actual values
9451       self.be_inst = i_bedict # the new dict (without defaults)
9452     else:
9453       self.be_new = self.be_inst = {}
9454
9455     # osparams processing
9456     if self.op.osparams:
9457       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
9458       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
9459       self.os_inst = i_osdict # the new dict (without defaults)
9460     else:
9461       self.os_inst = {}
9462
9463     self.warn = []
9464
9465     if constants.BE_MEMORY in self.op.beparams and not self.op.force:
9466       mem_check_list = [pnode]
9467       if be_new[constants.BE_AUTO_BALANCE]:
9468         # either we changed auto_balance to yes or it was from before
9469         mem_check_list.extend(instance.secondary_nodes)
9470       instance_info = self.rpc.call_instance_info(pnode, instance.name,
9471                                                   instance.hypervisor)
9472       nodeinfo = self.rpc.call_node_info(mem_check_list, None,
9473                                          instance.hypervisor)
9474       pninfo = nodeinfo[pnode]
9475       msg = pninfo.fail_msg
9476       if msg:
9477         # Assume the primary node is unreachable and go ahead
9478         self.warn.append("Can't get info from primary node %s: %s" %
9479                          (pnode,  msg))
9480       elif not isinstance(pninfo.payload.get('memory_free', None), int):
9481         self.warn.append("Node data from primary node %s doesn't contain"
9482                          " free memory information" % pnode)
9483       elif instance_info.fail_msg:
9484         self.warn.append("Can't get instance runtime information: %s" %
9485                         instance_info.fail_msg)
9486       else:
9487         if instance_info.payload:
9488           current_mem = int(instance_info.payload['memory'])
9489         else:
9490           # Assume instance not running
9491           # (there is a slight race condition here, but it's not very probable,
9492           # and we have no other way to check)
9493           current_mem = 0
9494         miss_mem = (be_new[constants.BE_MEMORY] - current_mem -
9495                     pninfo.payload['memory_free'])
9496         if miss_mem > 0:
9497           raise errors.OpPrereqError("This change will prevent the instance"
9498                                      " from starting, due to %d MB of memory"
9499                                      " missing on its primary node" % miss_mem,
9500                                      errors.ECODE_NORES)
9501
9502       if be_new[constants.BE_AUTO_BALANCE]:
9503         for node, nres in nodeinfo.items():
9504           if node not in instance.secondary_nodes:
9505             continue
9506           msg = nres.fail_msg
9507           if msg:
9508             self.warn.append("Can't get info from secondary node %s: %s" %
9509                              (node, msg))
9510           elif not isinstance(nres.payload.get('memory_free', None), int):
9511             self.warn.append("Secondary node %s didn't return free"
9512                              " memory information" % node)
9513           elif be_new[constants.BE_MEMORY] > nres.payload['memory_free']:
9514             self.warn.append("Not enough memory to failover instance to"
9515                              " secondary node %s" % node)
9516
9517     # NIC processing
9518     self.nic_pnew = {}
9519     self.nic_pinst = {}
9520     for nic_op, nic_dict in self.op.nics:
9521       if nic_op == constants.DDM_REMOVE:
9522         if not instance.nics:
9523           raise errors.OpPrereqError("Instance has no NICs, cannot remove",
9524                                      errors.ECODE_INVAL)
9525         continue
9526       if nic_op != constants.DDM_ADD:
9527         # an existing nic
9528         if not instance.nics:
9529           raise errors.OpPrereqError("Invalid NIC index %s, instance has"
9530                                      " no NICs" % nic_op,
9531                                      errors.ECODE_INVAL)
9532         if nic_op < 0 or nic_op >= len(instance.nics):
9533           raise errors.OpPrereqError("Invalid NIC index %s, valid values"
9534                                      " are 0 to %d" %
9535                                      (nic_op, len(instance.nics) - 1),
9536                                      errors.ECODE_INVAL)
9537         old_nic_params = instance.nics[nic_op].nicparams
9538         old_nic_ip = instance.nics[nic_op].ip
9539       else:
9540         old_nic_params = {}
9541         old_nic_ip = None
9542
9543       update_params_dict = dict([(key, nic_dict[key])
9544                                  for key in constants.NICS_PARAMETERS
9545                                  if key in nic_dict])
9546
9547       if 'bridge' in nic_dict:
9548         update_params_dict[constants.NIC_LINK] = nic_dict['bridge']
9549
9550       new_nic_params = _GetUpdatedParams(old_nic_params,
9551                                          update_params_dict)
9552       utils.ForceDictType(new_nic_params, constants.NICS_PARAMETER_TYPES)
9553       new_filled_nic_params = cluster.SimpleFillNIC(new_nic_params)
9554       objects.NIC.CheckParameterSyntax(new_filled_nic_params)
9555       self.nic_pinst[nic_op] = new_nic_params
9556       self.nic_pnew[nic_op] = new_filled_nic_params
9557       new_nic_mode = new_filled_nic_params[constants.NIC_MODE]
9558
9559       if new_nic_mode == constants.NIC_MODE_BRIDGED:
9560         nic_bridge = new_filled_nic_params[constants.NIC_LINK]
9561         msg = self.rpc.call_bridges_exist(pnode, [nic_bridge]).fail_msg
9562         if msg:
9563           msg = "Error checking bridges on node %s: %s" % (pnode, msg)
9564           if self.op.force:
9565             self.warn.append(msg)
9566           else:
9567             raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
9568       if new_nic_mode == constants.NIC_MODE_ROUTED:
9569         if 'ip' in nic_dict:
9570           nic_ip = nic_dict['ip']
9571         else:
9572           nic_ip = old_nic_ip
9573         if nic_ip is None:
9574           raise errors.OpPrereqError('Cannot set the nic ip to None'
9575                                      ' on a routed nic', errors.ECODE_INVAL)
9576       if 'mac' in nic_dict:
9577         nic_mac = nic_dict['mac']
9578         if nic_mac is None:
9579           raise errors.OpPrereqError('Cannot set the nic mac to None',
9580                                      errors.ECODE_INVAL)
9581         elif nic_mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
9582           # otherwise generate the mac
9583           nic_dict['mac'] = self.cfg.GenerateMAC(self.proc.GetECId())
9584         else:
9585           # or validate/reserve the current one
9586           try:
9587             self.cfg.ReserveMAC(nic_mac, self.proc.GetECId())
9588           except errors.ReservationError:
9589             raise errors.OpPrereqError("MAC address %s already in use"
9590                                        " in cluster" % nic_mac,
9591                                        errors.ECODE_NOTUNIQUE)
9592
9593     # DISK processing
9594     if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
9595       raise errors.OpPrereqError("Disk operations not supported for"
9596                                  " diskless instances",
9597                                  errors.ECODE_INVAL)
9598     for disk_op, _ in self.op.disks:
9599       if disk_op == constants.DDM_REMOVE:
9600         if len(instance.disks) == 1:
9601           raise errors.OpPrereqError("Cannot remove the last disk of"
9602                                      " an instance", errors.ECODE_INVAL)
9603         _CheckInstanceDown(self, instance, "cannot remove disks")
9604
9605       if (disk_op == constants.DDM_ADD and
9606           len(instance.disks) >= constants.MAX_DISKS):
9607         raise errors.OpPrereqError("Instance has too many disks (%d), cannot"
9608                                    " add more" % constants.MAX_DISKS,
9609                                    errors.ECODE_STATE)
9610       if disk_op not in (constants.DDM_ADD, constants.DDM_REMOVE):
9611         # an existing disk
9612         if disk_op < 0 or disk_op >= len(instance.disks):
9613           raise errors.OpPrereqError("Invalid disk index %s, valid values"
9614                                      " are 0 to %d" %
9615                                      (disk_op, len(instance.disks)),
9616                                      errors.ECODE_INVAL)
9617
9618     return
9619
9620   def _ConvertPlainToDrbd(self, feedback_fn):
9621     """Converts an instance from plain to drbd.
9622
9623     """
9624     feedback_fn("Converting template to drbd")
9625     instance = self.instance
9626     pnode = instance.primary_node
9627     snode = self.op.remote_node
9628
9629     # create a fake disk info for _GenerateDiskTemplate
9630     disk_info = [{"size": d.size, "mode": d.mode} for d in instance.disks]
9631     new_disks = _GenerateDiskTemplate(self, self.op.disk_template,
9632                                       instance.name, pnode, [snode],
9633                                       disk_info, None, None, 0, feedback_fn)
9634     info = _GetInstanceInfoText(instance)
9635     feedback_fn("Creating aditional volumes...")
9636     # first, create the missing data and meta devices
9637     for disk in new_disks:
9638       # unfortunately this is... not too nice
9639       _CreateSingleBlockDev(self, pnode, instance, disk.children[1],
9640                             info, True)
9641       for child in disk.children:
9642         _CreateSingleBlockDev(self, snode, instance, child, info, True)
9643     # at this stage, all new LVs have been created, we can rename the
9644     # old ones
9645     feedback_fn("Renaming original volumes...")
9646     rename_list = [(o, n.children[0].logical_id)
9647                    for (o, n) in zip(instance.disks, new_disks)]
9648     result = self.rpc.call_blockdev_rename(pnode, rename_list)
9649     result.Raise("Failed to rename original LVs")
9650
9651     feedback_fn("Initializing DRBD devices...")
9652     # all child devices are in place, we can now create the DRBD devices
9653     for disk in new_disks:
9654       for node in [pnode, snode]:
9655         f_create = node == pnode
9656         _CreateSingleBlockDev(self, node, instance, disk, info, f_create)
9657
9658     # at this point, the instance has been modified
9659     instance.disk_template = constants.DT_DRBD8
9660     instance.disks = new_disks
9661     self.cfg.Update(instance, feedback_fn)
9662
9663     # disks are created, waiting for sync
9664     disk_abort = not _WaitForSync(self, instance)
9665     if disk_abort:
9666       raise errors.OpExecError("There are some degraded disks for"
9667                                " this instance, please cleanup manually")
9668
9669   def _ConvertDrbdToPlain(self, feedback_fn):
9670     """Converts an instance from drbd to plain.
9671
9672     """
9673     instance = self.instance
9674     assert len(instance.secondary_nodes) == 1
9675     pnode = instance.primary_node
9676     snode = instance.secondary_nodes[0]
9677     feedback_fn("Converting template to plain")
9678
9679     old_disks = instance.disks
9680     new_disks = [d.children[0] for d in old_disks]
9681
9682     # copy over size and mode
9683     for parent, child in zip(old_disks, new_disks):
9684       child.size = parent.size
9685       child.mode = parent.mode
9686
9687     # update instance structure
9688     instance.disks = new_disks
9689     instance.disk_template = constants.DT_PLAIN
9690     self.cfg.Update(instance, feedback_fn)
9691
9692     feedback_fn("Removing volumes on the secondary node...")
9693     for disk in old_disks:
9694       self.cfg.SetDiskID(disk, snode)
9695       msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
9696       if msg:
9697         self.LogWarning("Could not remove block device %s on node %s,"
9698                         " continuing anyway: %s", disk.iv_name, snode, msg)
9699
9700     feedback_fn("Removing unneeded volumes on the primary node...")
9701     for idx, disk in enumerate(old_disks):
9702       meta = disk.children[1]
9703       self.cfg.SetDiskID(meta, pnode)
9704       msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
9705       if msg:
9706         self.LogWarning("Could not remove metadata for disk %d on node %s,"
9707                         " continuing anyway: %s", idx, pnode, msg)
9708
9709   def Exec(self, feedback_fn):
9710     """Modifies an instance.
9711
9712     All parameters take effect only at the next restart of the instance.
9713
9714     """
9715     # Process here the warnings from CheckPrereq, as we don't have a
9716     # feedback_fn there.
9717     for warn in self.warn:
9718       feedback_fn("WARNING: %s" % warn)
9719
9720     result = []
9721     instance = self.instance
9722     # disk changes
9723     for disk_op, disk_dict in self.op.disks:
9724       if disk_op == constants.DDM_REMOVE:
9725         # remove the last disk
9726         device = instance.disks.pop()
9727         device_idx = len(instance.disks)
9728         for node, disk in device.ComputeNodeTree(instance.primary_node):
9729           self.cfg.SetDiskID(disk, node)
9730           msg = self.rpc.call_blockdev_remove(node, disk).fail_msg
9731           if msg:
9732             self.LogWarning("Could not remove disk/%d on node %s: %s,"
9733                             " continuing anyway", device_idx, node, msg)
9734         result.append(("disk/%d" % device_idx, "remove"))
9735       elif disk_op == constants.DDM_ADD:
9736         # add a new disk
9737         if instance.disk_template in (constants.DT_FILE,
9738                                         constants.DT_SHARED_FILE):
9739           file_driver, file_path = instance.disks[0].logical_id
9740           file_path = os.path.dirname(file_path)
9741         else:
9742           file_driver = file_path = None
9743         disk_idx_base = len(instance.disks)
9744         new_disk = _GenerateDiskTemplate(self,
9745                                          instance.disk_template,
9746                                          instance.name, instance.primary_node,
9747                                          instance.secondary_nodes,
9748                                          [disk_dict],
9749                                          file_path,
9750                                          file_driver,
9751                                          disk_idx_base, feedback_fn)[0]
9752         instance.disks.append(new_disk)
9753         info = _GetInstanceInfoText(instance)
9754
9755         logging.info("Creating volume %s for instance %s",
9756                      new_disk.iv_name, instance.name)
9757         # Note: this needs to be kept in sync with _CreateDisks
9758         #HARDCODE
9759         for node in instance.all_nodes:
9760           f_create = node == instance.primary_node
9761           try:
9762             _CreateBlockDev(self, node, instance, new_disk,
9763                             f_create, info, f_create)
9764           except errors.OpExecError, err:
9765             self.LogWarning("Failed to create volume %s (%s) on"
9766                             " node %s: %s",
9767                             new_disk.iv_name, new_disk, node, err)
9768         result.append(("disk/%d" % disk_idx_base, "add:size=%s,mode=%s" %
9769                        (new_disk.size, new_disk.mode)))
9770       else:
9771         # change a given disk
9772         instance.disks[disk_op].mode = disk_dict['mode']
9773         result.append(("disk.mode/%d" % disk_op, disk_dict['mode']))
9774
9775     if self.op.disk_template:
9776       r_shut = _ShutdownInstanceDisks(self, instance)
9777       if not r_shut:
9778         raise errors.OpExecError("Cannot shutdown instance disks, unable to"
9779                                  " proceed with disk template conversion")
9780       mode = (instance.disk_template, self.op.disk_template)
9781       try:
9782         self._DISK_CONVERSIONS[mode](self, feedback_fn)
9783       except:
9784         self.cfg.ReleaseDRBDMinors(instance.name)
9785         raise
9786       result.append(("disk_template", self.op.disk_template))
9787
9788     # NIC changes
9789     for nic_op, nic_dict in self.op.nics:
9790       if nic_op == constants.DDM_REMOVE:
9791         # remove the last nic
9792         del instance.nics[-1]
9793         result.append(("nic.%d" % len(instance.nics), "remove"))
9794       elif nic_op == constants.DDM_ADD:
9795         # mac and bridge should be set, by now
9796         mac = nic_dict['mac']
9797         ip = nic_dict.get('ip', None)
9798         nicparams = self.nic_pinst[constants.DDM_ADD]
9799         new_nic = objects.NIC(mac=mac, ip=ip, nicparams=nicparams)
9800         instance.nics.append(new_nic)
9801         result.append(("nic.%d" % (len(instance.nics) - 1),
9802                        "add:mac=%s,ip=%s,mode=%s,link=%s" %
9803                        (new_nic.mac, new_nic.ip,
9804                         self.nic_pnew[constants.DDM_ADD][constants.NIC_MODE],
9805                         self.nic_pnew[constants.DDM_ADD][constants.NIC_LINK]
9806                        )))
9807       else:
9808         for key in 'mac', 'ip':
9809           if key in nic_dict:
9810             setattr(instance.nics[nic_op], key, nic_dict[key])
9811         if nic_op in self.nic_pinst:
9812           instance.nics[nic_op].nicparams = self.nic_pinst[nic_op]
9813         for key, val in nic_dict.iteritems():
9814           result.append(("nic.%s/%d" % (key, nic_op), val))
9815
9816     # hvparams changes
9817     if self.op.hvparams:
9818       instance.hvparams = self.hv_inst
9819       for key, val in self.op.hvparams.iteritems():
9820         result.append(("hv/%s" % key, val))
9821
9822     # beparams changes
9823     if self.op.beparams:
9824       instance.beparams = self.be_inst
9825       for key, val in self.op.beparams.iteritems():
9826         result.append(("be/%s" % key, val))
9827
9828     # OS change
9829     if self.op.os_name:
9830       instance.os = self.op.os_name
9831
9832     # osparams changes
9833     if self.op.osparams:
9834       instance.osparams = self.os_inst
9835       for key, val in self.op.osparams.iteritems():
9836         result.append(("os/%s" % key, val))
9837
9838     self.cfg.Update(instance, feedback_fn)
9839
9840     return result
9841
9842   _DISK_CONVERSIONS = {
9843     (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
9844     (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
9845     }
9846
9847
9848 class LUBackupQuery(NoHooksLU):
9849   """Query the exports list
9850
9851   """
9852   REQ_BGL = False
9853
9854   def ExpandNames(self):
9855     self.needed_locks = {}
9856     self.share_locks[locking.LEVEL_NODE] = 1
9857     if not self.op.nodes:
9858       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9859     else:
9860       self.needed_locks[locking.LEVEL_NODE] = \
9861         _GetWantedNodes(self, self.op.nodes)
9862
9863   def Exec(self, feedback_fn):
9864     """Compute the list of all the exported system images.
9865
9866     @rtype: dict
9867     @return: a dictionary with the structure node->(export-list)
9868         where export-list is a list of the instances exported on
9869         that node.
9870
9871     """
9872     self.nodes = self.acquired_locks[locking.LEVEL_NODE]
9873     rpcresult = self.rpc.call_export_list(self.nodes)
9874     result = {}
9875     for node in rpcresult:
9876       if rpcresult[node].fail_msg:
9877         result[node] = False
9878       else:
9879         result[node] = rpcresult[node].payload
9880
9881     return result
9882
9883
9884 class LUBackupPrepare(NoHooksLU):
9885   """Prepares an instance for an export and returns useful information.
9886
9887   """
9888   REQ_BGL = False
9889
9890   def ExpandNames(self):
9891     self._ExpandAndLockInstance()
9892
9893   def CheckPrereq(self):
9894     """Check prerequisites.
9895
9896     """
9897     instance_name = self.op.instance_name
9898
9899     self.instance = self.cfg.GetInstanceInfo(instance_name)
9900     assert self.instance is not None, \
9901           "Cannot retrieve locked instance %s" % self.op.instance_name
9902     _CheckNodeOnline(self, self.instance.primary_node)
9903
9904     self._cds = _GetClusterDomainSecret()
9905
9906   def Exec(self, feedback_fn):
9907     """Prepares an instance for an export.
9908
9909     """
9910     instance = self.instance
9911
9912     if self.op.mode == constants.EXPORT_MODE_REMOTE:
9913       salt = utils.GenerateSecret(8)
9914
9915       feedback_fn("Generating X509 certificate on %s" % instance.primary_node)
9916       result = self.rpc.call_x509_cert_create(instance.primary_node,
9917                                               constants.RIE_CERT_VALIDITY)
9918       result.Raise("Can't create X509 key and certificate on %s" % result.node)
9919
9920       (name, cert_pem) = result.payload
9921
9922       cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
9923                                              cert_pem)
9924
9925       return {
9926         "handshake": masterd.instance.ComputeRemoteExportHandshake(self._cds),
9927         "x509_key_name": (name, utils.Sha1Hmac(self._cds, name, salt=salt),
9928                           salt),
9929         "x509_ca": utils.SignX509Certificate(cert, self._cds, salt),
9930         }
9931
9932     return None
9933
9934
9935 class LUBackupExport(LogicalUnit):
9936   """Export an instance to an image in the cluster.
9937
9938   """
9939   HPATH = "instance-export"
9940   HTYPE = constants.HTYPE_INSTANCE
9941   REQ_BGL = False
9942
9943   def CheckArguments(self):
9944     """Check the arguments.
9945
9946     """
9947     self.x509_key_name = self.op.x509_key_name
9948     self.dest_x509_ca_pem = self.op.destination_x509_ca
9949
9950     if self.op.mode == constants.EXPORT_MODE_REMOTE:
9951       if not self.x509_key_name:
9952         raise errors.OpPrereqError("Missing X509 key name for encryption",
9953                                    errors.ECODE_INVAL)
9954
9955       if not self.dest_x509_ca_pem:
9956         raise errors.OpPrereqError("Missing destination X509 CA",
9957                                    errors.ECODE_INVAL)
9958
9959   def ExpandNames(self):
9960     self._ExpandAndLockInstance()
9961
9962     # Lock all nodes for local exports
9963     if self.op.mode == constants.EXPORT_MODE_LOCAL:
9964       # FIXME: lock only instance primary and destination node
9965       #
9966       # Sad but true, for now we have do lock all nodes, as we don't know where
9967       # the previous export might be, and in this LU we search for it and
9968       # remove it from its current node. In the future we could fix this by:
9969       #  - making a tasklet to search (share-lock all), then create the
9970       #    new one, then one to remove, after
9971       #  - removing the removal operation altogether
9972       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9973
9974   def DeclareLocks(self, level):
9975     """Last minute lock declaration."""
9976     # All nodes are locked anyway, so nothing to do here.
9977
9978   def BuildHooksEnv(self):
9979     """Build hooks env.
9980
9981     This will run on the master, primary node and target node.
9982
9983     """
9984     env = {
9985       "EXPORT_MODE": self.op.mode,
9986       "EXPORT_NODE": self.op.target_node,
9987       "EXPORT_DO_SHUTDOWN": self.op.shutdown,
9988       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
9989       # TODO: Generic function for boolean env variables
9990       "REMOVE_INSTANCE": str(bool(self.op.remove_instance)),
9991       }
9992
9993     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
9994
9995     nl = [self.cfg.GetMasterNode(), self.instance.primary_node]
9996
9997     if self.op.mode == constants.EXPORT_MODE_LOCAL:
9998       nl.append(self.op.target_node)
9999
10000     return env, nl, nl
10001
10002   def CheckPrereq(self):
10003     """Check prerequisites.
10004
10005     This checks that the instance and node names are valid.
10006
10007     """
10008     instance_name = self.op.instance_name
10009
10010     self.instance = self.cfg.GetInstanceInfo(instance_name)
10011     assert self.instance is not None, \
10012           "Cannot retrieve locked instance %s" % self.op.instance_name
10013     _CheckNodeOnline(self, self.instance.primary_node)
10014
10015     if (self.op.remove_instance and self.instance.admin_up and
10016         not self.op.shutdown):
10017       raise errors.OpPrereqError("Can not remove instance without shutting it"
10018                                  " down before")
10019
10020     if self.op.mode == constants.EXPORT_MODE_LOCAL:
10021       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
10022       self.dst_node = self.cfg.GetNodeInfo(self.op.target_node)
10023       assert self.dst_node is not None
10024
10025       _CheckNodeOnline(self, self.dst_node.name)
10026       _CheckNodeNotDrained(self, self.dst_node.name)
10027
10028       self._cds = None
10029       self.dest_disk_info = None
10030       self.dest_x509_ca = None
10031
10032     elif self.op.mode == constants.EXPORT_MODE_REMOTE:
10033       self.dst_node = None
10034
10035       if len(self.op.target_node) != len(self.instance.disks):
10036         raise errors.OpPrereqError(("Received destination information for %s"
10037                                     " disks, but instance %s has %s disks") %
10038                                    (len(self.op.target_node), instance_name,
10039                                     len(self.instance.disks)),
10040                                    errors.ECODE_INVAL)
10041
10042       cds = _GetClusterDomainSecret()
10043
10044       # Check X509 key name
10045       try:
10046         (key_name, hmac_digest, hmac_salt) = self.x509_key_name
10047       except (TypeError, ValueError), err:
10048         raise errors.OpPrereqError("Invalid data for X509 key name: %s" % err)
10049
10050       if not utils.VerifySha1Hmac(cds, key_name, hmac_digest, salt=hmac_salt):
10051         raise errors.OpPrereqError("HMAC for X509 key name is wrong",
10052                                    errors.ECODE_INVAL)
10053
10054       # Load and verify CA
10055       try:
10056         (cert, _) = utils.LoadSignedX509Certificate(self.dest_x509_ca_pem, cds)
10057       except OpenSSL.crypto.Error, err:
10058         raise errors.OpPrereqError("Unable to load destination X509 CA (%s)" %
10059                                    (err, ), errors.ECODE_INVAL)
10060
10061       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
10062       if errcode is not None:
10063         raise errors.OpPrereqError("Invalid destination X509 CA (%s)" %
10064                                    (msg, ), errors.ECODE_INVAL)
10065
10066       self.dest_x509_ca = cert
10067
10068       # Verify target information
10069       disk_info = []
10070       for idx, disk_data in enumerate(self.op.target_node):
10071         try:
10072           (host, port, magic) = \
10073             masterd.instance.CheckRemoteExportDiskInfo(cds, idx, disk_data)
10074         except errors.GenericError, err:
10075           raise errors.OpPrereqError("Target info for disk %s: %s" %
10076                                      (idx, err), errors.ECODE_INVAL)
10077
10078         disk_info.append((host, port, magic))
10079
10080       assert len(disk_info) == len(self.op.target_node)
10081       self.dest_disk_info = disk_info
10082
10083     else:
10084       raise errors.ProgrammerError("Unhandled export mode %r" %
10085                                    self.op.mode)
10086
10087     # instance disk type verification
10088     # TODO: Implement export support for file-based disks
10089     for disk in self.instance.disks:
10090       if disk.dev_type == constants.LD_FILE:
10091         raise errors.OpPrereqError("Export not supported for instances with"
10092                                    " file-based disks", errors.ECODE_INVAL)
10093
10094   def _CleanupExports(self, feedback_fn):
10095     """Removes exports of current instance from all other nodes.
10096
10097     If an instance in a cluster with nodes A..D was exported to node C, its
10098     exports will be removed from the nodes A, B and D.
10099
10100     """
10101     assert self.op.mode != constants.EXPORT_MODE_REMOTE
10102
10103     nodelist = self.cfg.GetNodeList()
10104     nodelist.remove(self.dst_node.name)
10105
10106     # on one-node clusters nodelist will be empty after the removal
10107     # if we proceed the backup would be removed because OpBackupQuery
10108     # substitutes an empty list with the full cluster node list.
10109     iname = self.instance.name
10110     if nodelist:
10111       feedback_fn("Removing old exports for instance %s" % iname)
10112       exportlist = self.rpc.call_export_list(nodelist)
10113       for node in exportlist:
10114         if exportlist[node].fail_msg:
10115           continue
10116         if iname in exportlist[node].payload:
10117           msg = self.rpc.call_export_remove(node, iname).fail_msg
10118           if msg:
10119             self.LogWarning("Could not remove older export for instance %s"
10120                             " on node %s: %s", iname, node, msg)
10121
10122   def Exec(self, feedback_fn):
10123     """Export an instance to an image in the cluster.
10124
10125     """
10126     assert self.op.mode in constants.EXPORT_MODES
10127
10128     instance = self.instance
10129     src_node = instance.primary_node
10130
10131     if self.op.shutdown:
10132       # shutdown the instance, but not the disks
10133       feedback_fn("Shutting down instance %s" % instance.name)
10134       result = self.rpc.call_instance_shutdown(src_node, instance,
10135                                                self.op.shutdown_timeout)
10136       # TODO: Maybe ignore failures if ignore_remove_failures is set
10137       result.Raise("Could not shutdown instance %s on"
10138                    " node %s" % (instance.name, src_node))
10139
10140     # set the disks ID correctly since call_instance_start needs the
10141     # correct drbd minor to create the symlinks
10142     for disk in instance.disks:
10143       self.cfg.SetDiskID(disk, src_node)
10144
10145     activate_disks = (not instance.admin_up)
10146
10147     if activate_disks:
10148       # Activate the instance disks if we'exporting a stopped instance
10149       feedback_fn("Activating disks for %s" % instance.name)
10150       _StartInstanceDisks(self, instance, None)
10151
10152     try:
10153       helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
10154                                                      instance)
10155
10156       helper.CreateSnapshots()
10157       try:
10158         if (self.op.shutdown and instance.admin_up and
10159             not self.op.remove_instance):
10160           assert not activate_disks
10161           feedback_fn("Starting instance %s" % instance.name)
10162           result = self.rpc.call_instance_start(src_node, instance, None, None)
10163           msg = result.fail_msg
10164           if msg:
10165             feedback_fn("Failed to start instance: %s" % msg)
10166             _ShutdownInstanceDisks(self, instance)
10167             raise errors.OpExecError("Could not start instance: %s" % msg)
10168
10169         if self.op.mode == constants.EXPORT_MODE_LOCAL:
10170           (fin_resu, dresults) = helper.LocalExport(self.dst_node)
10171         elif self.op.mode == constants.EXPORT_MODE_REMOTE:
10172           connect_timeout = constants.RIE_CONNECT_TIMEOUT
10173           timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
10174
10175           (key_name, _, _) = self.x509_key_name
10176
10177           dest_ca_pem = \
10178             OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
10179                                             self.dest_x509_ca)
10180
10181           (fin_resu, dresults) = helper.RemoteExport(self.dest_disk_info,
10182                                                      key_name, dest_ca_pem,
10183                                                      timeouts)
10184       finally:
10185         helper.Cleanup()
10186
10187       # Check for backwards compatibility
10188       assert len(dresults) == len(instance.disks)
10189       assert compat.all(isinstance(i, bool) for i in dresults), \
10190              "Not all results are boolean: %r" % dresults
10191
10192     finally:
10193       if activate_disks:
10194         feedback_fn("Deactivating disks for %s" % instance.name)
10195         _ShutdownInstanceDisks(self, instance)
10196
10197     if not (compat.all(dresults) and fin_resu):
10198       failures = []
10199       if not fin_resu:
10200         failures.append("export finalization")
10201       if not compat.all(dresults):
10202         fdsk = utils.CommaJoin(idx for (idx, dsk) in enumerate(dresults)
10203                                if not dsk)
10204         failures.append("disk export: disk(s) %s" % fdsk)
10205
10206       raise errors.OpExecError("Export failed, errors in %s" %
10207                                utils.CommaJoin(failures))
10208
10209     # At this point, the export was successful, we can cleanup/finish
10210
10211     # Remove instance if requested
10212     if self.op.remove_instance:
10213       feedback_fn("Removing instance %s" % instance.name)
10214       _RemoveInstance(self, feedback_fn, instance,
10215                       self.op.ignore_remove_failures)
10216
10217     if self.op.mode == constants.EXPORT_MODE_LOCAL:
10218       self._CleanupExports(feedback_fn)
10219
10220     return fin_resu, dresults
10221
10222
10223 class LUBackupRemove(NoHooksLU):
10224   """Remove exports related to the named instance.
10225
10226   """
10227   REQ_BGL = False
10228
10229   def ExpandNames(self):
10230     self.needed_locks = {}
10231     # We need all nodes to be locked in order for RemoveExport to work, but we
10232     # don't need to lock the instance itself, as nothing will happen to it (and
10233     # we can remove exports also for a removed instance)
10234     self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
10235
10236   def Exec(self, feedback_fn):
10237     """Remove any export.
10238
10239     """
10240     instance_name = self.cfg.ExpandInstanceName(self.op.instance_name)
10241     # If the instance was not found we'll try with the name that was passed in.
10242     # This will only work if it was an FQDN, though.
10243     fqdn_warn = False
10244     if not instance_name:
10245       fqdn_warn = True
10246       instance_name = self.op.instance_name
10247
10248     locked_nodes = self.acquired_locks[locking.LEVEL_NODE]
10249     exportlist = self.rpc.call_export_list(locked_nodes)
10250     found = False
10251     for node in exportlist:
10252       msg = exportlist[node].fail_msg
10253       if msg:
10254         self.LogWarning("Failed to query node %s (continuing): %s", node, msg)
10255         continue
10256       if instance_name in exportlist[node].payload:
10257         found = True
10258         result = self.rpc.call_export_remove(node, instance_name)
10259         msg = result.fail_msg
10260         if msg:
10261           logging.error("Could not remove export for instance %s"
10262                         " on node %s: %s", instance_name, node, msg)
10263
10264     if fqdn_warn and not found:
10265       feedback_fn("Export not found. If trying to remove an export belonging"
10266                   " to a deleted instance please use its Fully Qualified"
10267                   " Domain Name.")
10268
10269
10270 class LUGroupAdd(LogicalUnit):
10271   """Logical unit for creating node groups.
10272
10273   """
10274   HPATH = "group-add"
10275   HTYPE = constants.HTYPE_GROUP
10276   REQ_BGL = False
10277
10278   def ExpandNames(self):
10279     # We need the new group's UUID here so that we can create and acquire the
10280     # corresponding lock. Later, in Exec(), we'll indicate to cfg.AddNodeGroup
10281     # that it should not check whether the UUID exists in the configuration.
10282     self.group_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
10283     self.needed_locks = {}
10284     self.add_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
10285
10286   def CheckPrereq(self):
10287     """Check prerequisites.
10288
10289     This checks that the given group name is not an existing node group
10290     already.
10291
10292     """
10293     try:
10294       existing_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
10295     except errors.OpPrereqError:
10296       pass
10297     else:
10298       raise errors.OpPrereqError("Desired group name '%s' already exists as a"
10299                                  " node group (UUID: %s)" %
10300                                  (self.op.group_name, existing_uuid),
10301                                  errors.ECODE_EXISTS)
10302
10303     if self.op.ndparams:
10304       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
10305
10306   def BuildHooksEnv(self):
10307     """Build hooks env.
10308
10309     """
10310     env = {
10311       "GROUP_NAME": self.op.group_name,
10312       }
10313     mn = self.cfg.GetMasterNode()
10314     return env, [mn], [mn]
10315
10316   def Exec(self, feedback_fn):
10317     """Add the node group to the cluster.
10318
10319     """
10320     group_obj = objects.NodeGroup(name=self.op.group_name, members=[],
10321                                   uuid=self.group_uuid,
10322                                   alloc_policy=self.op.alloc_policy,
10323                                   ndparams=self.op.ndparams)
10324
10325     self.cfg.AddNodeGroup(group_obj, self.proc.GetECId(), check_uuid=False)
10326     del self.remove_locks[locking.LEVEL_NODEGROUP]
10327
10328
10329 class LUGroupAssignNodes(NoHooksLU):
10330   """Logical unit for assigning nodes to groups.
10331
10332   """
10333   REQ_BGL = False
10334
10335   def ExpandNames(self):
10336     # These raise errors.OpPrereqError on their own:
10337     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
10338     self.op.nodes = _GetWantedNodes(self, self.op.nodes)
10339
10340     # We want to lock all the affected nodes and groups. We have readily
10341     # available the list of nodes, and the *destination* group. To gather the
10342     # list of "source" groups, we need to fetch node information.
10343     self.node_data = self.cfg.GetAllNodesInfo()
10344     affected_groups = set(self.node_data[node].group for node in self.op.nodes)
10345     affected_groups.add(self.group_uuid)
10346
10347     self.needed_locks = {
10348       locking.LEVEL_NODEGROUP: list(affected_groups),
10349       locking.LEVEL_NODE: self.op.nodes,
10350       }
10351
10352   def CheckPrereq(self):
10353     """Check prerequisites.
10354
10355     """
10356     self.group = self.cfg.GetNodeGroup(self.group_uuid)
10357     instance_data = self.cfg.GetAllInstancesInfo()
10358
10359     if self.group is None:
10360       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
10361                                (self.op.group_name, self.group_uuid))
10362
10363     (new_splits, previous_splits) = \
10364       self.CheckAssignmentForSplitInstances([(node, self.group_uuid)
10365                                              for node in self.op.nodes],
10366                                             self.node_data, instance_data)
10367
10368     if new_splits:
10369       fmt_new_splits = utils.CommaJoin(utils.NiceSort(new_splits))
10370
10371       if not self.op.force:
10372         raise errors.OpExecError("The following instances get split by this"
10373                                  " change and --force was not given: %s" %
10374                                  fmt_new_splits)
10375       else:
10376         self.LogWarning("This operation will split the following instances: %s",
10377                         fmt_new_splits)
10378
10379         if previous_splits:
10380           self.LogWarning("In addition, these already-split instances continue"
10381                           " to be spit across groups: %s",
10382                           utils.CommaJoin(utils.NiceSort(previous_splits)))
10383
10384   def Exec(self, feedback_fn):
10385     """Assign nodes to a new group.
10386
10387     """
10388     for node in self.op.nodes:
10389       self.node_data[node].group = self.group_uuid
10390
10391     self.cfg.Update(self.group, feedback_fn) # Saves all modified nodes.
10392
10393   @staticmethod
10394   def CheckAssignmentForSplitInstances(changes, node_data, instance_data):
10395     """Check for split instances after a node assignment.
10396
10397     This method considers a series of node assignments as an atomic operation,
10398     and returns information about split instances after applying the set of
10399     changes.
10400
10401     In particular, it returns information about newly split instances, and
10402     instances that were already split, and remain so after the change.
10403
10404     Only instances whose disk template is listed in constants.DTS_NET_MIRROR are
10405     considered.
10406
10407     @type changes: list of (node_name, new_group_uuid) pairs.
10408     @param changes: list of node assignments to consider.
10409     @param node_data: a dict with data for all nodes
10410     @param instance_data: a dict with all instances to consider
10411     @rtype: a two-tuple
10412     @return: a list of instances that were previously okay and result split as a
10413       consequence of this change, and a list of instances that were previously
10414       split and this change does not fix.
10415
10416     """
10417     changed_nodes = dict((node, group) for node, group in changes
10418                          if node_data[node].group != group)
10419
10420     all_split_instances = set()
10421     previously_split_instances = set()
10422
10423     def InstanceNodes(instance):
10424       return [instance.primary_node] + list(instance.secondary_nodes)
10425
10426     for inst in instance_data.values():
10427       if inst.disk_template not in constants.DTS_NET_MIRROR:
10428         continue
10429
10430       instance_nodes = InstanceNodes(inst)
10431
10432       if len(set(node_data[node].group for node in instance_nodes)) > 1:
10433         previously_split_instances.add(inst.name)
10434
10435       if len(set(changed_nodes.get(node, node_data[node].group)
10436                  for node in instance_nodes)) > 1:
10437         all_split_instances.add(inst.name)
10438
10439     return (list(all_split_instances - previously_split_instances),
10440             list(previously_split_instances & all_split_instances))
10441
10442
10443 class _GroupQuery(_QueryBase):
10444   FIELDS = query.GROUP_FIELDS
10445
10446   def ExpandNames(self, lu):
10447     lu.needed_locks = {}
10448
10449     self._all_groups = lu.cfg.GetAllNodeGroupsInfo()
10450     name_to_uuid = dict((g.name, g.uuid) for g in self._all_groups.values())
10451
10452     if not self.names:
10453       self.wanted = [name_to_uuid[name]
10454                      for name in utils.NiceSort(name_to_uuid.keys())]
10455     else:
10456       # Accept names to be either names or UUIDs.
10457       missing = []
10458       self.wanted = []
10459       all_uuid = frozenset(self._all_groups.keys())
10460
10461       for name in self.names:
10462         if name in all_uuid:
10463           self.wanted.append(name)
10464         elif name in name_to_uuid:
10465           self.wanted.append(name_to_uuid[name])
10466         else:
10467           missing.append(name)
10468
10469       if missing:
10470         raise errors.OpPrereqError("Some groups do not exist: %s" % missing,
10471                                    errors.ECODE_NOENT)
10472
10473   def DeclareLocks(self, lu, level):
10474     pass
10475
10476   def _GetQueryData(self, lu):
10477     """Computes the list of node groups and their attributes.
10478
10479     """
10480     do_nodes = query.GQ_NODE in self.requested_data
10481     do_instances = query.GQ_INST in self.requested_data
10482
10483     group_to_nodes = None
10484     group_to_instances = None
10485
10486     # For GQ_NODE, we need to map group->[nodes], and group->[instances] for
10487     # GQ_INST. The former is attainable with just GetAllNodesInfo(), but for the
10488     # latter GetAllInstancesInfo() is not enough, for we have to go through
10489     # instance->node. Hence, we will need to process nodes even if we only need
10490     # instance information.
10491     if do_nodes or do_instances:
10492       all_nodes = lu.cfg.GetAllNodesInfo()
10493       group_to_nodes = dict((uuid, []) for uuid in self.wanted)
10494       node_to_group = {}
10495
10496       for node in all_nodes.values():
10497         if node.group in group_to_nodes:
10498           group_to_nodes[node.group].append(node.name)
10499           node_to_group[node.name] = node.group
10500
10501       if do_instances:
10502         all_instances = lu.cfg.GetAllInstancesInfo()
10503         group_to_instances = dict((uuid, []) for uuid in self.wanted)
10504
10505         for instance in all_instances.values():
10506           node = instance.primary_node
10507           if node in node_to_group:
10508             group_to_instances[node_to_group[node]].append(instance.name)
10509
10510         if not do_nodes:
10511           # Do not pass on node information if it was not requested.
10512           group_to_nodes = None
10513
10514     return query.GroupQueryData([self._all_groups[uuid]
10515                                  for uuid in self.wanted],
10516                                 group_to_nodes, group_to_instances)
10517
10518
10519 class LUGroupQuery(NoHooksLU):
10520   """Logical unit for querying node groups.
10521
10522   """
10523   REQ_BGL = False
10524
10525   def CheckArguments(self):
10526     self.gq = _GroupQuery(qlang.MakeSimpleFilter("name", self.op.names),
10527                           self.op.output_fields, False)
10528
10529   def ExpandNames(self):
10530     self.gq.ExpandNames(self)
10531
10532   def Exec(self, feedback_fn):
10533     return self.gq.OldStyleQuery(self)
10534
10535
10536 class LUGroupSetParams(LogicalUnit):
10537   """Modifies the parameters of a node group.
10538
10539   """
10540   HPATH = "group-modify"
10541   HTYPE = constants.HTYPE_GROUP
10542   REQ_BGL = False
10543
10544   def CheckArguments(self):
10545     all_changes = [
10546       self.op.ndparams,
10547       self.op.alloc_policy,
10548       ]
10549
10550     if all_changes.count(None) == len(all_changes):
10551       raise errors.OpPrereqError("Please pass at least one modification",
10552                                  errors.ECODE_INVAL)
10553
10554   def ExpandNames(self):
10555     # This raises errors.OpPrereqError on its own:
10556     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
10557
10558     self.needed_locks = {
10559       locking.LEVEL_NODEGROUP: [self.group_uuid],
10560       }
10561
10562   def CheckPrereq(self):
10563     """Check prerequisites.
10564
10565     """
10566     self.group = self.cfg.GetNodeGroup(self.group_uuid)
10567
10568     if self.group is None:
10569       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
10570                                (self.op.group_name, self.group_uuid))
10571
10572     if self.op.ndparams:
10573       new_ndparams = _GetUpdatedParams(self.group.ndparams, self.op.ndparams)
10574       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
10575       self.new_ndparams = new_ndparams
10576
10577   def BuildHooksEnv(self):
10578     """Build hooks env.
10579
10580     """
10581     env = {
10582       "GROUP_NAME": self.op.group_name,
10583       "NEW_ALLOC_POLICY": self.op.alloc_policy,
10584       }
10585     mn = self.cfg.GetMasterNode()
10586     return env, [mn], [mn]
10587
10588   def Exec(self, feedback_fn):
10589     """Modifies the node group.
10590
10591     """
10592     result = []
10593
10594     if self.op.ndparams:
10595       self.group.ndparams = self.new_ndparams
10596       result.append(("ndparams", str(self.group.ndparams)))
10597
10598     if self.op.alloc_policy:
10599       self.group.alloc_policy = self.op.alloc_policy
10600
10601     self.cfg.Update(self.group, feedback_fn)
10602     return result
10603
10604
10605
10606 class LUGroupRemove(LogicalUnit):
10607   HPATH = "group-remove"
10608   HTYPE = constants.HTYPE_GROUP
10609   REQ_BGL = False
10610
10611   def ExpandNames(self):
10612     # This will raises errors.OpPrereqError on its own:
10613     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
10614     self.needed_locks = {
10615       locking.LEVEL_NODEGROUP: [self.group_uuid],
10616       }
10617
10618   def CheckPrereq(self):
10619     """Check prerequisites.
10620
10621     This checks that the given group name exists as a node group, that is
10622     empty (i.e., contains no nodes), and that is not the last group of the
10623     cluster.
10624
10625     """
10626     # Verify that the group is empty.
10627     group_nodes = [node.name
10628                    for node in self.cfg.GetAllNodesInfo().values()
10629                    if node.group == self.group_uuid]
10630
10631     if group_nodes:
10632       raise errors.OpPrereqError("Group '%s' not empty, has the following"
10633                                  " nodes: %s" %
10634                                  (self.op.group_name,
10635                                   utils.CommaJoin(utils.NiceSort(group_nodes))),
10636                                  errors.ECODE_STATE)
10637
10638     # Verify the cluster would not be left group-less.
10639     if len(self.cfg.GetNodeGroupList()) == 1:
10640       raise errors.OpPrereqError("Group '%s' is the only group,"
10641                                  " cannot be removed" %
10642                                  self.op.group_name,
10643                                  errors.ECODE_STATE)
10644
10645   def BuildHooksEnv(self):
10646     """Build hooks env.
10647
10648     """
10649     env = {
10650       "GROUP_NAME": self.op.group_name,
10651       }
10652     mn = self.cfg.GetMasterNode()
10653     return env, [mn], [mn]
10654
10655   def Exec(self, feedback_fn):
10656     """Remove the node group.
10657
10658     """
10659     try:
10660       self.cfg.RemoveNodeGroup(self.group_uuid)
10661     except errors.ConfigurationError:
10662       raise errors.OpExecError("Group '%s' with UUID %s disappeared" %
10663                                (self.op.group_name, self.group_uuid))
10664
10665     self.remove_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
10666
10667
10668 class LUGroupRename(LogicalUnit):
10669   HPATH = "group-rename"
10670   HTYPE = constants.HTYPE_GROUP
10671   REQ_BGL = False
10672
10673   def ExpandNames(self):
10674     # This raises errors.OpPrereqError on its own:
10675     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
10676
10677     self.needed_locks = {
10678       locking.LEVEL_NODEGROUP: [self.group_uuid],
10679       }
10680
10681   def CheckPrereq(self):
10682     """Check prerequisites.
10683
10684     Ensures requested new name is not yet used.
10685
10686     """
10687     try:
10688       new_name_uuid = self.cfg.LookupNodeGroup(self.op.new_name)
10689     except errors.OpPrereqError:
10690       pass
10691     else:
10692       raise errors.OpPrereqError("Desired new name '%s' clashes with existing"
10693                                  " node group (UUID: %s)" %
10694                                  (self.op.new_name, new_name_uuid),
10695                                  errors.ECODE_EXISTS)
10696
10697   def BuildHooksEnv(self):
10698     """Build hooks env.
10699
10700     """
10701     env = {
10702       "OLD_NAME": self.op.group_name,
10703       "NEW_NAME": self.op.new_name,
10704       }
10705
10706     mn = self.cfg.GetMasterNode()
10707     all_nodes = self.cfg.GetAllNodesInfo()
10708     run_nodes = [mn]
10709     all_nodes.pop(mn, None)
10710
10711     for node in all_nodes.values():
10712       if node.group == self.group_uuid:
10713         run_nodes.append(node.name)
10714
10715     return env, run_nodes, run_nodes
10716
10717   def Exec(self, feedback_fn):
10718     """Rename the node group.
10719
10720     """
10721     group = self.cfg.GetNodeGroup(self.group_uuid)
10722
10723     if group is None:
10724       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
10725                                (self.op.group_name, self.group_uuid))
10726
10727     group.name = self.op.new_name
10728     self.cfg.Update(group, feedback_fn)
10729
10730     return self.op.new_name
10731
10732
10733 class TagsLU(NoHooksLU): # pylint: disable-msg=W0223
10734   """Generic tags LU.
10735
10736   This is an abstract class which is the parent of all the other tags LUs.
10737
10738   """
10739
10740   def ExpandNames(self):
10741     self.needed_locks = {}
10742     if self.op.kind == constants.TAG_NODE:
10743       self.op.name = _ExpandNodeName(self.cfg, self.op.name)
10744       self.needed_locks[locking.LEVEL_NODE] = self.op.name
10745     elif self.op.kind == constants.TAG_INSTANCE:
10746       self.op.name = _ExpandInstanceName(self.cfg, self.op.name)
10747       self.needed_locks[locking.LEVEL_INSTANCE] = self.op.name
10748
10749     # FIXME: Acquire BGL for cluster tag operations (as of this writing it's
10750     # not possible to acquire the BGL based on opcode parameters)
10751
10752   def CheckPrereq(self):
10753     """Check prerequisites.
10754
10755     """
10756     if self.op.kind == constants.TAG_CLUSTER:
10757       self.target = self.cfg.GetClusterInfo()
10758     elif self.op.kind == constants.TAG_NODE:
10759       self.target = self.cfg.GetNodeInfo(self.op.name)
10760     elif self.op.kind == constants.TAG_INSTANCE:
10761       self.target = self.cfg.GetInstanceInfo(self.op.name)
10762     else:
10763       raise errors.OpPrereqError("Wrong tag type requested (%s)" %
10764                                  str(self.op.kind), errors.ECODE_INVAL)
10765
10766
10767 class LUTagsGet(TagsLU):
10768   """Returns the tags of a given object.
10769
10770   """
10771   REQ_BGL = False
10772
10773   def ExpandNames(self):
10774     TagsLU.ExpandNames(self)
10775
10776     # Share locks as this is only a read operation
10777     self.share_locks = dict.fromkeys(locking.LEVELS, 1)
10778
10779   def Exec(self, feedback_fn):
10780     """Returns the tag list.
10781
10782     """
10783     return list(self.target.GetTags())
10784
10785
10786 class LUTagsSearch(NoHooksLU):
10787   """Searches the tags for a given pattern.
10788
10789   """
10790   REQ_BGL = False
10791
10792   def ExpandNames(self):
10793     self.needed_locks = {}
10794
10795   def CheckPrereq(self):
10796     """Check prerequisites.
10797
10798     This checks the pattern passed for validity by compiling it.
10799
10800     """
10801     try:
10802       self.re = re.compile(self.op.pattern)
10803     except re.error, err:
10804       raise errors.OpPrereqError("Invalid search pattern '%s': %s" %
10805                                  (self.op.pattern, err), errors.ECODE_INVAL)
10806
10807   def Exec(self, feedback_fn):
10808     """Returns the tag list.
10809
10810     """
10811     cfg = self.cfg
10812     tgts = [("/cluster", cfg.GetClusterInfo())]
10813     ilist = cfg.GetAllInstancesInfo().values()
10814     tgts.extend([("/instances/%s" % i.name, i) for i in ilist])
10815     nlist = cfg.GetAllNodesInfo().values()
10816     tgts.extend([("/nodes/%s" % n.name, n) for n in nlist])
10817     results = []
10818     for path, target in tgts:
10819       for tag in target.GetTags():
10820         if self.re.search(tag):
10821           results.append((path, tag))
10822     return results
10823
10824
10825 class LUTagsSet(TagsLU):
10826   """Sets a tag on a given object.
10827
10828   """
10829   REQ_BGL = False
10830
10831   def CheckPrereq(self):
10832     """Check prerequisites.
10833
10834     This checks the type and length of the tag name and value.
10835
10836     """
10837     TagsLU.CheckPrereq(self)
10838     for tag in self.op.tags:
10839       objects.TaggableObject.ValidateTag(tag)
10840
10841   def Exec(self, feedback_fn):
10842     """Sets the tag.
10843
10844     """
10845     try:
10846       for tag in self.op.tags:
10847         self.target.AddTag(tag)
10848     except errors.TagError, err:
10849       raise errors.OpExecError("Error while setting tag: %s" % str(err))
10850     self.cfg.Update(self.target, feedback_fn)
10851
10852
10853 class LUTagsDel(TagsLU):
10854   """Delete a list of tags from a given object.
10855
10856   """
10857   REQ_BGL = False
10858
10859   def CheckPrereq(self):
10860     """Check prerequisites.
10861
10862     This checks that we have the given tag.
10863
10864     """
10865     TagsLU.CheckPrereq(self)
10866     for tag in self.op.tags:
10867       objects.TaggableObject.ValidateTag(tag)
10868     del_tags = frozenset(self.op.tags)
10869     cur_tags = self.target.GetTags()
10870
10871     diff_tags = del_tags - cur_tags
10872     if diff_tags:
10873       diff_names = ("'%s'" % i for i in sorted(diff_tags))
10874       raise errors.OpPrereqError("Tag(s) %s not found" %
10875                                  (utils.CommaJoin(diff_names), ),
10876                                  errors.ECODE_NOENT)
10877
10878   def Exec(self, feedback_fn):
10879     """Remove the tag from the object.
10880
10881     """
10882     for tag in self.op.tags:
10883       self.target.RemoveTag(tag)
10884     self.cfg.Update(self.target, feedback_fn)
10885
10886
10887 class LUTestDelay(NoHooksLU):
10888   """Sleep for a specified amount of time.
10889
10890   This LU sleeps on the master and/or nodes for a specified amount of
10891   time.
10892
10893   """
10894   REQ_BGL = False
10895
10896   def ExpandNames(self):
10897     """Expand names and set required locks.
10898
10899     This expands the node list, if any.
10900
10901     """
10902     self.needed_locks = {}
10903     if self.op.on_nodes:
10904       # _GetWantedNodes can be used here, but is not always appropriate to use
10905       # this way in ExpandNames. Check LogicalUnit.ExpandNames docstring for
10906       # more information.
10907       self.op.on_nodes = _GetWantedNodes(self, self.op.on_nodes)
10908       self.needed_locks[locking.LEVEL_NODE] = self.op.on_nodes
10909
10910   def _TestDelay(self):
10911     """Do the actual sleep.
10912
10913     """
10914     if self.op.on_master:
10915       if not utils.TestDelay(self.op.duration):
10916         raise errors.OpExecError("Error during master delay test")
10917     if self.op.on_nodes:
10918       result = self.rpc.call_test_delay(self.op.on_nodes, self.op.duration)
10919       for node, node_result in result.items():
10920         node_result.Raise("Failure during rpc call to node %s" % node)
10921
10922   def Exec(self, feedback_fn):
10923     """Execute the test delay opcode, with the wanted repetitions.
10924
10925     """
10926     if self.op.repeat == 0:
10927       self._TestDelay()
10928     else:
10929       top_value = self.op.repeat - 1
10930       for i in range(self.op.repeat):
10931         self.LogInfo("Test delay iteration %d/%d" % (i, top_value))
10932         self._TestDelay()
10933
10934
10935 class LUTestJqueue(NoHooksLU):
10936   """Utility LU to test some aspects of the job queue.
10937
10938   """
10939   REQ_BGL = False
10940
10941   # Must be lower than default timeout for WaitForJobChange to see whether it
10942   # notices changed jobs
10943   _CLIENT_CONNECT_TIMEOUT = 20.0
10944   _CLIENT_CONFIRM_TIMEOUT = 60.0
10945
10946   @classmethod
10947   def _NotifyUsingSocket(cls, cb, errcls):
10948     """Opens a Unix socket and waits for another program to connect.
10949
10950     @type cb: callable
10951     @param cb: Callback to send socket name to client
10952     @type errcls: class
10953     @param errcls: Exception class to use for errors
10954
10955     """
10956     # Using a temporary directory as there's no easy way to create temporary
10957     # sockets without writing a custom loop around tempfile.mktemp and
10958     # socket.bind
10959     tmpdir = tempfile.mkdtemp()
10960     try:
10961       tmpsock = utils.PathJoin(tmpdir, "sock")
10962
10963       logging.debug("Creating temporary socket at %s", tmpsock)
10964       sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
10965       try:
10966         sock.bind(tmpsock)
10967         sock.listen(1)
10968
10969         # Send details to client
10970         cb(tmpsock)
10971
10972         # Wait for client to connect before continuing
10973         sock.settimeout(cls._CLIENT_CONNECT_TIMEOUT)
10974         try:
10975           (conn, _) = sock.accept()
10976         except socket.error, err:
10977           raise errcls("Client didn't connect in time (%s)" % err)
10978       finally:
10979         sock.close()
10980     finally:
10981       # Remove as soon as client is connected
10982       shutil.rmtree(tmpdir)
10983
10984     # Wait for client to close
10985     try:
10986       try:
10987         # pylint: disable-msg=E1101
10988         # Instance of '_socketobject' has no ... member
10989         conn.settimeout(cls._CLIENT_CONFIRM_TIMEOUT)
10990         conn.recv(1)
10991       except socket.error, err:
10992         raise errcls("Client failed to confirm notification (%s)" % err)
10993     finally:
10994       conn.close()
10995
10996   def _SendNotification(self, test, arg, sockname):
10997     """Sends a notification to the client.
10998
10999     @type test: string
11000     @param test: Test name
11001     @param arg: Test argument (depends on test)
11002     @type sockname: string
11003     @param sockname: Socket path
11004
11005     """
11006     self.Log(constants.ELOG_JQUEUE_TEST, (sockname, test, arg))
11007
11008   def _Notify(self, prereq, test, arg):
11009     """Notifies the client of a test.
11010
11011     @type prereq: bool
11012     @param prereq: Whether this is a prereq-phase test
11013     @type test: string
11014     @param test: Test name
11015     @param arg: Test argument (depends on test)
11016
11017     """
11018     if prereq:
11019       errcls = errors.OpPrereqError
11020     else:
11021       errcls = errors.OpExecError
11022
11023     return self._NotifyUsingSocket(compat.partial(self._SendNotification,
11024                                                   test, arg),
11025                                    errcls)
11026
11027   def CheckArguments(self):
11028     self.checkargs_calls = getattr(self, "checkargs_calls", 0) + 1
11029     self.expandnames_calls = 0
11030
11031   def ExpandNames(self):
11032     checkargs_calls = getattr(self, "checkargs_calls", 0)
11033     if checkargs_calls < 1:
11034       raise errors.ProgrammerError("CheckArguments was not called")
11035
11036     self.expandnames_calls += 1
11037
11038     if self.op.notify_waitlock:
11039       self._Notify(True, constants.JQT_EXPANDNAMES, None)
11040
11041     self.LogInfo("Expanding names")
11042
11043     # Get lock on master node (just to get a lock, not for a particular reason)
11044     self.needed_locks = {
11045       locking.LEVEL_NODE: self.cfg.GetMasterNode(),
11046       }
11047
11048   def Exec(self, feedback_fn):
11049     if self.expandnames_calls < 1:
11050       raise errors.ProgrammerError("ExpandNames was not called")
11051
11052     if self.op.notify_exec:
11053       self._Notify(False, constants.JQT_EXEC, None)
11054
11055     self.LogInfo("Executing")
11056
11057     if self.op.log_messages:
11058       self._Notify(False, constants.JQT_STARTMSG, len(self.op.log_messages))
11059       for idx, msg in enumerate(self.op.log_messages):
11060         self.LogInfo("Sending log message %s", idx + 1)
11061         feedback_fn(constants.JQT_MSGPREFIX + msg)
11062         # Report how many test messages have been sent
11063         self._Notify(False, constants.JQT_LOGMSG, idx + 1)
11064
11065     if self.op.fail:
11066       raise errors.OpExecError("Opcode failure was requested")
11067
11068     return True
11069
11070
11071 class IAllocator(object):
11072   """IAllocator framework.
11073
11074   An IAllocator instance has three sets of attributes:
11075     - cfg that is needed to query the cluster
11076     - input data (all members of the _KEYS class attribute are required)
11077     - four buffer attributes (in|out_data|text), that represent the
11078       input (to the external script) in text and data structure format,
11079       and the output from it, again in two formats
11080     - the result variables from the script (success, info, nodes) for
11081       easy usage
11082
11083   """
11084   # pylint: disable-msg=R0902
11085   # lots of instance attributes
11086   _ALLO_KEYS = [
11087     "name", "mem_size", "disks", "disk_template",
11088     "os", "tags", "nics", "vcpus", "hypervisor",
11089     ]
11090   _RELO_KEYS = [
11091     "name", "relocate_from",
11092     ]
11093   _EVAC_KEYS = [
11094     "evac_nodes",
11095     ]
11096
11097   def __init__(self, cfg, rpc, mode, **kwargs):
11098     self.cfg = cfg
11099     self.rpc = rpc
11100     # init buffer variables
11101     self.in_text = self.out_text = self.in_data = self.out_data = None
11102     # init all input fields so that pylint is happy
11103     self.mode = mode
11104     self.mem_size = self.disks = self.disk_template = None
11105     self.os = self.tags = self.nics = self.vcpus = None
11106     self.hypervisor = None
11107     self.relocate_from = None
11108     self.name = None
11109     self.evac_nodes = None
11110     # computed fields
11111     self.required_nodes = None
11112     # init result fields
11113     self.success = self.info = self.result = None
11114     if self.mode == constants.IALLOCATOR_MODE_ALLOC:
11115       keyset = self._ALLO_KEYS
11116       fn = self._AddNewInstance
11117     elif self.mode == constants.IALLOCATOR_MODE_RELOC:
11118       keyset = self._RELO_KEYS
11119       fn = self._AddRelocateInstance
11120     elif self.mode == constants.IALLOCATOR_MODE_MEVAC:
11121       keyset = self._EVAC_KEYS
11122       fn = self._AddEvacuateNodes
11123     else:
11124       raise errors.ProgrammerError("Unknown mode '%s' passed to the"
11125                                    " IAllocator" % self.mode)
11126     for key in kwargs:
11127       if key not in keyset:
11128         raise errors.ProgrammerError("Invalid input parameter '%s' to"
11129                                      " IAllocator" % key)
11130       setattr(self, key, kwargs[key])
11131
11132     for key in keyset:
11133       if key not in kwargs:
11134         raise errors.ProgrammerError("Missing input parameter '%s' to"
11135                                      " IAllocator" % key)
11136     self._BuildInputData(fn)
11137
11138   def _ComputeClusterData(self):
11139     """Compute the generic allocator input data.
11140
11141     This is the data that is independent of the actual operation.
11142
11143     """
11144     cfg = self.cfg
11145     cluster_info = cfg.GetClusterInfo()
11146     # cluster data
11147     data = {
11148       "version": constants.IALLOCATOR_VERSION,
11149       "cluster_name": cfg.GetClusterName(),
11150       "cluster_tags": list(cluster_info.GetTags()),
11151       "enabled_hypervisors": list(cluster_info.enabled_hypervisors),
11152       # we don't have job IDs
11153       }
11154     ninfo = cfg.GetAllNodesInfo()
11155     iinfo = cfg.GetAllInstancesInfo().values()
11156     i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo]
11157
11158     # node data
11159     node_list = [n.name for n in ninfo.values() if n.vm_capable]
11160
11161     if self.mode == constants.IALLOCATOR_MODE_ALLOC:
11162       hypervisor_name = self.hypervisor
11163     elif self.mode == constants.IALLOCATOR_MODE_RELOC:
11164       hypervisor_name = cfg.GetInstanceInfo(self.name).hypervisor
11165     elif self.mode == constants.IALLOCATOR_MODE_MEVAC:
11166       hypervisor_name = cluster_info.enabled_hypervisors[0]
11167
11168     node_data = self.rpc.call_node_info(node_list, cfg.GetVGName(),
11169                                         hypervisor_name)
11170     node_iinfo = \
11171       self.rpc.call_all_instances_info(node_list,
11172                                        cluster_info.enabled_hypervisors)
11173
11174     data["nodegroups"] = self._ComputeNodeGroupData(cfg)
11175
11176     config_ndata = self._ComputeBasicNodeData(ninfo)
11177     data["nodes"] = self._ComputeDynamicNodeData(ninfo, node_data, node_iinfo,
11178                                                  i_list, config_ndata)
11179     assert len(data["nodes"]) == len(ninfo), \
11180         "Incomplete node data computed"
11181
11182     data["instances"] = self._ComputeInstanceData(cluster_info, i_list)
11183
11184     self.in_data = data
11185
11186   @staticmethod
11187   def _ComputeNodeGroupData(cfg):
11188     """Compute node groups data.
11189
11190     """
11191     ng = {}
11192     for guuid, gdata in cfg.GetAllNodeGroupsInfo().items():
11193       ng[guuid] = {
11194         "name": gdata.name,
11195         "alloc_policy": gdata.alloc_policy,
11196         }
11197     return ng
11198
11199   @staticmethod
11200   def _ComputeBasicNodeData(node_cfg):
11201     """Compute global node data.
11202
11203     @rtype: dict
11204     @returns: a dict of name: (node dict, node config)
11205
11206     """
11207     node_results = {}
11208     for ninfo in node_cfg.values():
11209       # fill in static (config-based) values
11210       pnr = {
11211         "tags": list(ninfo.GetTags()),
11212         "primary_ip": ninfo.primary_ip,
11213         "secondary_ip": ninfo.secondary_ip,
11214         "offline": ninfo.offline,
11215         "drained": ninfo.drained,
11216         "master_candidate": ninfo.master_candidate,
11217         "group": ninfo.group,
11218         "master_capable": ninfo.master_capable,
11219         "vm_capable": ninfo.vm_capable,
11220         }
11221
11222       node_results[ninfo.name] = pnr
11223
11224     return node_results
11225
11226   @staticmethod
11227   def _ComputeDynamicNodeData(node_cfg, node_data, node_iinfo, i_list,
11228                               node_results):
11229     """Compute global node data.
11230
11231     @param node_results: the basic node structures as filled from the config
11232
11233     """
11234     # make a copy of the current dict
11235     node_results = dict(node_results)
11236     for nname, nresult in node_data.items():
11237       assert nname in node_results, "Missing basic data for node %s" % nname
11238       ninfo = node_cfg[nname]
11239
11240       if not (ninfo.offline or ninfo.drained):
11241         nresult.Raise("Can't get data for node %s" % nname)
11242         node_iinfo[nname].Raise("Can't get node instance info from node %s" %
11243                                 nname)
11244         remote_info = nresult.payload
11245
11246         for attr in ['memory_total', 'memory_free', 'memory_dom0',
11247                      'vg_size', 'vg_free', 'cpu_total']:
11248           if attr not in remote_info:
11249             raise errors.OpExecError("Node '%s' didn't return attribute"
11250                                      " '%s'" % (nname, attr))
11251           if not isinstance(remote_info[attr], int):
11252             raise errors.OpExecError("Node '%s' returned invalid value"
11253                                      " for '%s': %s" %
11254                                      (nname, attr, remote_info[attr]))
11255         # compute memory used by primary instances
11256         i_p_mem = i_p_up_mem = 0
11257         for iinfo, beinfo in i_list:
11258           if iinfo.primary_node == nname:
11259             i_p_mem += beinfo[constants.BE_MEMORY]
11260             if iinfo.name not in node_iinfo[nname].payload:
11261               i_used_mem = 0
11262             else:
11263               i_used_mem = int(node_iinfo[nname].payload[iinfo.name]['memory'])
11264             i_mem_diff = beinfo[constants.BE_MEMORY] - i_used_mem
11265             remote_info['memory_free'] -= max(0, i_mem_diff)
11266
11267             if iinfo.admin_up:
11268               i_p_up_mem += beinfo[constants.BE_MEMORY]
11269
11270         # compute memory used by instances
11271         pnr_dyn = {
11272           "total_memory": remote_info['memory_total'],
11273           "reserved_memory": remote_info['memory_dom0'],
11274           "free_memory": remote_info['memory_free'],
11275           "total_disk": remote_info['vg_size'],
11276           "free_disk": remote_info['vg_free'],
11277           "total_cpus": remote_info['cpu_total'],
11278           "i_pri_memory": i_p_mem,
11279           "i_pri_up_memory": i_p_up_mem,
11280           }
11281         pnr_dyn.update(node_results[nname])
11282         node_results[nname] = pnr_dyn
11283
11284     return node_results
11285
11286   @staticmethod
11287   def _ComputeInstanceData(cluster_info, i_list):
11288     """Compute global instance data.
11289
11290     """
11291     instance_data = {}
11292     for iinfo, beinfo in i_list:
11293       nic_data = []
11294       for nic in iinfo.nics:
11295         filled_params = cluster_info.SimpleFillNIC(nic.nicparams)
11296         nic_dict = {"mac": nic.mac,
11297                     "ip": nic.ip,
11298                     "mode": filled_params[constants.NIC_MODE],
11299                     "link": filled_params[constants.NIC_LINK],
11300                    }
11301         if filled_params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
11302           nic_dict["bridge"] = filled_params[constants.NIC_LINK]
11303         nic_data.append(nic_dict)
11304       pir = {
11305         "tags": list(iinfo.GetTags()),
11306         "admin_up": iinfo.admin_up,
11307         "vcpus": beinfo[constants.BE_VCPUS],
11308         "memory": beinfo[constants.BE_MEMORY],
11309         "os": iinfo.os,
11310         "nodes": [iinfo.primary_node] + list(iinfo.secondary_nodes),
11311         "nics": nic_data,
11312         "disks": [{"size": dsk.size, "mode": dsk.mode} for dsk in iinfo.disks],
11313         "disk_template": iinfo.disk_template,
11314         "hypervisor": iinfo.hypervisor,
11315         }
11316       pir["disk_space_total"] = _ComputeDiskSize(iinfo.disk_template,
11317                                                  pir["disks"])
11318       instance_data[iinfo.name] = pir
11319
11320     return instance_data
11321
11322   def _AddNewInstance(self):
11323     """Add new instance data to allocator structure.
11324
11325     This in combination with _AllocatorGetClusterData will create the
11326     correct structure needed as input for the allocator.
11327
11328     The checks for the completeness of the opcode must have already been
11329     done.
11330
11331     """
11332     disk_space = _ComputeDiskSize(self.disk_template, self.disks)
11333
11334     if self.disk_template in constants.DTS_NET_MIRROR:
11335       self.required_nodes = 2
11336     else:
11337       self.required_nodes = 1
11338     request = {
11339       "name": self.name,
11340       "disk_template": self.disk_template,
11341       "tags": self.tags,
11342       "os": self.os,
11343       "vcpus": self.vcpus,
11344       "memory": self.mem_size,
11345       "disks": self.disks,
11346       "disk_space_total": disk_space,
11347       "nics": self.nics,
11348       "required_nodes": self.required_nodes,
11349       }
11350     return request
11351
11352   def _AddRelocateInstance(self):
11353     """Add relocate instance data to allocator structure.
11354
11355     This in combination with _IAllocatorGetClusterData will create the
11356     correct structure needed as input for the allocator.
11357
11358     The checks for the completeness of the opcode must have already been
11359     done.
11360
11361     """
11362     instance = self.cfg.GetInstanceInfo(self.name)
11363     if instance is None:
11364       raise errors.ProgrammerError("Unknown instance '%s' passed to"
11365                                    " IAllocator" % self.name)
11366
11367     if instance.disk_template not in constants.DTS_MIRRORED:
11368       raise errors.OpPrereqError("Can't relocate non-mirrored instances",
11369                                  errors.ECODE_INVAL)
11370
11371     if instance.disk_template in constants.DTS_NET_MIRROR and \
11372         len(instance.secondary_nodes) != 1:
11373       raise errors.OpPrereqError("Instance has not exactly one secondary node",
11374                                  errors.ECODE_STATE)
11375
11376     self.required_nodes = 1
11377     disk_sizes = [{'size': disk.size} for disk in instance.disks]
11378     disk_space = _ComputeDiskSize(instance.disk_template, disk_sizes)
11379
11380     request = {
11381       "name": self.name,
11382       "disk_space_total": disk_space,
11383       "required_nodes": self.required_nodes,
11384       "relocate_from": self.relocate_from,
11385       }
11386     return request
11387
11388   def _AddEvacuateNodes(self):
11389     """Add evacuate nodes data to allocator structure.
11390
11391     """
11392     request = {
11393       "evac_nodes": self.evac_nodes
11394       }
11395     return request
11396
11397   def _BuildInputData(self, fn):
11398     """Build input data structures.
11399
11400     """
11401     self._ComputeClusterData()
11402
11403     request = fn()
11404     request["type"] = self.mode
11405     self.in_data["request"] = request
11406
11407     self.in_text = serializer.Dump(self.in_data)
11408
11409   def Run(self, name, validate=True, call_fn=None):
11410     """Run an instance allocator and return the results.
11411
11412     """
11413     if call_fn is None:
11414       call_fn = self.rpc.call_iallocator_runner
11415
11416     result = call_fn(self.cfg.GetMasterNode(), name, self.in_text)
11417     result.Raise("Failure while running the iallocator script")
11418
11419     self.out_text = result.payload
11420     if validate:
11421       self._ValidateResult()
11422
11423   def _ValidateResult(self):
11424     """Process the allocator results.
11425
11426     This will process and if successful save the result in
11427     self.out_data and the other parameters.
11428
11429     """
11430     try:
11431       rdict = serializer.Load(self.out_text)
11432     except Exception, err:
11433       raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
11434
11435     if not isinstance(rdict, dict):
11436       raise errors.OpExecError("Can't parse iallocator results: not a dict")
11437
11438     # TODO: remove backwards compatiblity in later versions
11439     if "nodes" in rdict and "result" not in rdict:
11440       rdict["result"] = rdict["nodes"]
11441       del rdict["nodes"]
11442
11443     for key in "success", "info", "result":
11444       if key not in rdict:
11445         raise errors.OpExecError("Can't parse iallocator results:"
11446                                  " missing key '%s'" % key)
11447       setattr(self, key, rdict[key])
11448
11449     if not isinstance(rdict["result"], list):
11450       raise errors.OpExecError("Can't parse iallocator results: 'result' key"
11451                                " is not a list")
11452     self.out_data = rdict
11453
11454
11455 class LUTestAllocator(NoHooksLU):
11456   """Run allocator tests.
11457
11458   This LU runs the allocator tests
11459
11460   """
11461   def CheckPrereq(self):
11462     """Check prerequisites.
11463
11464     This checks the opcode parameters depending on the director and mode test.
11465
11466     """
11467     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
11468       for attr in ["mem_size", "disks", "disk_template",
11469                    "os", "tags", "nics", "vcpus"]:
11470         if not hasattr(self.op, attr):
11471           raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
11472                                      attr, errors.ECODE_INVAL)
11473       iname = self.cfg.ExpandInstanceName(self.op.name)
11474       if iname is not None:
11475         raise errors.OpPrereqError("Instance '%s' already in the cluster" %
11476                                    iname, errors.ECODE_EXISTS)
11477       if not isinstance(self.op.nics, list):
11478         raise errors.OpPrereqError("Invalid parameter 'nics'",
11479                                    errors.ECODE_INVAL)
11480       if not isinstance(self.op.disks, list):
11481         raise errors.OpPrereqError("Invalid parameter 'disks'",
11482                                    errors.ECODE_INVAL)
11483       for row in self.op.disks:
11484         if (not isinstance(row, dict) or
11485             "size" not in row or
11486             not isinstance(row["size"], int) or
11487             "mode" not in row or
11488             row["mode"] not in ['r', 'w']):
11489           raise errors.OpPrereqError("Invalid contents of the 'disks'"
11490                                      " parameter", errors.ECODE_INVAL)
11491       if self.op.hypervisor is None:
11492         self.op.hypervisor = self.cfg.GetHypervisorType()
11493     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
11494       fname = _ExpandInstanceName(self.cfg, self.op.name)
11495       self.op.name = fname
11496       self.relocate_from = self.cfg.GetInstanceInfo(fname).secondary_nodes
11497     elif self.op.mode == constants.IALLOCATOR_MODE_MEVAC:
11498       if not hasattr(self.op, "evac_nodes"):
11499         raise errors.OpPrereqError("Missing attribute 'evac_nodes' on"
11500                                    " opcode input", errors.ECODE_INVAL)
11501     else:
11502       raise errors.OpPrereqError("Invalid test allocator mode '%s'" %
11503                                  self.op.mode, errors.ECODE_INVAL)
11504
11505     if self.op.direction == constants.IALLOCATOR_DIR_OUT:
11506       if self.op.allocator is None:
11507         raise errors.OpPrereqError("Missing allocator name",
11508                                    errors.ECODE_INVAL)
11509     elif self.op.direction != constants.IALLOCATOR_DIR_IN:
11510       raise errors.OpPrereqError("Wrong allocator test '%s'" %
11511                                  self.op.direction, errors.ECODE_INVAL)
11512
11513   def Exec(self, feedback_fn):
11514     """Run the allocator test.
11515
11516     """
11517     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
11518       ial = IAllocator(self.cfg, self.rpc,
11519                        mode=self.op.mode,
11520                        name=self.op.name,
11521                        mem_size=self.op.mem_size,
11522                        disks=self.op.disks,
11523                        disk_template=self.op.disk_template,
11524                        os=self.op.os,
11525                        tags=self.op.tags,
11526                        nics=self.op.nics,
11527                        vcpus=self.op.vcpus,
11528                        hypervisor=self.op.hypervisor,
11529                        )
11530     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
11531       ial = IAllocator(self.cfg, self.rpc,
11532                        mode=self.op.mode,
11533                        name=self.op.name,
11534                        relocate_from=list(self.relocate_from),
11535                        )
11536     elif self.op.mode == constants.IALLOCATOR_MODE_MEVAC:
11537       ial = IAllocator(self.cfg, self.rpc,
11538                        mode=self.op.mode,
11539                        evac_nodes=self.op.evac_nodes)
11540     else:
11541       raise errors.ProgrammerError("Uncatched mode %s in"
11542                                    " LUTestAllocator.Exec", self.op.mode)
11543
11544     if self.op.direction == constants.IALLOCATOR_DIR_IN:
11545       result = ial.in_text
11546     else:
11547       ial.Run(self.op.allocator, validate=False)
11548       result = ial.out_text
11549     return result
11550
11551
11552 #: Query type implementations
11553 _QUERY_IMPL = {
11554   constants.QR_INSTANCE: _InstanceQuery,
11555   constants.QR_NODE: _NodeQuery,
11556   constants.QR_GROUP: _GroupQuery,
11557   }
11558
11559
11560 def _GetQueryImplementation(name):
11561   """Returns the implemtnation for a query type.
11562
11563   @param name: Query type, must be one of L{constants.QR_OP_QUERY}
11564
11565   """
11566   try:
11567     return _QUERY_IMPL[name]
11568   except KeyError:
11569     raise errors.OpPrereqError("Unknown query resource '%s'" % name,
11570                                errors.ECODE_INVAL)