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