LUClusterVerifyGroup: Localize virtual file paths
[ganeti-local] / lib / cmdlib.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Module implementing the master-side code."""
23
24 # pylint: disable=W0201,C0302
25
26 # W0201 since most LU attributes are defined in CheckPrereq or similar
27 # functions
28
29 # C0302: since we have waaaay too many lines in this module
30
31 import os
32 import os.path
33 import time
34 import re
35 import logging
36 import copy
37 import OpenSSL
38 import socket
39 import tempfile
40 import shutil
41 import itertools
42 import operator
43
44 from ganeti import ssh
45 from ganeti import utils
46 from ganeti import errors
47 from ganeti import hypervisor
48 from ganeti import locking
49 from ganeti import constants
50 from ganeti import objects
51 from ganeti import ssconf
52 from ganeti import uidpool
53 from ganeti import compat
54 from ganeti import masterd
55 from ganeti import netutils
56 from ganeti import query
57 from ganeti import qlang
58 from ganeti import opcodes
59 from ganeti import ht
60 from ganeti import rpc
61 from ganeti import runtime
62 from ganeti import pathutils
63 from ganeti import vcluster
64 from ganeti.masterd import iallocator
65
66 import ganeti.masterd.instance # pylint: disable=W0611
67
68
69 # States of instance
70 INSTANCE_DOWN = [constants.ADMINST_DOWN]
71 INSTANCE_ONLINE = [constants.ADMINST_DOWN, constants.ADMINST_UP]
72 INSTANCE_NOT_RUNNING = [constants.ADMINST_DOWN, constants.ADMINST_OFFLINE]
73
74 #: Instance status in which an instance can be marked as offline/online
75 CAN_CHANGE_INSTANCE_OFFLINE = (frozenset(INSTANCE_DOWN) | frozenset([
76   constants.ADMINST_OFFLINE,
77   ]))
78
79
80 class ResultWithJobs:
81   """Data container for LU results with jobs.
82
83   Instances of this class returned from L{LogicalUnit.Exec} will be recognized
84   by L{mcpu._ProcessResult}. The latter will then submit the jobs
85   contained in the C{jobs} attribute and include the job IDs in the opcode
86   result.
87
88   """
89   def __init__(self, jobs, **kwargs):
90     """Initializes this class.
91
92     Additional return values can be specified as keyword arguments.
93
94     @type jobs: list of lists of L{opcode.OpCode}
95     @param jobs: A list of lists of opcode objects
96
97     """
98     self.jobs = jobs
99     self.other = kwargs
100
101
102 class LogicalUnit(object):
103   """Logical Unit base class.
104
105   Subclasses must follow these rules:
106     - implement ExpandNames
107     - implement CheckPrereq (except when tasklets are used)
108     - implement Exec (except when tasklets are used)
109     - implement BuildHooksEnv
110     - implement BuildHooksNodes
111     - redefine HPATH and HTYPE
112     - optionally redefine their run requirements:
113         REQ_BGL: the LU needs to hold the Big Ganeti Lock exclusively
114
115   Note that all commands require root permissions.
116
117   @ivar dry_run_result: the value (if any) that will be returned to the caller
118       in dry-run mode (signalled by opcode dry_run parameter)
119
120   """
121   HPATH = None
122   HTYPE = None
123   REQ_BGL = True
124
125   def __init__(self, processor, op, context, rpc_runner):
126     """Constructor for LogicalUnit.
127
128     This needs to be overridden in derived classes in order to check op
129     validity.
130
131     """
132     self.proc = processor
133     self.op = op
134     self.cfg = context.cfg
135     self.glm = context.glm
136     # readability alias
137     self.owned_locks = context.glm.list_owned
138     self.context = context
139     self.rpc = rpc_runner
140     # Dicts used to declare locking needs to mcpu
141     self.needed_locks = None
142     self.share_locks = dict.fromkeys(locking.LEVELS, 0)
143     self.add_locks = {}
144     self.remove_locks = {}
145     # Used to force good behavior when calling helper functions
146     self.recalculate_locks = {}
147     # logging
148     self.Log = processor.Log # pylint: disable=C0103
149     self.LogWarning = processor.LogWarning # pylint: disable=C0103
150     self.LogInfo = processor.LogInfo # pylint: disable=C0103
151     self.LogStep = processor.LogStep # pylint: disable=C0103
152     # support for dry-run
153     self.dry_run_result = None
154     # support for generic debug attribute
155     if (not hasattr(self.op, "debug_level") or
156         not isinstance(self.op.debug_level, int)):
157       self.op.debug_level = 0
158
159     # Tasklets
160     self.tasklets = None
161
162     # Validate opcode parameters and set defaults
163     self.op.Validate(True)
164
165     self.CheckArguments()
166
167   def CheckArguments(self):
168     """Check syntactic validity for the opcode arguments.
169
170     This method is for doing a simple syntactic check and ensure
171     validity of opcode parameters, without any cluster-related
172     checks. While the same can be accomplished in ExpandNames and/or
173     CheckPrereq, doing these separate is better because:
174
175       - ExpandNames is left as as purely a lock-related function
176       - CheckPrereq is run after we have acquired locks (and possible
177         waited for them)
178
179     The function is allowed to change the self.op attribute so that
180     later methods can no longer worry about missing parameters.
181
182     """
183     pass
184
185   def ExpandNames(self):
186     """Expand names for this LU.
187
188     This method is called before starting to execute the opcode, and it should
189     update all the parameters of the opcode to their canonical form (e.g. a
190     short node name must be fully expanded after this method has successfully
191     completed). This way locking, hooks, logging, etc. can work correctly.
192
193     LUs which implement this method must also populate the self.needed_locks
194     member, as a dict with lock levels as keys, and a list of needed lock names
195     as values. Rules:
196
197       - use an empty dict if you don't need any lock
198       - if you don't need any lock at a particular level omit that
199         level (note that in this case C{DeclareLocks} won't be called
200         at all for that level)
201       - if you need locks at a level, but you can't calculate it in
202         this function, initialise that level with an empty list and do
203         further processing in L{LogicalUnit.DeclareLocks} (see that
204         function's docstring)
205       - don't put anything for the BGL level
206       - if you want all locks at a level use L{locking.ALL_SET} as a value
207
208     If you need to share locks (rather than acquire them exclusively) at one
209     level you can modify self.share_locks, setting a true value (usually 1) for
210     that level. By default locks are not shared.
211
212     This function can also define a list of tasklets, which then will be
213     executed in order instead of the usual LU-level CheckPrereq and Exec
214     functions, if those are not defined by the LU.
215
216     Examples::
217
218       # Acquire all nodes and one instance
219       self.needed_locks = {
220         locking.LEVEL_NODE: locking.ALL_SET,
221         locking.LEVEL_INSTANCE: ['instance1.example.com'],
222       }
223       # Acquire just two nodes
224       self.needed_locks = {
225         locking.LEVEL_NODE: ['node1.example.com', 'node2.example.com'],
226       }
227       # Acquire no locks
228       self.needed_locks = {} # No, you can't leave it to the default value None
229
230     """
231     # The implementation of this method is mandatory only if the new LU is
232     # concurrent, so that old LUs don't need to be changed all at the same
233     # time.
234     if self.REQ_BGL:
235       self.needed_locks = {} # Exclusive LUs don't need locks.
236     else:
237       raise NotImplementedError
238
239   def DeclareLocks(self, level):
240     """Declare LU locking needs for a level
241
242     While most LUs can just declare their locking needs at ExpandNames time,
243     sometimes there's the need to calculate some locks after having acquired
244     the ones before. This function is called just before acquiring locks at a
245     particular level, but after acquiring the ones at lower levels, and permits
246     such calculations. It can be used to modify self.needed_locks, and by
247     default it does nothing.
248
249     This function is only called if you have something already set in
250     self.needed_locks for the level.
251
252     @param level: Locking level which is going to be locked
253     @type level: member of L{ganeti.locking.LEVELS}
254
255     """
256
257   def CheckPrereq(self):
258     """Check prerequisites for this LU.
259
260     This method should check that the prerequisites for the execution
261     of this LU are fulfilled. It can do internode communication, but
262     it should be idempotent - no cluster or system changes are
263     allowed.
264
265     The method should raise errors.OpPrereqError in case something is
266     not fulfilled. Its return value is ignored.
267
268     This method should also update all the parameters of the opcode to
269     their canonical form if it hasn't been done by ExpandNames before.
270
271     """
272     if self.tasklets is not None:
273       for (idx, tl) in enumerate(self.tasklets):
274         logging.debug("Checking prerequisites for tasklet %s/%s",
275                       idx + 1, len(self.tasklets))
276         tl.CheckPrereq()
277     else:
278       pass
279
280   def Exec(self, feedback_fn):
281     """Execute the LU.
282
283     This method should implement the actual work. It should raise
284     errors.OpExecError for failures that are somewhat dealt with in
285     code, or expected.
286
287     """
288     if self.tasklets is not None:
289       for (idx, tl) in enumerate(self.tasklets):
290         logging.debug("Executing tasklet %s/%s", idx + 1, len(self.tasklets))
291         tl.Exec(feedback_fn)
292     else:
293       raise NotImplementedError
294
295   def BuildHooksEnv(self):
296     """Build hooks environment for this LU.
297
298     @rtype: dict
299     @return: Dictionary containing the environment that will be used for
300       running the hooks for this LU. The keys of the dict must not be prefixed
301       with "GANETI_"--that'll be added by the hooks runner. The hooks runner
302       will extend the environment with additional variables. If no environment
303       should be defined, an empty dictionary should be returned (not C{None}).
304     @note: If the C{HPATH} attribute of the LU class is C{None}, this function
305       will not be called.
306
307     """
308     raise NotImplementedError
309
310   def BuildHooksNodes(self):
311     """Build list of nodes to run LU's hooks.
312
313     @rtype: tuple; (list, list)
314     @return: Tuple containing a list of node names on which the hook
315       should run before the execution and a list of node names on which the
316       hook should run after the execution. No nodes should be returned as an
317       empty list (and not None).
318     @note: If the C{HPATH} attribute of the LU class is C{None}, this function
319       will not be called.
320
321     """
322     raise NotImplementedError
323
324   def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result):
325     """Notify the LU about the results of its hooks.
326
327     This method is called every time a hooks phase is executed, and notifies
328     the Logical Unit about the hooks' result. The LU can then use it to alter
329     its result based on the hooks.  By default the method does nothing and the
330     previous result is passed back unchanged but any LU can define it if it
331     wants to use the local cluster hook-scripts somehow.
332
333     @param phase: one of L{constants.HOOKS_PHASE_POST} or
334         L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
335     @param hook_results: the results of the multi-node hooks rpc call
336     @param feedback_fn: function used send feedback back to the caller
337     @param lu_result: the previous Exec result this LU had, or None
338         in the PRE phase
339     @return: the new Exec result, based on the previous result
340         and hook results
341
342     """
343     # API must be kept, thus we ignore the unused argument and could
344     # be a function warnings
345     # pylint: disable=W0613,R0201
346     return lu_result
347
348   def _ExpandAndLockInstance(self):
349     """Helper function to expand and lock an instance.
350
351     Many LUs that work on an instance take its name in self.op.instance_name
352     and need to expand it and then declare the expanded name for locking. This
353     function does it, and then updates self.op.instance_name to the expanded
354     name. It also initializes needed_locks as a dict, if this hasn't been done
355     before.
356
357     """
358     if self.needed_locks is None:
359       self.needed_locks = {}
360     else:
361       assert locking.LEVEL_INSTANCE not in self.needed_locks, \
362         "_ExpandAndLockInstance called with instance-level locks set"
363     self.op.instance_name = _ExpandInstanceName(self.cfg,
364                                                 self.op.instance_name)
365     self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
366
367   def _LockInstancesNodes(self, primary_only=False,
368                           level=locking.LEVEL_NODE):
369     """Helper function to declare instances' nodes for locking.
370
371     This function should be called after locking one or more instances to lock
372     their nodes. Its effect is populating self.needed_locks[locking.LEVEL_NODE]
373     with all primary or secondary nodes for instances already locked and
374     present in self.needed_locks[locking.LEVEL_INSTANCE].
375
376     It should be called from DeclareLocks, and for safety only works if
377     self.recalculate_locks[locking.LEVEL_NODE] is set.
378
379     In the future it may grow parameters to just lock some instance's nodes, or
380     to just lock primaries or secondary nodes, if needed.
381
382     If should be called in DeclareLocks in a way similar to::
383
384       if level == locking.LEVEL_NODE:
385         self._LockInstancesNodes()
386
387     @type primary_only: boolean
388     @param primary_only: only lock primary nodes of locked instances
389     @param level: Which lock level to use for locking nodes
390
391     """
392     assert level in self.recalculate_locks, \
393       "_LockInstancesNodes helper function called with no nodes to recalculate"
394
395     # TODO: check if we're really been called with the instance locks held
396
397     # For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the
398     # future we might want to have different behaviors depending on the value
399     # of self.recalculate_locks[locking.LEVEL_NODE]
400     wanted_nodes = []
401     locked_i = self.owned_locks(locking.LEVEL_INSTANCE)
402     for _, instance in self.cfg.GetMultiInstanceInfo(locked_i):
403       wanted_nodes.append(instance.primary_node)
404       if not primary_only:
405         wanted_nodes.extend(instance.secondary_nodes)
406
407     if self.recalculate_locks[level] == constants.LOCKS_REPLACE:
408       self.needed_locks[level] = wanted_nodes
409     elif self.recalculate_locks[level] == constants.LOCKS_APPEND:
410       self.needed_locks[level].extend(wanted_nodes)
411     else:
412       raise errors.ProgrammerError("Unknown recalculation mode")
413
414     del self.recalculate_locks[level]
415
416
417 class NoHooksLU(LogicalUnit): # pylint: disable=W0223
418   """Simple LU which runs no hooks.
419
420   This LU is intended as a parent for other LogicalUnits which will
421   run no hooks, in order to reduce duplicate code.
422
423   """
424   HPATH = None
425   HTYPE = None
426
427   def BuildHooksEnv(self):
428     """Empty BuildHooksEnv for NoHooksLu.
429
430     This just raises an error.
431
432     """
433     raise AssertionError("BuildHooksEnv called for NoHooksLUs")
434
435   def BuildHooksNodes(self):
436     """Empty BuildHooksNodes for NoHooksLU.
437
438     """
439     raise AssertionError("BuildHooksNodes called for NoHooksLU")
440
441
442 class Tasklet:
443   """Tasklet base class.
444
445   Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
446   they can mix legacy code with tasklets. Locking needs to be done in the LU,
447   tasklets know nothing about locks.
448
449   Subclasses must follow these rules:
450     - Implement CheckPrereq
451     - Implement Exec
452
453   """
454   def __init__(self, lu):
455     self.lu = lu
456
457     # Shortcuts
458     self.cfg = lu.cfg
459     self.rpc = lu.rpc
460
461   def CheckPrereq(self):
462     """Check prerequisites for this tasklets.
463
464     This method should check whether the prerequisites for the execution of
465     this tasklet are fulfilled. It can do internode communication, but it
466     should be idempotent - no cluster or system changes are allowed.
467
468     The method should raise errors.OpPrereqError in case something is not
469     fulfilled. Its return value is ignored.
470
471     This method should also update all parameters to their canonical form if it
472     hasn't been done before.
473
474     """
475     pass
476
477   def Exec(self, feedback_fn):
478     """Execute the tasklet.
479
480     This method should implement the actual work. It should raise
481     errors.OpExecError for failures that are somewhat dealt with in code, or
482     expected.
483
484     """
485     raise NotImplementedError
486
487
488 class _QueryBase:
489   """Base for query utility classes.
490
491   """
492   #: Attribute holding field definitions
493   FIELDS = None
494
495   #: Field to sort by
496   SORT_FIELD = "name"
497
498   def __init__(self, qfilter, fields, use_locking):
499     """Initializes this class.
500
501     """
502     self.use_locking = use_locking
503
504     self.query = query.Query(self.FIELDS, fields, qfilter=qfilter,
505                              namefield=self.SORT_FIELD)
506     self.requested_data = self.query.RequestedData()
507     self.names = self.query.RequestedNames()
508
509     # Sort only if no names were requested
510     self.sort_by_name = not self.names
511
512     self.do_locking = None
513     self.wanted = None
514
515   def _GetNames(self, lu, all_names, lock_level):
516     """Helper function to determine names asked for in the query.
517
518     """
519     if self.do_locking:
520       names = lu.owned_locks(lock_level)
521     else:
522       names = all_names
523
524     if self.wanted == locking.ALL_SET:
525       assert not self.names
526       # caller didn't specify names, so ordering is not important
527       return utils.NiceSort(names)
528
529     # caller specified names and we must keep the same order
530     assert self.names
531     assert not self.do_locking or lu.glm.is_owned(lock_level)
532
533     missing = set(self.wanted).difference(names)
534     if missing:
535       raise errors.OpExecError("Some items were removed before retrieving"
536                                " their data: %s" % missing)
537
538     # Return expanded names
539     return self.wanted
540
541   def ExpandNames(self, lu):
542     """Expand names for this query.
543
544     See L{LogicalUnit.ExpandNames}.
545
546     """
547     raise NotImplementedError()
548
549   def DeclareLocks(self, lu, level):
550     """Declare locks for this query.
551
552     See L{LogicalUnit.DeclareLocks}.
553
554     """
555     raise NotImplementedError()
556
557   def _GetQueryData(self, lu):
558     """Collects all data for this query.
559
560     @return: Query data object
561
562     """
563     raise NotImplementedError()
564
565   def NewStyleQuery(self, lu):
566     """Collect data and execute query.
567
568     """
569     return query.GetQueryResponse(self.query, self._GetQueryData(lu),
570                                   sort_by_name=self.sort_by_name)
571
572   def OldStyleQuery(self, lu):
573     """Collect data and execute query.
574
575     """
576     return self.query.OldStyleQuery(self._GetQueryData(lu),
577                                     sort_by_name=self.sort_by_name)
578
579
580 def _ShareAll():
581   """Returns a dict declaring all lock levels shared.
582
583   """
584   return dict.fromkeys(locking.LEVELS, 1)
585
586
587 def _AnnotateDiskParams(instance, devs, cfg):
588   """Little helper wrapper to the rpc annotation method.
589
590   @param instance: The instance object
591   @type devs: List of L{objects.Disk}
592   @param devs: The root devices (not any of its children!)
593   @param cfg: The config object
594   @returns The annotated disk copies
595   @see L{rpc.AnnotateDiskParams}
596
597   """
598   return rpc.AnnotateDiskParams(instance.disk_template, devs,
599                                 cfg.GetInstanceDiskParams(instance))
600
601
602 def _CheckInstancesNodeGroups(cfg, instances, owned_groups, owned_nodes,
603                               cur_group_uuid):
604   """Checks if node groups for locked instances are still correct.
605
606   @type cfg: L{config.ConfigWriter}
607   @param cfg: Cluster configuration
608   @type instances: dict; string as key, L{objects.Instance} as value
609   @param instances: Dictionary, instance name as key, instance object as value
610   @type owned_groups: iterable of string
611   @param owned_groups: List of owned groups
612   @type owned_nodes: iterable of string
613   @param owned_nodes: List of owned nodes
614   @type cur_group_uuid: string or None
615   @param cur_group_uuid: Optional group UUID to check against instance's groups
616
617   """
618   for (name, inst) in instances.items():
619     assert owned_nodes.issuperset(inst.all_nodes), \
620       "Instance %s's nodes changed while we kept the lock" % name
621
622     inst_groups = _CheckInstanceNodeGroups(cfg, name, owned_groups)
623
624     assert cur_group_uuid is None or cur_group_uuid in inst_groups, \
625       "Instance %s has no node in group %s" % (name, cur_group_uuid)
626
627
628 def _CheckInstanceNodeGroups(cfg, instance_name, owned_groups,
629                              primary_only=False):
630   """Checks if the owned node groups are still correct for an instance.
631
632   @type cfg: L{config.ConfigWriter}
633   @param cfg: The cluster configuration
634   @type instance_name: string
635   @param instance_name: Instance name
636   @type owned_groups: set or frozenset
637   @param owned_groups: List of currently owned node groups
638   @type primary_only: boolean
639   @param primary_only: Whether to check node groups for only the primary node
640
641   """
642   inst_groups = cfg.GetInstanceNodeGroups(instance_name, primary_only)
643
644   if not owned_groups.issuperset(inst_groups):
645     raise errors.OpPrereqError("Instance %s's node groups changed since"
646                                " locks were acquired, current groups are"
647                                " are '%s', owning groups '%s'; retry the"
648                                " operation" %
649                                (instance_name,
650                                 utils.CommaJoin(inst_groups),
651                                 utils.CommaJoin(owned_groups)),
652                                errors.ECODE_STATE)
653
654   return inst_groups
655
656
657 def _CheckNodeGroupInstances(cfg, group_uuid, owned_instances):
658   """Checks if the instances in a node group are still correct.
659
660   @type cfg: L{config.ConfigWriter}
661   @param cfg: The cluster configuration
662   @type group_uuid: string
663   @param group_uuid: Node group UUID
664   @type owned_instances: set or frozenset
665   @param owned_instances: List of currently owned instances
666
667   """
668   wanted_instances = cfg.GetNodeGroupInstances(group_uuid)
669   if owned_instances != wanted_instances:
670     raise errors.OpPrereqError("Instances in node group '%s' changed since"
671                                " locks were acquired, wanted '%s', have '%s';"
672                                " retry the operation" %
673                                (group_uuid,
674                                 utils.CommaJoin(wanted_instances),
675                                 utils.CommaJoin(owned_instances)),
676                                errors.ECODE_STATE)
677
678   return wanted_instances
679
680
681 def _SupportsOob(cfg, node):
682   """Tells if node supports OOB.
683
684   @type cfg: L{config.ConfigWriter}
685   @param cfg: The cluster configuration
686   @type node: L{objects.Node}
687   @param node: The node
688   @return: The OOB script if supported or an empty string otherwise
689
690   """
691   return cfg.GetNdParams(node)[constants.ND_OOB_PROGRAM]
692
693
694 def _CopyLockList(names):
695   """Makes a copy of a list of lock names.
696
697   Handles L{locking.ALL_SET} correctly.
698
699   """
700   if names == locking.ALL_SET:
701     return locking.ALL_SET
702   else:
703     return names[:]
704
705
706 def _GetWantedNodes(lu, nodes):
707   """Returns list of checked and expanded node names.
708
709   @type lu: L{LogicalUnit}
710   @param lu: the logical unit on whose behalf we execute
711   @type nodes: list
712   @param nodes: list of node names or None for all nodes
713   @rtype: list
714   @return: the list of nodes, sorted
715   @raise errors.ProgrammerError: if the nodes parameter is wrong type
716
717   """
718   if nodes:
719     return [_ExpandNodeName(lu.cfg, name) for name in nodes]
720
721   return utils.NiceSort(lu.cfg.GetNodeList())
722
723
724 def _GetWantedInstances(lu, instances):
725   """Returns list of checked and expanded instance names.
726
727   @type lu: L{LogicalUnit}
728   @param lu: the logical unit on whose behalf we execute
729   @type instances: list
730   @param instances: list of instance names or None for all instances
731   @rtype: list
732   @return: the list of instances, sorted
733   @raise errors.OpPrereqError: if the instances parameter is wrong type
734   @raise errors.OpPrereqError: if any of the passed instances is not found
735
736   """
737   if instances:
738     wanted = [_ExpandInstanceName(lu.cfg, name) for name in instances]
739   else:
740     wanted = utils.NiceSort(lu.cfg.GetInstanceList())
741   return wanted
742
743
744 def _GetUpdatedParams(old_params, update_dict,
745                       use_default=True, use_none=False):
746   """Return the new version of a parameter dictionary.
747
748   @type old_params: dict
749   @param old_params: old parameters
750   @type update_dict: dict
751   @param update_dict: dict containing new parameter values, or
752       constants.VALUE_DEFAULT to reset the parameter to its default
753       value
754   @param use_default: boolean
755   @type use_default: whether to recognise L{constants.VALUE_DEFAULT}
756       values as 'to be deleted' values
757   @param use_none: boolean
758   @type use_none: whether to recognise C{None} values as 'to be
759       deleted' values
760   @rtype: dict
761   @return: the new parameter dictionary
762
763   """
764   params_copy = copy.deepcopy(old_params)
765   for key, val in update_dict.iteritems():
766     if ((use_default and val == constants.VALUE_DEFAULT) or
767         (use_none and val is None)):
768       try:
769         del params_copy[key]
770       except KeyError:
771         pass
772     else:
773       params_copy[key] = val
774   return params_copy
775
776
777 def _GetUpdatedIPolicy(old_ipolicy, new_ipolicy, group_policy=False):
778   """Return the new version of a instance policy.
779
780   @param group_policy: whether this policy applies to a group and thus
781     we should support removal of policy entries
782
783   """
784   use_none = use_default = group_policy
785   ipolicy = copy.deepcopy(old_ipolicy)
786   for key, value in new_ipolicy.items():
787     if key not in constants.IPOLICY_ALL_KEYS:
788       raise errors.OpPrereqError("Invalid key in new ipolicy: %s" % key,
789                                  errors.ECODE_INVAL)
790     if key in constants.IPOLICY_ISPECS:
791       utils.ForceDictType(value, constants.ISPECS_PARAMETER_TYPES)
792       ipolicy[key] = _GetUpdatedParams(old_ipolicy.get(key, {}), value,
793                                        use_none=use_none,
794                                        use_default=use_default)
795     else:
796       if (not value or value == [constants.VALUE_DEFAULT] or
797           value == constants.VALUE_DEFAULT):
798         if group_policy:
799           del ipolicy[key]
800         else:
801           raise errors.OpPrereqError("Can't unset ipolicy attribute '%s'"
802                                      " on the cluster'" % key,
803                                      errors.ECODE_INVAL)
804       else:
805         if key in constants.IPOLICY_PARAMETERS:
806           # FIXME: we assume all such values are float
807           try:
808             ipolicy[key] = float(value)
809           except (TypeError, ValueError), err:
810             raise errors.OpPrereqError("Invalid value for attribute"
811                                        " '%s': '%s', error: %s" %
812                                        (key, value, err), errors.ECODE_INVAL)
813         else:
814           # FIXME: we assume all others are lists; this should be redone
815           # in a nicer way
816           ipolicy[key] = list(value)
817   try:
818     objects.InstancePolicy.CheckParameterSyntax(ipolicy, not group_policy)
819   except errors.ConfigurationError, err:
820     raise errors.OpPrereqError("Invalid instance policy: %s" % err,
821                                errors.ECODE_INVAL)
822   return ipolicy
823
824
825 def _UpdateAndVerifySubDict(base, updates, type_check):
826   """Updates and verifies a dict with sub dicts of the same type.
827
828   @param base: The dict with the old data
829   @param updates: The dict with the new data
830   @param type_check: Dict suitable to ForceDictType to verify correct types
831   @returns: A new dict with updated and verified values
832
833   """
834   def fn(old, value):
835     new = _GetUpdatedParams(old, value)
836     utils.ForceDictType(new, type_check)
837     return new
838
839   ret = copy.deepcopy(base)
840   ret.update(dict((key, fn(base.get(key, {}), value))
841                   for key, value in updates.items()))
842   return ret
843
844
845 def _MergeAndVerifyHvState(op_input, obj_input):
846   """Combines the hv state from an opcode with the one of the object
847
848   @param op_input: The input dict from the opcode
849   @param obj_input: The input dict from the objects
850   @return: The verified and updated dict
851
852   """
853   if op_input:
854     invalid_hvs = set(op_input) - constants.HYPER_TYPES
855     if invalid_hvs:
856       raise errors.OpPrereqError("Invalid hypervisor(s) in hypervisor state:"
857                                  " %s" % utils.CommaJoin(invalid_hvs),
858                                  errors.ECODE_INVAL)
859     if obj_input is None:
860       obj_input = {}
861     type_check = constants.HVSTS_PARAMETER_TYPES
862     return _UpdateAndVerifySubDict(obj_input, op_input, type_check)
863
864   return None
865
866
867 def _MergeAndVerifyDiskState(op_input, obj_input):
868   """Combines the disk state from an opcode with the one of the object
869
870   @param op_input: The input dict from the opcode
871   @param obj_input: The input dict from the objects
872   @return: The verified and updated dict
873   """
874   if op_input:
875     invalid_dst = set(op_input) - constants.DS_VALID_TYPES
876     if invalid_dst:
877       raise errors.OpPrereqError("Invalid storage type(s) in disk state: %s" %
878                                  utils.CommaJoin(invalid_dst),
879                                  errors.ECODE_INVAL)
880     type_check = constants.DSS_PARAMETER_TYPES
881     if obj_input is None:
882       obj_input = {}
883     return dict((key, _UpdateAndVerifySubDict(obj_input.get(key, {}), value,
884                                               type_check))
885                 for key, value in op_input.items())
886
887   return None
888
889
890 def _ReleaseLocks(lu, level, names=None, keep=None):
891   """Releases locks owned by an LU.
892
893   @type lu: L{LogicalUnit}
894   @param level: Lock level
895   @type names: list or None
896   @param names: Names of locks to release
897   @type keep: list or None
898   @param keep: Names of locks to retain
899
900   """
901   assert not (keep is not None and names is not None), \
902          "Only one of the 'names' and the 'keep' parameters can be given"
903
904   if names is not None:
905     should_release = names.__contains__
906   elif keep:
907     should_release = lambda name: name not in keep
908   else:
909     should_release = None
910
911   owned = lu.owned_locks(level)
912   if not owned:
913     # Not owning any lock at this level, do nothing
914     pass
915
916   elif should_release:
917     retain = []
918     release = []
919
920     # Determine which locks to release
921     for name in owned:
922       if should_release(name):
923         release.append(name)
924       else:
925         retain.append(name)
926
927     assert len(lu.owned_locks(level)) == (len(retain) + len(release))
928
929     # Release just some locks
930     lu.glm.release(level, names=release)
931
932     assert frozenset(lu.owned_locks(level)) == frozenset(retain)
933   else:
934     # Release everything
935     lu.glm.release(level)
936
937     assert not lu.glm.is_owned(level), "No locks should be owned"
938
939
940 def _MapInstanceDisksToNodes(instances):
941   """Creates a map from (node, volume) to instance name.
942
943   @type instances: list of L{objects.Instance}
944   @rtype: dict; tuple of (node name, volume name) as key, instance name as value
945
946   """
947   return dict(((node, vol), inst.name)
948               for inst in instances
949               for (node, vols) in inst.MapLVsByNode().items()
950               for vol in vols)
951
952
953 def _RunPostHook(lu, node_name):
954   """Runs the post-hook for an opcode on a single node.
955
956   """
957   hm = lu.proc.BuildHooksManager(lu)
958   try:
959     hm.RunPhase(constants.HOOKS_PHASE_POST, nodes=[node_name])
960   except Exception, err: # pylint: disable=W0703
961     lu.LogWarning("Errors occurred running hooks on %s: %s" % (node_name, err))
962
963
964 def _CheckOutputFields(static, dynamic, selected):
965   """Checks whether all selected fields are valid.
966
967   @type static: L{utils.FieldSet}
968   @param static: static fields set
969   @type dynamic: L{utils.FieldSet}
970   @param dynamic: dynamic fields set
971
972   """
973   f = utils.FieldSet()
974   f.Extend(static)
975   f.Extend(dynamic)
976
977   delta = f.NonMatching(selected)
978   if delta:
979     raise errors.OpPrereqError("Unknown output fields selected: %s"
980                                % ",".join(delta), errors.ECODE_INVAL)
981
982
983 def _CheckGlobalHvParams(params):
984   """Validates that given hypervisor params are not global ones.
985
986   This will ensure that instances don't get customised versions of
987   global params.
988
989   """
990   used_globals = constants.HVC_GLOBALS.intersection(params)
991   if used_globals:
992     msg = ("The following hypervisor parameters are global and cannot"
993            " be customized at instance level, please modify them at"
994            " cluster level: %s" % utils.CommaJoin(used_globals))
995     raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
996
997
998 def _CheckNodeOnline(lu, node, msg=None):
999   """Ensure that a given node is online.
1000
1001   @param lu: the LU on behalf of which we make the check
1002   @param node: the node to check
1003   @param msg: if passed, should be a message to replace the default one
1004   @raise errors.OpPrereqError: if the node is offline
1005
1006   """
1007   if msg is None:
1008     msg = "Can't use offline node"
1009   if lu.cfg.GetNodeInfo(node).offline:
1010     raise errors.OpPrereqError("%s: %s" % (msg, node), errors.ECODE_STATE)
1011
1012
1013 def _CheckNodeNotDrained(lu, node):
1014   """Ensure that a given node is not drained.
1015
1016   @param lu: the LU on behalf of which we make the check
1017   @param node: the node to check
1018   @raise errors.OpPrereqError: if the node is drained
1019
1020   """
1021   if lu.cfg.GetNodeInfo(node).drained:
1022     raise errors.OpPrereqError("Can't use drained node %s" % node,
1023                                errors.ECODE_STATE)
1024
1025
1026 def _CheckNodeVmCapable(lu, node):
1027   """Ensure that a given node is vm capable.
1028
1029   @param lu: the LU on behalf of which we make the check
1030   @param node: the node to check
1031   @raise errors.OpPrereqError: if the node is not vm capable
1032
1033   """
1034   if not lu.cfg.GetNodeInfo(node).vm_capable:
1035     raise errors.OpPrereqError("Can't use non-vm_capable node %s" % node,
1036                                errors.ECODE_STATE)
1037
1038
1039 def _CheckNodeHasOS(lu, node, os_name, force_variant):
1040   """Ensure that a node supports a given OS.
1041
1042   @param lu: the LU on behalf of which we make the check
1043   @param node: the node to check
1044   @param os_name: the OS to query about
1045   @param force_variant: whether to ignore variant errors
1046   @raise errors.OpPrereqError: if the node is not supporting the OS
1047
1048   """
1049   result = lu.rpc.call_os_get(node, os_name)
1050   result.Raise("OS '%s' not in supported OS list for node %s" %
1051                (os_name, node),
1052                prereq=True, ecode=errors.ECODE_INVAL)
1053   if not force_variant:
1054     _CheckOSVariant(result.payload, os_name)
1055
1056
1057 def _CheckNodeHasSecondaryIP(lu, node, secondary_ip, prereq):
1058   """Ensure that a node has the given secondary ip.
1059
1060   @type lu: L{LogicalUnit}
1061   @param lu: the LU on behalf of which we make the check
1062   @type node: string
1063   @param node: the node to check
1064   @type secondary_ip: string
1065   @param secondary_ip: the ip to check
1066   @type prereq: boolean
1067   @param prereq: whether to throw a prerequisite or an execute error
1068   @raise errors.OpPrereqError: if the node doesn't have the ip, and prereq=True
1069   @raise errors.OpExecError: if the node doesn't have the ip, and prereq=False
1070
1071   """
1072   result = lu.rpc.call_node_has_ip_address(node, secondary_ip)
1073   result.Raise("Failure checking secondary ip on node %s" % node,
1074                prereq=prereq, ecode=errors.ECODE_ENVIRON)
1075   if not result.payload:
1076     msg = ("Node claims it doesn't have the secondary ip you gave (%s),"
1077            " please fix and re-run this command" % secondary_ip)
1078     if prereq:
1079       raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
1080     else:
1081       raise errors.OpExecError(msg)
1082
1083
1084 def _GetClusterDomainSecret():
1085   """Reads the cluster domain secret.
1086
1087   """
1088   return utils.ReadOneLineFile(pathutils.CLUSTER_DOMAIN_SECRET_FILE,
1089                                strict=True)
1090
1091
1092 def _CheckInstanceState(lu, instance, req_states, msg=None):
1093   """Ensure that an instance is in one of the required states.
1094
1095   @param lu: the LU on behalf of which we make the check
1096   @param instance: the instance to check
1097   @param msg: if passed, should be a message to replace the default one
1098   @raise errors.OpPrereqError: if the instance is not in the required state
1099
1100   """
1101   if msg is None:
1102     msg = "can't use instance from outside %s states" % ", ".join(req_states)
1103   if instance.admin_state not in req_states:
1104     raise errors.OpPrereqError("Instance '%s' is marked to be %s, %s" %
1105                                (instance.name, instance.admin_state, msg),
1106                                errors.ECODE_STATE)
1107
1108   if constants.ADMINST_UP not in req_states:
1109     pnode = instance.primary_node
1110     if not lu.cfg.GetNodeInfo(pnode).offline:
1111       ins_l = lu.rpc.call_instance_list([pnode], [instance.hypervisor])[pnode]
1112       ins_l.Raise("Can't contact node %s for instance information" % pnode,
1113                   prereq=True, ecode=errors.ECODE_ENVIRON)
1114       if instance.name in ins_l.payload:
1115         raise errors.OpPrereqError("Instance %s is running, %s" %
1116                                    (instance.name, msg), errors.ECODE_STATE)
1117     else:
1118       lu.LogWarning("Primary node offline, ignoring check that instance"
1119                      " is down")
1120
1121
1122 def _ComputeMinMaxSpec(name, qualifier, ipolicy, value):
1123   """Computes if value is in the desired range.
1124
1125   @param name: name of the parameter for which we perform the check
1126   @param qualifier: a qualifier used in the error message (e.g. 'disk/1',
1127       not just 'disk')
1128   @param ipolicy: dictionary containing min, max and std values
1129   @param value: actual value that we want to use
1130   @return: None or element not meeting the criteria
1131
1132
1133   """
1134   if value in [None, constants.VALUE_AUTO]:
1135     return None
1136   max_v = ipolicy[constants.ISPECS_MAX].get(name, value)
1137   min_v = ipolicy[constants.ISPECS_MIN].get(name, value)
1138   if value > max_v or min_v > value:
1139     if qualifier:
1140       fqn = "%s/%s" % (name, qualifier)
1141     else:
1142       fqn = name
1143     return ("%s value %s is not in range [%s, %s]" %
1144             (fqn, value, min_v, max_v))
1145   return None
1146
1147
1148 def _ComputeIPolicySpecViolation(ipolicy, mem_size, cpu_count, disk_count,
1149                                  nic_count, disk_sizes, spindle_use,
1150                                  _compute_fn=_ComputeMinMaxSpec):
1151   """Verifies ipolicy against provided specs.
1152
1153   @type ipolicy: dict
1154   @param ipolicy: The ipolicy
1155   @type mem_size: int
1156   @param mem_size: The memory size
1157   @type cpu_count: int
1158   @param cpu_count: Used cpu cores
1159   @type disk_count: int
1160   @param disk_count: Number of disks used
1161   @type nic_count: int
1162   @param nic_count: Number of nics used
1163   @type disk_sizes: list of ints
1164   @param disk_sizes: Disk sizes of used disk (len must match C{disk_count})
1165   @type spindle_use: int
1166   @param spindle_use: The number of spindles this instance uses
1167   @param _compute_fn: The compute function (unittest only)
1168   @return: A list of violations, or an empty list of no violations are found
1169
1170   """
1171   assert disk_count == len(disk_sizes)
1172
1173   test_settings = [
1174     (constants.ISPEC_MEM_SIZE, "", mem_size),
1175     (constants.ISPEC_CPU_COUNT, "", cpu_count),
1176     (constants.ISPEC_DISK_COUNT, "", disk_count),
1177     (constants.ISPEC_NIC_COUNT, "", nic_count),
1178     (constants.ISPEC_SPINDLE_USE, "", spindle_use),
1179     ] + [(constants.ISPEC_DISK_SIZE, str(idx), d)
1180          for idx, d in enumerate(disk_sizes)]
1181
1182   return filter(None,
1183                 (_compute_fn(name, qualifier, ipolicy, value)
1184                  for (name, qualifier, value) in test_settings))
1185
1186
1187 def _ComputeIPolicyInstanceViolation(ipolicy, instance,
1188                                      _compute_fn=_ComputeIPolicySpecViolation):
1189   """Compute if instance meets the specs of ipolicy.
1190
1191   @type ipolicy: dict
1192   @param ipolicy: The ipolicy to verify against
1193   @type instance: L{objects.Instance}
1194   @param instance: The instance to verify
1195   @param _compute_fn: The function to verify ipolicy (unittest only)
1196   @see: L{_ComputeIPolicySpecViolation}
1197
1198   """
1199   mem_size = instance.beparams.get(constants.BE_MAXMEM, None)
1200   cpu_count = instance.beparams.get(constants.BE_VCPUS, None)
1201   spindle_use = instance.beparams.get(constants.BE_SPINDLE_USE, None)
1202   disk_count = len(instance.disks)
1203   disk_sizes = [disk.size for disk in instance.disks]
1204   nic_count = len(instance.nics)
1205
1206   return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
1207                      disk_sizes, spindle_use)
1208
1209
1210 def _ComputeIPolicyInstanceSpecViolation(
1211   ipolicy, instance_spec, _compute_fn=_ComputeIPolicySpecViolation):
1212   """Compute if instance specs meets the specs of ipolicy.
1213
1214   @type ipolicy: dict
1215   @param ipolicy: The ipolicy to verify against
1216   @param instance_spec: dict
1217   @param instance_spec: The instance spec to verify
1218   @param _compute_fn: The function to verify ipolicy (unittest only)
1219   @see: L{_ComputeIPolicySpecViolation}
1220
1221   """
1222   mem_size = instance_spec.get(constants.ISPEC_MEM_SIZE, None)
1223   cpu_count = instance_spec.get(constants.ISPEC_CPU_COUNT, None)
1224   disk_count = instance_spec.get(constants.ISPEC_DISK_COUNT, 0)
1225   disk_sizes = instance_spec.get(constants.ISPEC_DISK_SIZE, [])
1226   nic_count = instance_spec.get(constants.ISPEC_NIC_COUNT, 0)
1227   spindle_use = instance_spec.get(constants.ISPEC_SPINDLE_USE, None)
1228
1229   return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
1230                      disk_sizes, spindle_use)
1231
1232
1233 def _ComputeIPolicyNodeViolation(ipolicy, instance, current_group,
1234                                  target_group,
1235                                  _compute_fn=_ComputeIPolicyInstanceViolation):
1236   """Compute if instance meets the specs of the new target group.
1237
1238   @param ipolicy: The ipolicy to verify
1239   @param instance: The instance object to verify
1240   @param current_group: The current group of the instance
1241   @param target_group: The new group of the instance
1242   @param _compute_fn: The function to verify ipolicy (unittest only)
1243   @see: L{_ComputeIPolicySpecViolation}
1244
1245   """
1246   if current_group == target_group:
1247     return []
1248   else:
1249     return _compute_fn(ipolicy, instance)
1250
1251
1252 def _CheckTargetNodeIPolicy(lu, ipolicy, instance, node, ignore=False,
1253                             _compute_fn=_ComputeIPolicyNodeViolation):
1254   """Checks that the target node is correct in terms of instance policy.
1255
1256   @param ipolicy: The ipolicy to verify
1257   @param instance: The instance object to verify
1258   @param node: The new node to relocate
1259   @param ignore: Ignore violations of the ipolicy
1260   @param _compute_fn: The function to verify ipolicy (unittest only)
1261   @see: L{_ComputeIPolicySpecViolation}
1262
1263   """
1264   primary_node = lu.cfg.GetNodeInfo(instance.primary_node)
1265   res = _compute_fn(ipolicy, instance, primary_node.group, node.group)
1266
1267   if res:
1268     msg = ("Instance does not meet target node group's (%s) instance"
1269            " policy: %s") % (node.group, utils.CommaJoin(res))
1270     if ignore:
1271       lu.LogWarning(msg)
1272     else:
1273       raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
1274
1275
1276 def _ComputeNewInstanceViolations(old_ipolicy, new_ipolicy, instances):
1277   """Computes a set of any instances that would violate the new ipolicy.
1278
1279   @param old_ipolicy: The current (still in-place) ipolicy
1280   @param new_ipolicy: The new (to become) ipolicy
1281   @param instances: List of instances to verify
1282   @return: A list of instances which violates the new ipolicy but
1283       did not before
1284
1285   """
1286   return (_ComputeViolatingInstances(new_ipolicy, instances) -
1287           _ComputeViolatingInstances(old_ipolicy, instances))
1288
1289
1290 def _ExpandItemName(fn, name, kind):
1291   """Expand an item name.
1292
1293   @param fn: the function to use for expansion
1294   @param name: requested item name
1295   @param kind: text description ('Node' or 'Instance')
1296   @return: the resolved (full) name
1297   @raise errors.OpPrereqError: if the item is not found
1298
1299   """
1300   full_name = fn(name)
1301   if full_name is None:
1302     raise errors.OpPrereqError("%s '%s' not known" % (kind, name),
1303                                errors.ECODE_NOENT)
1304   return full_name
1305
1306
1307 def _ExpandNodeName(cfg, name):
1308   """Wrapper over L{_ExpandItemName} for nodes."""
1309   return _ExpandItemName(cfg.ExpandNodeName, name, "Node")
1310
1311
1312 def _ExpandInstanceName(cfg, name):
1313   """Wrapper over L{_ExpandItemName} for instance."""
1314   return _ExpandItemName(cfg.ExpandInstanceName, name, "Instance")
1315
1316
1317 def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
1318                           minmem, maxmem, vcpus, nics, disk_template, disks,
1319                           bep, hvp, hypervisor_name, tags):
1320   """Builds instance related env variables for hooks
1321
1322   This builds the hook environment from individual variables.
1323
1324   @type name: string
1325   @param name: the name of the instance
1326   @type primary_node: string
1327   @param primary_node: the name of the instance's primary node
1328   @type secondary_nodes: list
1329   @param secondary_nodes: list of secondary nodes as strings
1330   @type os_type: string
1331   @param os_type: the name of the instance's OS
1332   @type status: string
1333   @param status: the desired status of the instance
1334   @type minmem: string
1335   @param minmem: the minimum memory size of the instance
1336   @type maxmem: string
1337   @param maxmem: the maximum memory size of the instance
1338   @type vcpus: string
1339   @param vcpus: the count of VCPUs the instance has
1340   @type nics: list
1341   @param nics: list of tuples (ip, mac, mode, link) representing
1342       the NICs the instance has
1343   @type disk_template: string
1344   @param disk_template: the disk template of the instance
1345   @type disks: list
1346   @param disks: the list of (size, mode) pairs
1347   @type bep: dict
1348   @param bep: the backend parameters for the instance
1349   @type hvp: dict
1350   @param hvp: the hypervisor parameters for the instance
1351   @type hypervisor_name: string
1352   @param hypervisor_name: the hypervisor for the instance
1353   @type tags: list
1354   @param tags: list of instance tags as strings
1355   @rtype: dict
1356   @return: the hook environment for this instance
1357
1358   """
1359   env = {
1360     "OP_TARGET": name,
1361     "INSTANCE_NAME": name,
1362     "INSTANCE_PRIMARY": primary_node,
1363     "INSTANCE_SECONDARIES": " ".join(secondary_nodes),
1364     "INSTANCE_OS_TYPE": os_type,
1365     "INSTANCE_STATUS": status,
1366     "INSTANCE_MINMEM": minmem,
1367     "INSTANCE_MAXMEM": maxmem,
1368     # TODO(2.7) remove deprecated "memory" value
1369     "INSTANCE_MEMORY": maxmem,
1370     "INSTANCE_VCPUS": vcpus,
1371     "INSTANCE_DISK_TEMPLATE": disk_template,
1372     "INSTANCE_HYPERVISOR": hypervisor_name,
1373   }
1374   if nics:
1375     nic_count = len(nics)
1376     for idx, (ip, mac, mode, link) in enumerate(nics):
1377       if ip is None:
1378         ip = ""
1379       env["INSTANCE_NIC%d_IP" % idx] = ip
1380       env["INSTANCE_NIC%d_MAC" % idx] = mac
1381       env["INSTANCE_NIC%d_MODE" % idx] = mode
1382       env["INSTANCE_NIC%d_LINK" % idx] = link
1383       if mode == constants.NIC_MODE_BRIDGED:
1384         env["INSTANCE_NIC%d_BRIDGE" % idx] = link
1385   else:
1386     nic_count = 0
1387
1388   env["INSTANCE_NIC_COUNT"] = nic_count
1389
1390   if disks:
1391     disk_count = len(disks)
1392     for idx, (size, mode) in enumerate(disks):
1393       env["INSTANCE_DISK%d_SIZE" % idx] = size
1394       env["INSTANCE_DISK%d_MODE" % idx] = mode
1395   else:
1396     disk_count = 0
1397
1398   env["INSTANCE_DISK_COUNT"] = disk_count
1399
1400   if not tags:
1401     tags = []
1402
1403   env["INSTANCE_TAGS"] = " ".join(tags)
1404
1405   for source, kind in [(bep, "BE"), (hvp, "HV")]:
1406     for key, value in source.items():
1407       env["INSTANCE_%s_%s" % (kind, key)] = value
1408
1409   return env
1410
1411
1412 def _NICListToTuple(lu, nics):
1413   """Build a list of nic information tuples.
1414
1415   This list is suitable to be passed to _BuildInstanceHookEnv or as a return
1416   value in LUInstanceQueryData.
1417
1418   @type lu:  L{LogicalUnit}
1419   @param lu: the logical unit on whose behalf we execute
1420   @type nics: list of L{objects.NIC}
1421   @param nics: list of nics to convert to hooks tuples
1422
1423   """
1424   hooks_nics = []
1425   cluster = lu.cfg.GetClusterInfo()
1426   for nic in nics:
1427     ip = nic.ip
1428     mac = nic.mac
1429     filled_params = cluster.SimpleFillNIC(nic.nicparams)
1430     mode = filled_params[constants.NIC_MODE]
1431     link = filled_params[constants.NIC_LINK]
1432     hooks_nics.append((ip, mac, mode, link))
1433   return hooks_nics
1434
1435
1436 def _BuildInstanceHookEnvByObject(lu, instance, override=None):
1437   """Builds instance related env variables for hooks from an object.
1438
1439   @type lu: L{LogicalUnit}
1440   @param lu: the logical unit on whose behalf we execute
1441   @type instance: L{objects.Instance}
1442   @param instance: the instance for which we should build the
1443       environment
1444   @type override: dict
1445   @param override: dictionary with key/values that will override
1446       our values
1447   @rtype: dict
1448   @return: the hook environment dictionary
1449
1450   """
1451   cluster = lu.cfg.GetClusterInfo()
1452   bep = cluster.FillBE(instance)
1453   hvp = cluster.FillHV(instance)
1454   args = {
1455     "name": instance.name,
1456     "primary_node": instance.primary_node,
1457     "secondary_nodes": instance.secondary_nodes,
1458     "os_type": instance.os,
1459     "status": instance.admin_state,
1460     "maxmem": bep[constants.BE_MAXMEM],
1461     "minmem": bep[constants.BE_MINMEM],
1462     "vcpus": bep[constants.BE_VCPUS],
1463     "nics": _NICListToTuple(lu, instance.nics),
1464     "disk_template": instance.disk_template,
1465     "disks": [(disk.size, disk.mode) for disk in instance.disks],
1466     "bep": bep,
1467     "hvp": hvp,
1468     "hypervisor_name": instance.hypervisor,
1469     "tags": instance.tags,
1470   }
1471   if override:
1472     args.update(override)
1473   return _BuildInstanceHookEnv(**args) # pylint: disable=W0142
1474
1475
1476 def _AdjustCandidatePool(lu, exceptions):
1477   """Adjust the candidate pool after node operations.
1478
1479   """
1480   mod_list = lu.cfg.MaintainCandidatePool(exceptions)
1481   if mod_list:
1482     lu.LogInfo("Promoted nodes to master candidate role: %s",
1483                utils.CommaJoin(node.name for node in mod_list))
1484     for name in mod_list:
1485       lu.context.ReaddNode(name)
1486   mc_now, mc_max, _ = lu.cfg.GetMasterCandidateStats(exceptions)
1487   if mc_now > mc_max:
1488     lu.LogInfo("Note: more nodes are candidates (%d) than desired (%d)" %
1489                (mc_now, mc_max))
1490
1491
1492 def _DecideSelfPromotion(lu, exceptions=None):
1493   """Decide whether I should promote myself as a master candidate.
1494
1495   """
1496   cp_size = lu.cfg.GetClusterInfo().candidate_pool_size
1497   mc_now, mc_should, _ = lu.cfg.GetMasterCandidateStats(exceptions)
1498   # the new node will increase mc_max with one, so:
1499   mc_should = min(mc_should + 1, cp_size)
1500   return mc_now < mc_should
1501
1502
1503 def _ComputeViolatingInstances(ipolicy, instances):
1504   """Computes a set of instances who violates given ipolicy.
1505
1506   @param ipolicy: The ipolicy to verify
1507   @type instances: object.Instance
1508   @param instances: List of instances to verify
1509   @return: A frozenset of instance names violating the ipolicy
1510
1511   """
1512   return frozenset([inst.name for inst in instances
1513                     if _ComputeIPolicyInstanceViolation(ipolicy, inst)])
1514
1515
1516 def _CheckNicsBridgesExist(lu, target_nics, target_node):
1517   """Check that the brigdes needed by a list of nics exist.
1518
1519   """
1520   cluster = lu.cfg.GetClusterInfo()
1521   paramslist = [cluster.SimpleFillNIC(nic.nicparams) for nic in target_nics]
1522   brlist = [params[constants.NIC_LINK] for params in paramslist
1523             if params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED]
1524   if brlist:
1525     result = lu.rpc.call_bridges_exist(target_node, brlist)
1526     result.Raise("Error checking bridges on destination node '%s'" %
1527                  target_node, prereq=True, ecode=errors.ECODE_ENVIRON)
1528
1529
1530 def _CheckInstanceBridgesExist(lu, instance, node=None):
1531   """Check that the brigdes needed by an instance exist.
1532
1533   """
1534   if node is None:
1535     node = instance.primary_node
1536   _CheckNicsBridgesExist(lu, instance.nics, node)
1537
1538
1539 def _CheckOSVariant(os_obj, name):
1540   """Check whether an OS name conforms to the os variants specification.
1541
1542   @type os_obj: L{objects.OS}
1543   @param os_obj: OS object to check
1544   @type name: string
1545   @param name: OS name passed by the user, to check for validity
1546
1547   """
1548   variant = objects.OS.GetVariant(name)
1549   if not os_obj.supported_variants:
1550     if variant:
1551       raise errors.OpPrereqError("OS '%s' doesn't support variants ('%s'"
1552                                  " passed)" % (os_obj.name, variant),
1553                                  errors.ECODE_INVAL)
1554     return
1555   if not variant:
1556     raise errors.OpPrereqError("OS name must include a variant",
1557                                errors.ECODE_INVAL)
1558
1559   if variant not in os_obj.supported_variants:
1560     raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
1561
1562
1563 def _GetNodeInstancesInner(cfg, fn):
1564   return [i for i in cfg.GetAllInstancesInfo().values() if fn(i)]
1565
1566
1567 def _GetNodeInstances(cfg, node_name):
1568   """Returns a list of all primary and secondary instances on a node.
1569
1570   """
1571
1572   return _GetNodeInstancesInner(cfg, lambda inst: node_name in inst.all_nodes)
1573
1574
1575 def _GetNodePrimaryInstances(cfg, node_name):
1576   """Returns primary instances on a node.
1577
1578   """
1579   return _GetNodeInstancesInner(cfg,
1580                                 lambda inst: node_name == inst.primary_node)
1581
1582
1583 def _GetNodeSecondaryInstances(cfg, node_name):
1584   """Returns secondary instances on a node.
1585
1586   """
1587   return _GetNodeInstancesInner(cfg,
1588                                 lambda inst: node_name in inst.secondary_nodes)
1589
1590
1591 def _GetStorageTypeArgs(cfg, storage_type):
1592   """Returns the arguments for a storage type.
1593
1594   """
1595   # Special case for file storage
1596   if storage_type == constants.ST_FILE:
1597     # storage.FileStorage wants a list of storage directories
1598     return [[cfg.GetFileStorageDir(), cfg.GetSharedFileStorageDir()]]
1599
1600   return []
1601
1602
1603 def _FindFaultyInstanceDisks(cfg, rpc_runner, instance, node_name, prereq):
1604   faulty = []
1605
1606   for dev in instance.disks:
1607     cfg.SetDiskID(dev, node_name)
1608
1609   result = rpc_runner.call_blockdev_getmirrorstatus(node_name, (instance.disks,
1610                                                                 instance))
1611   result.Raise("Failed to get disk status from node %s" % node_name,
1612                prereq=prereq, ecode=errors.ECODE_ENVIRON)
1613
1614   for idx, bdev_status in enumerate(result.payload):
1615     if bdev_status and bdev_status.ldisk_status == constants.LDS_FAULTY:
1616       faulty.append(idx)
1617
1618   return faulty
1619
1620
1621 def _CheckIAllocatorOrNode(lu, iallocator_slot, node_slot):
1622   """Check the sanity of iallocator and node arguments and use the
1623   cluster-wide iallocator if appropriate.
1624
1625   Check that at most one of (iallocator, node) is specified. If none is
1626   specified, or the iallocator is L{constants.DEFAULT_IALLOCATOR_SHORTCUT},
1627   then the LU's opcode's iallocator slot is filled with the cluster-wide
1628   default iallocator.
1629
1630   @type iallocator_slot: string
1631   @param iallocator_slot: the name of the opcode iallocator slot
1632   @type node_slot: string
1633   @param node_slot: the name of the opcode target node slot
1634
1635   """
1636   node = getattr(lu.op, node_slot, None)
1637   ialloc = getattr(lu.op, iallocator_slot, None)
1638   if node == []:
1639     node = None
1640
1641   if node is not None and ialloc is not None:
1642     raise errors.OpPrereqError("Do not specify both, iallocator and node",
1643                                errors.ECODE_INVAL)
1644   elif ((node is None and ialloc is None) or
1645         ialloc == constants.DEFAULT_IALLOCATOR_SHORTCUT):
1646     default_iallocator = lu.cfg.GetDefaultIAllocator()
1647     if default_iallocator:
1648       setattr(lu.op, iallocator_slot, default_iallocator)
1649     else:
1650       raise errors.OpPrereqError("No iallocator or node given and no"
1651                                  " cluster-wide default iallocator found;"
1652                                  " please specify either an iallocator or a"
1653                                  " node, or set a cluster-wide default"
1654                                  " iallocator", errors.ECODE_INVAL)
1655
1656
1657 def _GetDefaultIAllocator(cfg, ialloc):
1658   """Decides on which iallocator to use.
1659
1660   @type cfg: L{config.ConfigWriter}
1661   @param cfg: Cluster configuration object
1662   @type ialloc: string or None
1663   @param ialloc: Iallocator specified in opcode
1664   @rtype: string
1665   @return: Iallocator name
1666
1667   """
1668   if not ialloc:
1669     # Use default iallocator
1670     ialloc = cfg.GetDefaultIAllocator()
1671
1672   if not ialloc:
1673     raise errors.OpPrereqError("No iallocator was specified, neither in the"
1674                                " opcode nor as a cluster-wide default",
1675                                errors.ECODE_INVAL)
1676
1677   return ialloc
1678
1679
1680 class LUClusterPostInit(LogicalUnit):
1681   """Logical unit for running hooks after cluster initialization.
1682
1683   """
1684   HPATH = "cluster-init"
1685   HTYPE = constants.HTYPE_CLUSTER
1686
1687   def BuildHooksEnv(self):
1688     """Build hooks env.
1689
1690     """
1691     return {
1692       "OP_TARGET": self.cfg.GetClusterName(),
1693       }
1694
1695   def BuildHooksNodes(self):
1696     """Build hooks nodes.
1697
1698     """
1699     return ([], [self.cfg.GetMasterNode()])
1700
1701   def Exec(self, feedback_fn):
1702     """Nothing to do.
1703
1704     """
1705     return True
1706
1707
1708 class LUClusterDestroy(LogicalUnit):
1709   """Logical unit for destroying the cluster.
1710
1711   """
1712   HPATH = "cluster-destroy"
1713   HTYPE = constants.HTYPE_CLUSTER
1714
1715   def BuildHooksEnv(self):
1716     """Build hooks env.
1717
1718     """
1719     return {
1720       "OP_TARGET": self.cfg.GetClusterName(),
1721       }
1722
1723   def BuildHooksNodes(self):
1724     """Build hooks nodes.
1725
1726     """
1727     return ([], [])
1728
1729   def CheckPrereq(self):
1730     """Check prerequisites.
1731
1732     This checks whether the cluster is empty.
1733
1734     Any errors are signaled by raising errors.OpPrereqError.
1735
1736     """
1737     master = self.cfg.GetMasterNode()
1738
1739     nodelist = self.cfg.GetNodeList()
1740     if len(nodelist) != 1 or nodelist[0] != master:
1741       raise errors.OpPrereqError("There are still %d node(s) in"
1742                                  " this cluster." % (len(nodelist) - 1),
1743                                  errors.ECODE_INVAL)
1744     instancelist = self.cfg.GetInstanceList()
1745     if instancelist:
1746       raise errors.OpPrereqError("There are still %d instance(s) in"
1747                                  " this cluster." % len(instancelist),
1748                                  errors.ECODE_INVAL)
1749
1750   def Exec(self, feedback_fn):
1751     """Destroys the cluster.
1752
1753     """
1754     master_params = self.cfg.GetMasterNetworkParameters()
1755
1756     # Run post hooks on master node before it's removed
1757     _RunPostHook(self, master_params.name)
1758
1759     ems = self.cfg.GetUseExternalMipScript()
1760     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
1761                                                      master_params, ems)
1762     if result.fail_msg:
1763       self.LogWarning("Error disabling the master IP address: %s",
1764                       result.fail_msg)
1765
1766     return master_params.name
1767
1768
1769 def _VerifyCertificate(filename):
1770   """Verifies a certificate for L{LUClusterVerifyConfig}.
1771
1772   @type filename: string
1773   @param filename: Path to PEM file
1774
1775   """
1776   try:
1777     cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
1778                                            utils.ReadFile(filename))
1779   except Exception, err: # pylint: disable=W0703
1780     return (LUClusterVerifyConfig.ETYPE_ERROR,
1781             "Failed to load X509 certificate %s: %s" % (filename, err))
1782
1783   (errcode, msg) = \
1784     utils.VerifyX509Certificate(cert, constants.SSL_CERT_EXPIRATION_WARN,
1785                                 constants.SSL_CERT_EXPIRATION_ERROR)
1786
1787   if msg:
1788     fnamemsg = "While verifying %s: %s" % (filename, msg)
1789   else:
1790     fnamemsg = None
1791
1792   if errcode is None:
1793     return (None, fnamemsg)
1794   elif errcode == utils.CERT_WARNING:
1795     return (LUClusterVerifyConfig.ETYPE_WARNING, fnamemsg)
1796   elif errcode == utils.CERT_ERROR:
1797     return (LUClusterVerifyConfig.ETYPE_ERROR, fnamemsg)
1798
1799   raise errors.ProgrammerError("Unhandled certificate error code %r" % errcode)
1800
1801
1802 def _GetAllHypervisorParameters(cluster, instances):
1803   """Compute the set of all hypervisor parameters.
1804
1805   @type cluster: L{objects.Cluster}
1806   @param cluster: the cluster object
1807   @param instances: list of L{objects.Instance}
1808   @param instances: additional instances from which to obtain parameters
1809   @rtype: list of (origin, hypervisor, parameters)
1810   @return: a list with all parameters found, indicating the hypervisor they
1811        apply to, and the origin (can be "cluster", "os X", or "instance Y")
1812
1813   """
1814   hvp_data = []
1815
1816   for hv_name in cluster.enabled_hypervisors:
1817     hvp_data.append(("cluster", hv_name, cluster.GetHVDefaults(hv_name)))
1818
1819   for os_name, os_hvp in cluster.os_hvp.items():
1820     for hv_name, hv_params in os_hvp.items():
1821       if hv_params:
1822         full_params = cluster.GetHVDefaults(hv_name, os_name=os_name)
1823         hvp_data.append(("os %s" % os_name, hv_name, full_params))
1824
1825   # TODO: collapse identical parameter values in a single one
1826   for instance in instances:
1827     if instance.hvparams:
1828       hvp_data.append(("instance %s" % instance.name, instance.hypervisor,
1829                        cluster.FillHV(instance)))
1830
1831   return hvp_data
1832
1833
1834 class _VerifyErrors(object):
1835   """Mix-in for cluster/group verify LUs.
1836
1837   It provides _Error and _ErrorIf, and updates the self.bad boolean. (Expects
1838   self.op and self._feedback_fn to be available.)
1839
1840   """
1841
1842   ETYPE_FIELD = "code"
1843   ETYPE_ERROR = "ERROR"
1844   ETYPE_WARNING = "WARNING"
1845
1846   def _Error(self, ecode, item, msg, *args, **kwargs):
1847     """Format an error message.
1848
1849     Based on the opcode's error_codes parameter, either format a
1850     parseable error code, or a simpler error string.
1851
1852     This must be called only from Exec and functions called from Exec.
1853
1854     """
1855     ltype = kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR)
1856     itype, etxt, _ = ecode
1857     # first complete the msg
1858     if args:
1859       msg = msg % args
1860     # then format the whole message
1861     if self.op.error_codes: # This is a mix-in. pylint: disable=E1101
1862       msg = "%s:%s:%s:%s:%s" % (ltype, etxt, itype, item, msg)
1863     else:
1864       if item:
1865         item = " " + item
1866       else:
1867         item = ""
1868       msg = "%s: %s%s: %s" % (ltype, itype, item, msg)
1869     # and finally report it via the feedback_fn
1870     self._feedback_fn("  - %s" % msg) # Mix-in. pylint: disable=E1101
1871
1872   def _ErrorIf(self, cond, ecode, *args, **kwargs):
1873     """Log an error message if the passed condition is True.
1874
1875     """
1876     cond = (bool(cond)
1877             or self.op.debug_simulate_errors) # pylint: disable=E1101
1878
1879     # If the error code is in the list of ignored errors, demote the error to a
1880     # warning
1881     (_, etxt, _) = ecode
1882     if etxt in self.op.ignore_errors:     # pylint: disable=E1101
1883       kwargs[self.ETYPE_FIELD] = self.ETYPE_WARNING
1884
1885     if cond:
1886       self._Error(ecode, *args, **kwargs)
1887
1888     # do not mark the operation as failed for WARN cases only
1889     if kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR) == self.ETYPE_ERROR:
1890       self.bad = self.bad or cond
1891
1892
1893 class LUClusterVerify(NoHooksLU):
1894   """Submits all jobs necessary to verify the cluster.
1895
1896   """
1897   REQ_BGL = False
1898
1899   def ExpandNames(self):
1900     self.needed_locks = {}
1901
1902   def Exec(self, feedback_fn):
1903     jobs = []
1904
1905     if self.op.group_name:
1906       groups = [self.op.group_name]
1907       depends_fn = lambda: None
1908     else:
1909       groups = self.cfg.GetNodeGroupList()
1910
1911       # Verify global configuration
1912       jobs.append([
1913         opcodes.OpClusterVerifyConfig(ignore_errors=self.op.ignore_errors)
1914         ])
1915
1916       # Always depend on global verification
1917       depends_fn = lambda: [(-len(jobs), [])]
1918
1919     jobs.extend(
1920       [opcodes.OpClusterVerifyGroup(group_name=group,
1921                                     ignore_errors=self.op.ignore_errors,
1922                                     depends=depends_fn())]
1923       for group in groups)
1924
1925     # Fix up all parameters
1926     for op in itertools.chain(*jobs): # pylint: disable=W0142
1927       op.debug_simulate_errors = self.op.debug_simulate_errors
1928       op.verbose = self.op.verbose
1929       op.error_codes = self.op.error_codes
1930       try:
1931         op.skip_checks = self.op.skip_checks
1932       except AttributeError:
1933         assert not isinstance(op, opcodes.OpClusterVerifyGroup)
1934
1935     return ResultWithJobs(jobs)
1936
1937
1938 class LUClusterVerifyConfig(NoHooksLU, _VerifyErrors):
1939   """Verifies the cluster config.
1940
1941   """
1942   REQ_BGL = False
1943
1944   def _VerifyHVP(self, hvp_data):
1945     """Verifies locally the syntax of the hypervisor parameters.
1946
1947     """
1948     for item, hv_name, hv_params in hvp_data:
1949       msg = ("hypervisor %s parameters syntax check (source %s): %%s" %
1950              (item, hv_name))
1951       try:
1952         hv_class = hypervisor.GetHypervisor(hv_name)
1953         utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
1954         hv_class.CheckParameterSyntax(hv_params)
1955       except errors.GenericError, err:
1956         self._ErrorIf(True, constants.CV_ECLUSTERCFG, None, msg % str(err))
1957
1958   def ExpandNames(self):
1959     self.needed_locks = dict.fromkeys(locking.LEVELS, locking.ALL_SET)
1960     self.share_locks = _ShareAll()
1961
1962   def CheckPrereq(self):
1963     """Check prerequisites.
1964
1965     """
1966     # Retrieve all information
1967     self.all_group_info = self.cfg.GetAllNodeGroupsInfo()
1968     self.all_node_info = self.cfg.GetAllNodesInfo()
1969     self.all_inst_info = self.cfg.GetAllInstancesInfo()
1970
1971   def Exec(self, feedback_fn):
1972     """Verify integrity of cluster, performing various test on nodes.
1973
1974     """
1975     self.bad = False
1976     self._feedback_fn = feedback_fn
1977
1978     feedback_fn("* Verifying cluster config")
1979
1980     for msg in self.cfg.VerifyConfig():
1981       self._ErrorIf(True, constants.CV_ECLUSTERCFG, None, msg)
1982
1983     feedback_fn("* Verifying cluster certificate files")
1984
1985     for cert_filename in pathutils.ALL_CERT_FILES:
1986       (errcode, msg) = _VerifyCertificate(cert_filename)
1987       self._ErrorIf(errcode, constants.CV_ECLUSTERCERT, None, msg, code=errcode)
1988
1989     feedback_fn("* Verifying hypervisor parameters")
1990
1991     self._VerifyHVP(_GetAllHypervisorParameters(self.cfg.GetClusterInfo(),
1992                                                 self.all_inst_info.values()))
1993
1994     feedback_fn("* Verifying all nodes belong to an existing group")
1995
1996     # We do this verification here because, should this bogus circumstance
1997     # occur, it would never be caught by VerifyGroup, which only acts on
1998     # nodes/instances reachable from existing node groups.
1999
2000     dangling_nodes = set(node.name for node in self.all_node_info.values()
2001                          if node.group not in self.all_group_info)
2002
2003     dangling_instances = {}
2004     no_node_instances = []
2005
2006     for inst in self.all_inst_info.values():
2007       if inst.primary_node in dangling_nodes:
2008         dangling_instances.setdefault(inst.primary_node, []).append(inst.name)
2009       elif inst.primary_node not in self.all_node_info:
2010         no_node_instances.append(inst.name)
2011
2012     pretty_dangling = [
2013         "%s (%s)" %
2014         (node.name,
2015          utils.CommaJoin(dangling_instances.get(node.name,
2016                                                 ["no instances"])))
2017         for node in dangling_nodes]
2018
2019     self._ErrorIf(bool(dangling_nodes), constants.CV_ECLUSTERDANGLINGNODES,
2020                   None,
2021                   "the following nodes (and their instances) belong to a non"
2022                   " existing group: %s", utils.CommaJoin(pretty_dangling))
2023
2024     self._ErrorIf(bool(no_node_instances), constants.CV_ECLUSTERDANGLINGINST,
2025                   None,
2026                   "the following instances have a non-existing primary-node:"
2027                   " %s", utils.CommaJoin(no_node_instances))
2028
2029     return not self.bad
2030
2031
2032 class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
2033   """Verifies the status of a node group.
2034
2035   """
2036   HPATH = "cluster-verify"
2037   HTYPE = constants.HTYPE_CLUSTER
2038   REQ_BGL = False
2039
2040   _HOOKS_INDENT_RE = re.compile("^", re.M)
2041
2042   class NodeImage(object):
2043     """A class representing the logical and physical status of a node.
2044
2045     @type name: string
2046     @ivar name: the node name to which this object refers
2047     @ivar volumes: a structure as returned from
2048         L{ganeti.backend.GetVolumeList} (runtime)
2049     @ivar instances: a list of running instances (runtime)
2050     @ivar pinst: list of configured primary instances (config)
2051     @ivar sinst: list of configured secondary instances (config)
2052     @ivar sbp: dictionary of {primary-node: list of instances} for all
2053         instances for which this node is secondary (config)
2054     @ivar mfree: free memory, as reported by hypervisor (runtime)
2055     @ivar dfree: free disk, as reported by the node (runtime)
2056     @ivar offline: the offline status (config)
2057     @type rpc_fail: boolean
2058     @ivar rpc_fail: whether the RPC verify call was successfull (overall,
2059         not whether the individual keys were correct) (runtime)
2060     @type lvm_fail: boolean
2061     @ivar lvm_fail: whether the RPC call didn't return valid LVM data
2062     @type hyp_fail: boolean
2063     @ivar hyp_fail: whether the RPC call didn't return the instance list
2064     @type ghost: boolean
2065     @ivar ghost: whether this is a known node or not (config)
2066     @type os_fail: boolean
2067     @ivar os_fail: whether the RPC call didn't return valid OS data
2068     @type oslist: list
2069     @ivar oslist: list of OSes as diagnosed by DiagnoseOS
2070     @type vm_capable: boolean
2071     @ivar vm_capable: whether the node can host instances
2072
2073     """
2074     def __init__(self, offline=False, name=None, vm_capable=True):
2075       self.name = name
2076       self.volumes = {}
2077       self.instances = []
2078       self.pinst = []
2079       self.sinst = []
2080       self.sbp = {}
2081       self.mfree = 0
2082       self.dfree = 0
2083       self.offline = offline
2084       self.vm_capable = vm_capable
2085       self.rpc_fail = False
2086       self.lvm_fail = False
2087       self.hyp_fail = False
2088       self.ghost = False
2089       self.os_fail = False
2090       self.oslist = {}
2091
2092   def ExpandNames(self):
2093     # This raises errors.OpPrereqError on its own:
2094     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
2095
2096     # Get instances in node group; this is unsafe and needs verification later
2097     inst_names = \
2098       self.cfg.GetNodeGroupInstances(self.group_uuid, primary_only=True)
2099
2100     self.needed_locks = {
2101       locking.LEVEL_INSTANCE: inst_names,
2102       locking.LEVEL_NODEGROUP: [self.group_uuid],
2103       locking.LEVEL_NODE: [],
2104       }
2105
2106     self.share_locks = _ShareAll()
2107
2108   def DeclareLocks(self, level):
2109     if level == locking.LEVEL_NODE:
2110       # Get members of node group; this is unsafe and needs verification later
2111       nodes = set(self.cfg.GetNodeGroup(self.group_uuid).members)
2112
2113       all_inst_info = self.cfg.GetAllInstancesInfo()
2114
2115       # In Exec(), we warn about mirrored instances that have primary and
2116       # secondary living in separate node groups. To fully verify that
2117       # volumes for these instances are healthy, we will need to do an
2118       # extra call to their secondaries. We ensure here those nodes will
2119       # be locked.
2120       for inst in self.owned_locks(locking.LEVEL_INSTANCE):
2121         # Important: access only the instances whose lock is owned
2122         if all_inst_info[inst].disk_template in constants.DTS_INT_MIRROR:
2123           nodes.update(all_inst_info[inst].secondary_nodes)
2124
2125       self.needed_locks[locking.LEVEL_NODE] = nodes
2126
2127   def CheckPrereq(self):
2128     assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
2129     self.group_info = self.cfg.GetNodeGroup(self.group_uuid)
2130
2131     group_nodes = set(self.group_info.members)
2132     group_instances = \
2133       self.cfg.GetNodeGroupInstances(self.group_uuid, primary_only=True)
2134
2135     unlocked_nodes = \
2136         group_nodes.difference(self.owned_locks(locking.LEVEL_NODE))
2137
2138     unlocked_instances = \
2139         group_instances.difference(self.owned_locks(locking.LEVEL_INSTANCE))
2140
2141     if unlocked_nodes:
2142       raise errors.OpPrereqError("Missing lock for nodes: %s" %
2143                                  utils.CommaJoin(unlocked_nodes),
2144                                  errors.ECODE_STATE)
2145
2146     if unlocked_instances:
2147       raise errors.OpPrereqError("Missing lock for instances: %s" %
2148                                  utils.CommaJoin(unlocked_instances),
2149                                  errors.ECODE_STATE)
2150
2151     self.all_node_info = self.cfg.GetAllNodesInfo()
2152     self.all_inst_info = self.cfg.GetAllInstancesInfo()
2153
2154     self.my_node_names = utils.NiceSort(group_nodes)
2155     self.my_inst_names = utils.NiceSort(group_instances)
2156
2157     self.my_node_info = dict((name, self.all_node_info[name])
2158                              for name in self.my_node_names)
2159
2160     self.my_inst_info = dict((name, self.all_inst_info[name])
2161                              for name in self.my_inst_names)
2162
2163     # We detect here the nodes that will need the extra RPC calls for verifying
2164     # split LV volumes; they should be locked.
2165     extra_lv_nodes = set()
2166
2167     for inst in self.my_inst_info.values():
2168       if inst.disk_template in constants.DTS_INT_MIRROR:
2169         for nname in inst.all_nodes:
2170           if self.all_node_info[nname].group != self.group_uuid:
2171             extra_lv_nodes.add(nname)
2172
2173     unlocked_lv_nodes = \
2174         extra_lv_nodes.difference(self.owned_locks(locking.LEVEL_NODE))
2175
2176     if unlocked_lv_nodes:
2177       raise errors.OpPrereqError("Missing node locks for LV check: %s" %
2178                                  utils.CommaJoin(unlocked_lv_nodes),
2179                                  errors.ECODE_STATE)
2180     self.extra_lv_nodes = list(extra_lv_nodes)
2181
2182   def _VerifyNode(self, ninfo, nresult):
2183     """Perform some basic validation on data returned from a node.
2184
2185       - check the result data structure is well formed and has all the
2186         mandatory fields
2187       - check ganeti version
2188
2189     @type ninfo: L{objects.Node}
2190     @param ninfo: the node to check
2191     @param nresult: the results from the node
2192     @rtype: boolean
2193     @return: whether overall this call was successful (and we can expect
2194          reasonable values in the respose)
2195
2196     """
2197     node = ninfo.name
2198     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2199
2200     # main result, nresult should be a non-empty dict
2201     test = not nresult or not isinstance(nresult, dict)
2202     _ErrorIf(test, constants.CV_ENODERPC, node,
2203                   "unable to verify node: no data returned")
2204     if test:
2205       return False
2206
2207     # compares ganeti version
2208     local_version = constants.PROTOCOL_VERSION
2209     remote_version = nresult.get("version", None)
2210     test = not (remote_version and
2211                 isinstance(remote_version, (list, tuple)) and
2212                 len(remote_version) == 2)
2213     _ErrorIf(test, constants.CV_ENODERPC, node,
2214              "connection to node returned invalid data")
2215     if test:
2216       return False
2217
2218     test = local_version != remote_version[0]
2219     _ErrorIf(test, constants.CV_ENODEVERSION, node,
2220              "incompatible protocol versions: master %s,"
2221              " node %s", local_version, remote_version[0])
2222     if test:
2223       return False
2224
2225     # node seems compatible, we can actually try to look into its results
2226
2227     # full package version
2228     self._ErrorIf(constants.RELEASE_VERSION != remote_version[1],
2229                   constants.CV_ENODEVERSION, node,
2230                   "software version mismatch: master %s, node %s",
2231                   constants.RELEASE_VERSION, remote_version[1],
2232                   code=self.ETYPE_WARNING)
2233
2234     hyp_result = nresult.get(constants.NV_HYPERVISOR, None)
2235     if ninfo.vm_capable and isinstance(hyp_result, dict):
2236       for hv_name, hv_result in hyp_result.iteritems():
2237         test = hv_result is not None
2238         _ErrorIf(test, constants.CV_ENODEHV, node,
2239                  "hypervisor %s verify failure: '%s'", hv_name, hv_result)
2240
2241     hvp_result = nresult.get(constants.NV_HVPARAMS, None)
2242     if ninfo.vm_capable and isinstance(hvp_result, list):
2243       for item, hv_name, hv_result in hvp_result:
2244         _ErrorIf(True, constants.CV_ENODEHV, node,
2245                  "hypervisor %s parameter verify failure (source %s): %s",
2246                  hv_name, item, hv_result)
2247
2248     test = nresult.get(constants.NV_NODESETUP,
2249                        ["Missing NODESETUP results"])
2250     _ErrorIf(test, constants.CV_ENODESETUP, node, "node setup error: %s",
2251              "; ".join(test))
2252
2253     return True
2254
2255   def _VerifyNodeTime(self, ninfo, nresult,
2256                       nvinfo_starttime, nvinfo_endtime):
2257     """Check the node time.
2258
2259     @type ninfo: L{objects.Node}
2260     @param ninfo: the node to check
2261     @param nresult: the remote results for the node
2262     @param nvinfo_starttime: the start time of the RPC call
2263     @param nvinfo_endtime: the end time of the RPC call
2264
2265     """
2266     node = ninfo.name
2267     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2268
2269     ntime = nresult.get(constants.NV_TIME, None)
2270     try:
2271       ntime_merged = utils.MergeTime(ntime)
2272     except (ValueError, TypeError):
2273       _ErrorIf(True, constants.CV_ENODETIME, node, "Node returned invalid time")
2274       return
2275
2276     if ntime_merged < (nvinfo_starttime - constants.NODE_MAX_CLOCK_SKEW):
2277       ntime_diff = "%.01fs" % abs(nvinfo_starttime - ntime_merged)
2278     elif ntime_merged > (nvinfo_endtime + constants.NODE_MAX_CLOCK_SKEW):
2279       ntime_diff = "%.01fs" % abs(ntime_merged - nvinfo_endtime)
2280     else:
2281       ntime_diff = None
2282
2283     _ErrorIf(ntime_diff is not None, constants.CV_ENODETIME, node,
2284              "Node time diverges by at least %s from master node time",
2285              ntime_diff)
2286
2287   def _VerifyNodeLVM(self, ninfo, nresult, vg_name):
2288     """Check the node LVM results.
2289
2290     @type ninfo: L{objects.Node}
2291     @param ninfo: the node to check
2292     @param nresult: the remote results for the node
2293     @param vg_name: the configured VG name
2294
2295     """
2296     if vg_name is None:
2297       return
2298
2299     node = ninfo.name
2300     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2301
2302     # checks vg existence and size > 20G
2303     vglist = nresult.get(constants.NV_VGLIST, None)
2304     test = not vglist
2305     _ErrorIf(test, constants.CV_ENODELVM, node, "unable to check volume groups")
2306     if not test:
2307       vgstatus = utils.CheckVolumeGroupSize(vglist, vg_name,
2308                                             constants.MIN_VG_SIZE)
2309       _ErrorIf(vgstatus, constants.CV_ENODELVM, node, vgstatus)
2310
2311     # check pv names
2312     pvlist = nresult.get(constants.NV_PVLIST, None)
2313     test = pvlist is None
2314     _ErrorIf(test, constants.CV_ENODELVM, node, "Can't get PV list from node")
2315     if not test:
2316       # check that ':' is not present in PV names, since it's a
2317       # special character for lvcreate (denotes the range of PEs to
2318       # use on the PV)
2319       for _, pvname, owner_vg in pvlist:
2320         test = ":" in pvname
2321         _ErrorIf(test, constants.CV_ENODELVM, node,
2322                  "Invalid character ':' in PV '%s' of VG '%s'",
2323                  pvname, owner_vg)
2324
2325   def _VerifyNodeBridges(self, ninfo, nresult, bridges):
2326     """Check the node bridges.
2327
2328     @type ninfo: L{objects.Node}
2329     @param ninfo: the node to check
2330     @param nresult: the remote results for the node
2331     @param bridges: the expected list of bridges
2332
2333     """
2334     if not bridges:
2335       return
2336
2337     node = ninfo.name
2338     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2339
2340     missing = nresult.get(constants.NV_BRIDGES, None)
2341     test = not isinstance(missing, list)
2342     _ErrorIf(test, constants.CV_ENODENET, node,
2343              "did not return valid bridge information")
2344     if not test:
2345       _ErrorIf(bool(missing), constants.CV_ENODENET, node,
2346                "missing bridges: %s" % utils.CommaJoin(sorted(missing)))
2347
2348   def _VerifyNodeUserScripts(self, ninfo, nresult):
2349     """Check the results of user scripts presence and executability on the node
2350
2351     @type ninfo: L{objects.Node}
2352     @param ninfo: the node to check
2353     @param nresult: the remote results for the node
2354
2355     """
2356     node = ninfo.name
2357
2358     test = not constants.NV_USERSCRIPTS in nresult
2359     self._ErrorIf(test, constants.CV_ENODEUSERSCRIPTS, node,
2360                   "did not return user scripts information")
2361
2362     broken_scripts = nresult.get(constants.NV_USERSCRIPTS, None)
2363     if not test:
2364       self._ErrorIf(broken_scripts, constants.CV_ENODEUSERSCRIPTS, node,
2365                     "user scripts not present or not executable: %s" %
2366                     utils.CommaJoin(sorted(broken_scripts)))
2367
2368   def _VerifyNodeNetwork(self, ninfo, nresult):
2369     """Check the node network connectivity results.
2370
2371     @type ninfo: L{objects.Node}
2372     @param ninfo: the node to check
2373     @param nresult: the remote results for the node
2374
2375     """
2376     node = ninfo.name
2377     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2378
2379     test = constants.NV_NODELIST not in nresult
2380     _ErrorIf(test, constants.CV_ENODESSH, node,
2381              "node hasn't returned node ssh connectivity data")
2382     if not test:
2383       if nresult[constants.NV_NODELIST]:
2384         for a_node, a_msg in nresult[constants.NV_NODELIST].items():
2385           _ErrorIf(True, constants.CV_ENODESSH, node,
2386                    "ssh communication with node '%s': %s", a_node, a_msg)
2387
2388     test = constants.NV_NODENETTEST not in nresult
2389     _ErrorIf(test, constants.CV_ENODENET, node,
2390              "node hasn't returned node tcp connectivity data")
2391     if not test:
2392       if nresult[constants.NV_NODENETTEST]:
2393         nlist = utils.NiceSort(nresult[constants.NV_NODENETTEST].keys())
2394         for anode in nlist:
2395           _ErrorIf(True, constants.CV_ENODENET, node,
2396                    "tcp communication with node '%s': %s",
2397                    anode, nresult[constants.NV_NODENETTEST][anode])
2398
2399     test = constants.NV_MASTERIP not in nresult
2400     _ErrorIf(test, constants.CV_ENODENET, node,
2401              "node hasn't returned node master IP reachability data")
2402     if not test:
2403       if not nresult[constants.NV_MASTERIP]:
2404         if node == self.master_node:
2405           msg = "the master node cannot reach the master IP (not configured?)"
2406         else:
2407           msg = "cannot reach the master IP"
2408         _ErrorIf(True, constants.CV_ENODENET, node, msg)
2409
2410   def _VerifyInstance(self, instance, instanceconfig, node_image,
2411                       diskstatus):
2412     """Verify an instance.
2413
2414     This function checks to see if the required block devices are
2415     available on the instance's node.
2416
2417     """
2418     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2419     node_current = instanceconfig.primary_node
2420
2421     node_vol_should = {}
2422     instanceconfig.MapLVsByNode(node_vol_should)
2423
2424     cluster = self.cfg.GetClusterInfo()
2425     ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
2426                                                             self.group_info)
2427     err = _ComputeIPolicyInstanceViolation(ipolicy, instanceconfig)
2428     _ErrorIf(err, constants.CV_EINSTANCEPOLICY, instance, utils.CommaJoin(err))
2429
2430     for node in node_vol_should:
2431       n_img = node_image[node]
2432       if n_img.offline or n_img.rpc_fail or n_img.lvm_fail:
2433         # ignore missing volumes on offline or broken nodes
2434         continue
2435       for volume in node_vol_should[node]:
2436         test = volume not in n_img.volumes
2437         _ErrorIf(test, constants.CV_EINSTANCEMISSINGDISK, instance,
2438                  "volume %s missing on node %s", volume, node)
2439
2440     if instanceconfig.admin_state == constants.ADMINST_UP:
2441       pri_img = node_image[node_current]
2442       test = instance not in pri_img.instances and not pri_img.offline
2443       _ErrorIf(test, constants.CV_EINSTANCEDOWN, instance,
2444                "instance not running on its primary node %s",
2445                node_current)
2446
2447     diskdata = [(nname, success, status, idx)
2448                 for (nname, disks) in diskstatus.items()
2449                 for idx, (success, status) in enumerate(disks)]
2450
2451     for nname, success, bdev_status, idx in diskdata:
2452       # the 'ghost node' construction in Exec() ensures that we have a
2453       # node here
2454       snode = node_image[nname]
2455       bad_snode = snode.ghost or snode.offline
2456       _ErrorIf(instanceconfig.admin_state == constants.ADMINST_UP and
2457                not success and not bad_snode,
2458                constants.CV_EINSTANCEFAULTYDISK, instance,
2459                "couldn't retrieve status for disk/%s on %s: %s",
2460                idx, nname, bdev_status)
2461       _ErrorIf((instanceconfig.admin_state == constants.ADMINST_UP and
2462                 success and bdev_status.ldisk_status == constants.LDS_FAULTY),
2463                constants.CV_EINSTANCEFAULTYDISK, instance,
2464                "disk/%s on %s is faulty", idx, nname)
2465
2466   def _VerifyOrphanVolumes(self, node_vol_should, node_image, reserved):
2467     """Verify if there are any unknown volumes in the cluster.
2468
2469     The .os, .swap and backup volumes are ignored. All other volumes are
2470     reported as unknown.
2471
2472     @type reserved: L{ganeti.utils.FieldSet}
2473     @param reserved: a FieldSet of reserved volume names
2474
2475     """
2476     for node, n_img in node_image.items():
2477       if (n_img.offline or n_img.rpc_fail or n_img.lvm_fail or
2478           self.all_node_info[node].group != self.group_uuid):
2479         # skip non-healthy nodes
2480         continue
2481       for volume in n_img.volumes:
2482         test = ((node not in node_vol_should or
2483                 volume not in node_vol_should[node]) and
2484                 not reserved.Matches(volume))
2485         self._ErrorIf(test, constants.CV_ENODEORPHANLV, node,
2486                       "volume %s is unknown", volume)
2487
2488   def _VerifyNPlusOneMemory(self, node_image, instance_cfg):
2489     """Verify N+1 Memory Resilience.
2490
2491     Check that if one single node dies we can still start all the
2492     instances it was primary for.
2493
2494     """
2495     cluster_info = self.cfg.GetClusterInfo()
2496     for node, n_img in node_image.items():
2497       # This code checks that every node which is now listed as
2498       # secondary has enough memory to host all instances it is
2499       # supposed to should a single other node in the cluster fail.
2500       # FIXME: not ready for failover to an arbitrary node
2501       # FIXME: does not support file-backed instances
2502       # WARNING: we currently take into account down instances as well
2503       # as up ones, considering that even if they're down someone
2504       # might want to start them even in the event of a node failure.
2505       if n_img.offline or self.all_node_info[node].group != self.group_uuid:
2506         # we're skipping nodes marked offline and nodes in other groups from
2507         # the N+1 warning, since most likely we don't have good memory
2508         # infromation from them; we already list instances living on such
2509         # nodes, and that's enough warning
2510         continue
2511       #TODO(dynmem): also consider ballooning out other instances
2512       for prinode, instances in n_img.sbp.items():
2513         needed_mem = 0
2514         for instance in instances:
2515           bep = cluster_info.FillBE(instance_cfg[instance])
2516           if bep[constants.BE_AUTO_BALANCE]:
2517             needed_mem += bep[constants.BE_MINMEM]
2518         test = n_img.mfree < needed_mem
2519         self._ErrorIf(test, constants.CV_ENODEN1, node,
2520                       "not enough memory to accomodate instance failovers"
2521                       " should node %s fail (%dMiB needed, %dMiB available)",
2522                       prinode, needed_mem, n_img.mfree)
2523
2524   @classmethod
2525   def _VerifyFiles(cls, errorif, nodeinfo, master_node, all_nvinfo,
2526                    (files_all, files_opt, files_mc, files_vm)):
2527     """Verifies file checksums collected from all nodes.
2528
2529     @param errorif: Callback for reporting errors
2530     @param nodeinfo: List of L{objects.Node} objects
2531     @param master_node: Name of master node
2532     @param all_nvinfo: RPC results
2533
2534     """
2535     # Define functions determining which nodes to consider for a file
2536     files2nodefn = [
2537       (files_all, None),
2538       (files_mc, lambda node: (node.master_candidate or
2539                                node.name == master_node)),
2540       (files_vm, lambda node: node.vm_capable),
2541       ]
2542
2543     # Build mapping from filename to list of nodes which should have the file
2544     nodefiles = {}
2545     for (files, fn) in files2nodefn:
2546       if fn is None:
2547         filenodes = nodeinfo
2548       else:
2549         filenodes = filter(fn, nodeinfo)
2550       nodefiles.update((filename,
2551                         frozenset(map(operator.attrgetter("name"), filenodes)))
2552                        for filename in files)
2553
2554     assert set(nodefiles) == (files_all | files_mc | files_vm)
2555
2556     fileinfo = dict((filename, {}) for filename in nodefiles)
2557     ignore_nodes = set()
2558
2559     for node in nodeinfo:
2560       if node.offline:
2561         ignore_nodes.add(node.name)
2562         continue
2563
2564       nresult = all_nvinfo[node.name]
2565
2566       if nresult.fail_msg or not nresult.payload:
2567         node_files = None
2568       else:
2569         fingerprints = nresult.payload.get(constants.NV_FILELIST, None)
2570         node_files = dict((vcluster.LocalizeVirtualPath(key), value)
2571                           for (key, value) in fingerprints.items())
2572         del fingerprints
2573
2574       test = not (node_files and isinstance(node_files, dict))
2575       errorif(test, constants.CV_ENODEFILECHECK, node.name,
2576               "Node did not return file checksum data")
2577       if test:
2578         ignore_nodes.add(node.name)
2579         continue
2580
2581       # Build per-checksum mapping from filename to nodes having it
2582       for (filename, checksum) in node_files.items():
2583         assert filename in nodefiles
2584         fileinfo[filename].setdefault(checksum, set()).add(node.name)
2585
2586     for (filename, checksums) in fileinfo.items():
2587       assert compat.all(len(i) > 10 for i in checksums), "Invalid checksum"
2588
2589       # Nodes having the file
2590       with_file = frozenset(node_name
2591                             for nodes in fileinfo[filename].values()
2592                             for node_name in nodes) - ignore_nodes
2593
2594       expected_nodes = nodefiles[filename] - ignore_nodes
2595
2596       # Nodes missing file
2597       missing_file = expected_nodes - with_file
2598
2599       if filename in files_opt:
2600         # All or no nodes
2601         errorif(missing_file and missing_file != expected_nodes,
2602                 constants.CV_ECLUSTERFILECHECK, None,
2603                 "File %s is optional, but it must exist on all or no"
2604                 " nodes (not found on %s)",
2605                 filename, utils.CommaJoin(utils.NiceSort(missing_file)))
2606       else:
2607         errorif(missing_file, constants.CV_ECLUSTERFILECHECK, None,
2608                 "File %s is missing from node(s) %s", filename,
2609                 utils.CommaJoin(utils.NiceSort(missing_file)))
2610
2611         # Warn if a node has a file it shouldn't
2612         unexpected = with_file - expected_nodes
2613         errorif(unexpected,
2614                 constants.CV_ECLUSTERFILECHECK, None,
2615                 "File %s should not exist on node(s) %s",
2616                 filename, utils.CommaJoin(utils.NiceSort(unexpected)))
2617
2618       # See if there are multiple versions of the file
2619       test = len(checksums) > 1
2620       if test:
2621         variants = ["variant %s on %s" %
2622                     (idx + 1, utils.CommaJoin(utils.NiceSort(nodes)))
2623                     for (idx, (checksum, nodes)) in
2624                       enumerate(sorted(checksums.items()))]
2625       else:
2626         variants = []
2627
2628       errorif(test, constants.CV_ECLUSTERFILECHECK, None,
2629               "File %s found with %s different checksums (%s)",
2630               filename, len(checksums), "; ".join(variants))
2631
2632   def _VerifyNodeDrbd(self, ninfo, nresult, instanceinfo, drbd_helper,
2633                       drbd_map):
2634     """Verifies and the node DRBD status.
2635
2636     @type ninfo: L{objects.Node}
2637     @param ninfo: the node to check
2638     @param nresult: the remote results for the node
2639     @param instanceinfo: the dict of instances
2640     @param drbd_helper: the configured DRBD usermode helper
2641     @param drbd_map: the DRBD map as returned by
2642         L{ganeti.config.ConfigWriter.ComputeDRBDMap}
2643
2644     """
2645     node = ninfo.name
2646     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2647
2648     if drbd_helper:
2649       helper_result = nresult.get(constants.NV_DRBDHELPER, None)
2650       test = (helper_result is None)
2651       _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2652                "no drbd usermode helper returned")
2653       if helper_result:
2654         status, payload = helper_result
2655         test = not status
2656         _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2657                  "drbd usermode helper check unsuccessful: %s", payload)
2658         test = status and (payload != drbd_helper)
2659         _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2660                  "wrong drbd usermode helper: %s", payload)
2661
2662     # compute the DRBD minors
2663     node_drbd = {}
2664     for minor, instance in drbd_map[node].items():
2665       test = instance not in instanceinfo
2666       _ErrorIf(test, constants.CV_ECLUSTERCFG, None,
2667                "ghost instance '%s' in temporary DRBD map", instance)
2668         # ghost instance should not be running, but otherwise we
2669         # don't give double warnings (both ghost instance and
2670         # unallocated minor in use)
2671       if test:
2672         node_drbd[minor] = (instance, False)
2673       else:
2674         instance = instanceinfo[instance]
2675         node_drbd[minor] = (instance.name,
2676                             instance.admin_state == constants.ADMINST_UP)
2677
2678     # and now check them
2679     used_minors = nresult.get(constants.NV_DRBDLIST, [])
2680     test = not isinstance(used_minors, (tuple, list))
2681     _ErrorIf(test, constants.CV_ENODEDRBD, node,
2682              "cannot parse drbd status file: %s", str(used_minors))
2683     if test:
2684       # we cannot check drbd status
2685       return
2686
2687     for minor, (iname, must_exist) in node_drbd.items():
2688       test = minor not in used_minors and must_exist
2689       _ErrorIf(test, constants.CV_ENODEDRBD, node,
2690                "drbd minor %d of instance %s is not active", minor, iname)
2691     for minor in used_minors:
2692       test = minor not in node_drbd
2693       _ErrorIf(test, constants.CV_ENODEDRBD, node,
2694                "unallocated drbd minor %d is in use", minor)
2695
2696   def _UpdateNodeOS(self, ninfo, nresult, nimg):
2697     """Builds the node OS structures.
2698
2699     @type ninfo: L{objects.Node}
2700     @param ninfo: the node to check
2701     @param nresult: the remote results for the node
2702     @param nimg: the node image object
2703
2704     """
2705     node = ninfo.name
2706     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2707
2708     remote_os = nresult.get(constants.NV_OSLIST, None)
2709     test = (not isinstance(remote_os, list) or
2710             not compat.all(isinstance(v, list) and len(v) == 7
2711                            for v in remote_os))
2712
2713     _ErrorIf(test, constants.CV_ENODEOS, node,
2714              "node hasn't returned valid OS data")
2715
2716     nimg.os_fail = test
2717
2718     if test:
2719       return
2720
2721     os_dict = {}
2722
2723     for (name, os_path, status, diagnose,
2724          variants, parameters, api_ver) in nresult[constants.NV_OSLIST]:
2725
2726       if name not in os_dict:
2727         os_dict[name] = []
2728
2729       # parameters is a list of lists instead of list of tuples due to
2730       # JSON lacking a real tuple type, fix it:
2731       parameters = [tuple(v) for v in parameters]
2732       os_dict[name].append((os_path, status, diagnose,
2733                             set(variants), set(parameters), set(api_ver)))
2734
2735     nimg.oslist = os_dict
2736
2737   def _VerifyNodeOS(self, ninfo, nimg, base):
2738     """Verifies the node OS list.
2739
2740     @type ninfo: L{objects.Node}
2741     @param ninfo: the node to check
2742     @param nimg: the node image object
2743     @param base: the 'template' node we match against (e.g. from the master)
2744
2745     """
2746     node = ninfo.name
2747     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2748
2749     assert not nimg.os_fail, "Entered _VerifyNodeOS with failed OS rpc?"
2750
2751     beautify_params = lambda l: ["%s: %s" % (k, v) for (k, v) in l]
2752     for os_name, os_data in nimg.oslist.items():
2753       assert os_data, "Empty OS status for OS %s?!" % os_name
2754       f_path, f_status, f_diag, f_var, f_param, f_api = os_data[0]
2755       _ErrorIf(not f_status, constants.CV_ENODEOS, node,
2756                "Invalid OS %s (located at %s): %s", os_name, f_path, f_diag)
2757       _ErrorIf(len(os_data) > 1, constants.CV_ENODEOS, node,
2758                "OS '%s' has multiple entries (first one shadows the rest): %s",
2759                os_name, utils.CommaJoin([v[0] for v in os_data]))
2760       # comparisons with the 'base' image
2761       test = os_name not in base.oslist
2762       _ErrorIf(test, constants.CV_ENODEOS, node,
2763                "Extra OS %s not present on reference node (%s)",
2764                os_name, base.name)
2765       if test:
2766         continue
2767       assert base.oslist[os_name], "Base node has empty OS status?"
2768       _, b_status, _, b_var, b_param, b_api = base.oslist[os_name][0]
2769       if not b_status:
2770         # base OS is invalid, skipping
2771         continue
2772       for kind, a, b in [("API version", f_api, b_api),
2773                          ("variants list", f_var, b_var),
2774                          ("parameters", beautify_params(f_param),
2775                           beautify_params(b_param))]:
2776         _ErrorIf(a != b, constants.CV_ENODEOS, node,
2777                  "OS %s for %s differs from reference node %s: [%s] vs. [%s]",
2778                  kind, os_name, base.name,
2779                  utils.CommaJoin(sorted(a)), utils.CommaJoin(sorted(b)))
2780
2781     # check any missing OSes
2782     missing = set(base.oslist.keys()).difference(nimg.oslist.keys())
2783     _ErrorIf(missing, constants.CV_ENODEOS, node,
2784              "OSes present on reference node %s but missing on this node: %s",
2785              base.name, utils.CommaJoin(missing))
2786
2787   def _VerifyOob(self, ninfo, nresult):
2788     """Verifies out of band functionality of a node.
2789
2790     @type ninfo: L{objects.Node}
2791     @param ninfo: the node to check
2792     @param nresult: the remote results for the node
2793
2794     """
2795     node = ninfo.name
2796     # We just have to verify the paths on master and/or master candidates
2797     # as the oob helper is invoked on the master
2798     if ((ninfo.master_candidate or ninfo.master_capable) and
2799         constants.NV_OOB_PATHS in nresult):
2800       for path_result in nresult[constants.NV_OOB_PATHS]:
2801         self._ErrorIf(path_result, constants.CV_ENODEOOBPATH, node, path_result)
2802
2803   def _UpdateNodeVolumes(self, ninfo, nresult, nimg, vg_name):
2804     """Verifies and updates the node volume data.
2805
2806     This function will update a L{NodeImage}'s internal structures
2807     with data from the remote call.
2808
2809     @type ninfo: L{objects.Node}
2810     @param ninfo: the node to check
2811     @param nresult: the remote results for the node
2812     @param nimg: the node image object
2813     @param vg_name: the configured VG name
2814
2815     """
2816     node = ninfo.name
2817     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2818
2819     nimg.lvm_fail = True
2820     lvdata = nresult.get(constants.NV_LVLIST, "Missing LV data")
2821     if vg_name is None:
2822       pass
2823     elif isinstance(lvdata, basestring):
2824       _ErrorIf(True, constants.CV_ENODELVM, node, "LVM problem on node: %s",
2825                utils.SafeEncode(lvdata))
2826     elif not isinstance(lvdata, dict):
2827       _ErrorIf(True, constants.CV_ENODELVM, node,
2828                "rpc call to node failed (lvlist)")
2829     else:
2830       nimg.volumes = lvdata
2831       nimg.lvm_fail = False
2832
2833   def _UpdateNodeInstances(self, ninfo, nresult, nimg):
2834     """Verifies and updates the node instance list.
2835
2836     If the listing was successful, then updates this node's instance
2837     list. Otherwise, it marks the RPC call as failed for the instance
2838     list key.
2839
2840     @type ninfo: L{objects.Node}
2841     @param ninfo: the node to check
2842     @param nresult: the remote results for the node
2843     @param nimg: the node image object
2844
2845     """
2846     idata = nresult.get(constants.NV_INSTANCELIST, None)
2847     test = not isinstance(idata, list)
2848     self._ErrorIf(test, constants.CV_ENODEHV, ninfo.name,
2849                   "rpc call to node failed (instancelist): %s",
2850                   utils.SafeEncode(str(idata)))
2851     if test:
2852       nimg.hyp_fail = True
2853     else:
2854       nimg.instances = idata
2855
2856   def _UpdateNodeInfo(self, ninfo, nresult, nimg, vg_name):
2857     """Verifies and computes a node information map
2858
2859     @type ninfo: L{objects.Node}
2860     @param ninfo: the node to check
2861     @param nresult: the remote results for the node
2862     @param nimg: the node image object
2863     @param vg_name: the configured VG name
2864
2865     """
2866     node = ninfo.name
2867     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2868
2869     # try to read free memory (from the hypervisor)
2870     hv_info = nresult.get(constants.NV_HVINFO, None)
2871     test = not isinstance(hv_info, dict) or "memory_free" not in hv_info
2872     _ErrorIf(test, constants.CV_ENODEHV, node,
2873              "rpc call to node failed (hvinfo)")
2874     if not test:
2875       try:
2876         nimg.mfree = int(hv_info["memory_free"])
2877       except (ValueError, TypeError):
2878         _ErrorIf(True, constants.CV_ENODERPC, node,
2879                  "node returned invalid nodeinfo, check hypervisor")
2880
2881     # FIXME: devise a free space model for file based instances as well
2882     if vg_name is not None:
2883       test = (constants.NV_VGLIST not in nresult or
2884               vg_name not in nresult[constants.NV_VGLIST])
2885       _ErrorIf(test, constants.CV_ENODELVM, node,
2886                "node didn't return data for the volume group '%s'"
2887                " - it is either missing or broken", vg_name)
2888       if not test:
2889         try:
2890           nimg.dfree = int(nresult[constants.NV_VGLIST][vg_name])
2891         except (ValueError, TypeError):
2892           _ErrorIf(True, constants.CV_ENODERPC, node,
2893                    "node returned invalid LVM info, check LVM status")
2894
2895   def _CollectDiskInfo(self, nodelist, node_image, instanceinfo):
2896     """Gets per-disk status information for all instances.
2897
2898     @type nodelist: list of strings
2899     @param nodelist: Node names
2900     @type node_image: dict of (name, L{objects.Node})
2901     @param node_image: Node objects
2902     @type instanceinfo: dict of (name, L{objects.Instance})
2903     @param instanceinfo: Instance objects
2904     @rtype: {instance: {node: [(succes, payload)]}}
2905     @return: a dictionary of per-instance dictionaries with nodes as
2906         keys and disk information as values; the disk information is a
2907         list of tuples (success, payload)
2908
2909     """
2910     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2911
2912     node_disks = {}
2913     node_disks_devonly = {}
2914     diskless_instances = set()
2915     diskless = constants.DT_DISKLESS
2916
2917     for nname in nodelist:
2918       node_instances = list(itertools.chain(node_image[nname].pinst,
2919                                             node_image[nname].sinst))
2920       diskless_instances.update(inst for inst in node_instances
2921                                 if instanceinfo[inst].disk_template == diskless)
2922       disks = [(inst, disk)
2923                for inst in node_instances
2924                for disk in instanceinfo[inst].disks]
2925
2926       if not disks:
2927         # No need to collect data
2928         continue
2929
2930       node_disks[nname] = disks
2931
2932       # _AnnotateDiskParams makes already copies of the disks
2933       devonly = []
2934       for (inst, dev) in disks:
2935         (anno_disk,) = _AnnotateDiskParams(instanceinfo[inst], [dev], self.cfg)
2936         self.cfg.SetDiskID(anno_disk, nname)
2937         devonly.append(anno_disk)
2938
2939       node_disks_devonly[nname] = devonly
2940
2941     assert len(node_disks) == len(node_disks_devonly)
2942
2943     # Collect data from all nodes with disks
2944     result = self.rpc.call_blockdev_getmirrorstatus_multi(node_disks.keys(),
2945                                                           node_disks_devonly)
2946
2947     assert len(result) == len(node_disks)
2948
2949     instdisk = {}
2950
2951     for (nname, nres) in result.items():
2952       disks = node_disks[nname]
2953
2954       if nres.offline:
2955         # No data from this node
2956         data = len(disks) * [(False, "node offline")]
2957       else:
2958         msg = nres.fail_msg
2959         _ErrorIf(msg, constants.CV_ENODERPC, nname,
2960                  "while getting disk information: %s", msg)
2961         if msg:
2962           # No data from this node
2963           data = len(disks) * [(False, msg)]
2964         else:
2965           data = []
2966           for idx, i in enumerate(nres.payload):
2967             if isinstance(i, (tuple, list)) and len(i) == 2:
2968               data.append(i)
2969             else:
2970               logging.warning("Invalid result from node %s, entry %d: %s",
2971                               nname, idx, i)
2972               data.append((False, "Invalid result from the remote node"))
2973
2974       for ((inst, _), status) in zip(disks, data):
2975         instdisk.setdefault(inst, {}).setdefault(nname, []).append(status)
2976
2977     # Add empty entries for diskless instances.
2978     for inst in diskless_instances:
2979       assert inst not in instdisk
2980       instdisk[inst] = {}
2981
2982     assert compat.all(len(statuses) == len(instanceinfo[inst].disks) and
2983                       len(nnames) <= len(instanceinfo[inst].all_nodes) and
2984                       compat.all(isinstance(s, (tuple, list)) and
2985                                  len(s) == 2 for s in statuses)
2986                       for inst, nnames in instdisk.items()
2987                       for nname, statuses in nnames.items())
2988     assert set(instdisk) == set(instanceinfo), "instdisk consistency failure"
2989
2990     return instdisk
2991
2992   @staticmethod
2993   def _SshNodeSelector(group_uuid, all_nodes):
2994     """Create endless iterators for all potential SSH check hosts.
2995
2996     """
2997     nodes = [node for node in all_nodes
2998              if (node.group != group_uuid and
2999                  not node.offline)]
3000     keyfunc = operator.attrgetter("group")
3001
3002     return map(itertools.cycle,
3003                [sorted(map(operator.attrgetter("name"), names))
3004                 for _, names in itertools.groupby(sorted(nodes, key=keyfunc),
3005                                                   keyfunc)])
3006
3007   @classmethod
3008   def _SelectSshCheckNodes(cls, group_nodes, group_uuid, all_nodes):
3009     """Choose which nodes should talk to which other nodes.
3010
3011     We will make nodes contact all nodes in their group, and one node from
3012     every other group.
3013
3014     @warning: This algorithm has a known issue if one node group is much
3015       smaller than others (e.g. just one node). In such a case all other
3016       nodes will talk to the single node.
3017
3018     """
3019     online_nodes = sorted(node.name for node in group_nodes if not node.offline)
3020     sel = cls._SshNodeSelector(group_uuid, all_nodes)
3021
3022     return (online_nodes,
3023             dict((name, sorted([i.next() for i in sel]))
3024                  for name in online_nodes))
3025
3026   def BuildHooksEnv(self):
3027     """Build hooks env.
3028
3029     Cluster-Verify hooks just ran in the post phase and their failure makes
3030     the output be logged in the verify output and the verification to fail.
3031
3032     """
3033     env = {
3034       "CLUSTER_TAGS": " ".join(self.cfg.GetClusterInfo().GetTags())
3035       }
3036
3037     env.update(("NODE_TAGS_%s" % node.name, " ".join(node.GetTags()))
3038                for node in self.my_node_info.values())
3039
3040     return env
3041
3042   def BuildHooksNodes(self):
3043     """Build hooks nodes.
3044
3045     """
3046     return ([], self.my_node_names)
3047
3048   def Exec(self, feedback_fn):
3049     """Verify integrity of the node group, performing various test on nodes.
3050
3051     """
3052     # This method has too many local variables. pylint: disable=R0914
3053     feedback_fn("* Verifying group '%s'" % self.group_info.name)
3054
3055     if not self.my_node_names:
3056       # empty node group
3057       feedback_fn("* Empty node group, skipping verification")
3058       return True
3059
3060     self.bad = False
3061     _ErrorIf = self._ErrorIf # pylint: disable=C0103
3062     verbose = self.op.verbose
3063     self._feedback_fn = feedback_fn
3064
3065     vg_name = self.cfg.GetVGName()
3066     drbd_helper = self.cfg.GetDRBDHelper()
3067     cluster = self.cfg.GetClusterInfo()
3068     groupinfo = self.cfg.GetAllNodeGroupsInfo()
3069     hypervisors = cluster.enabled_hypervisors
3070     node_data_list = [self.my_node_info[name] for name in self.my_node_names]
3071
3072     i_non_redundant = [] # Non redundant instances
3073     i_non_a_balanced = [] # Non auto-balanced instances
3074     i_offline = 0 # Count of offline instances
3075     n_offline = 0 # Count of offline nodes
3076     n_drained = 0 # Count of nodes being drained
3077     node_vol_should = {}
3078
3079     # FIXME: verify OS list
3080
3081     # File verification
3082     filemap = _ComputeAncillaryFiles(cluster, False)
3083
3084     # do local checksums
3085     master_node = self.master_node = self.cfg.GetMasterNode()
3086     master_ip = self.cfg.GetMasterIP()
3087
3088     feedback_fn("* Gathering data (%d nodes)" % len(self.my_node_names))
3089
3090     user_scripts = []
3091     if self.cfg.GetUseExternalMipScript():
3092       user_scripts.append(pathutils.EXTERNAL_MASTER_SETUP_SCRIPT)
3093
3094     node_verify_param = {
3095       constants.NV_FILELIST:
3096         map(vcluster.MakeVirtualPath,
3097             utils.UniqueSequence(filename
3098                                  for files in filemap
3099                                  for filename in files)),
3100       constants.NV_NODELIST:
3101         self._SelectSshCheckNodes(node_data_list, self.group_uuid,
3102                                   self.all_node_info.values()),
3103       constants.NV_HYPERVISOR: hypervisors,
3104       constants.NV_HVPARAMS:
3105         _GetAllHypervisorParameters(cluster, self.all_inst_info.values()),
3106       constants.NV_NODENETTEST: [(node.name, node.primary_ip, node.secondary_ip)
3107                                  for node in node_data_list
3108                                  if not node.offline],
3109       constants.NV_INSTANCELIST: hypervisors,
3110       constants.NV_VERSION: None,
3111       constants.NV_HVINFO: self.cfg.GetHypervisorType(),
3112       constants.NV_NODESETUP: None,
3113       constants.NV_TIME: None,
3114       constants.NV_MASTERIP: (master_node, master_ip),
3115       constants.NV_OSLIST: None,
3116       constants.NV_VMNODES: self.cfg.GetNonVmCapableNodeList(),
3117       constants.NV_USERSCRIPTS: user_scripts,
3118       }
3119
3120     if vg_name is not None:
3121       node_verify_param[constants.NV_VGLIST] = None
3122       node_verify_param[constants.NV_LVLIST] = vg_name
3123       node_verify_param[constants.NV_PVLIST] = [vg_name]
3124
3125     if drbd_helper:
3126       node_verify_param[constants.NV_DRBDLIST] = None
3127       node_verify_param[constants.NV_DRBDHELPER] = drbd_helper
3128
3129     # bridge checks
3130     # FIXME: this needs to be changed per node-group, not cluster-wide
3131     bridges = set()
3132     default_nicpp = cluster.nicparams[constants.PP_DEFAULT]
3133     if default_nicpp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
3134       bridges.add(default_nicpp[constants.NIC_LINK])
3135     for instance in self.my_inst_info.values():
3136       for nic in instance.nics:
3137         full_nic = cluster.SimpleFillNIC(nic.nicparams)
3138         if full_nic[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
3139           bridges.add(full_nic[constants.NIC_LINK])
3140
3141     if bridges:
3142       node_verify_param[constants.NV_BRIDGES] = list(bridges)
3143
3144     # Build our expected cluster state
3145     node_image = dict((node.name, self.NodeImage(offline=node.offline,
3146                                                  name=node.name,
3147                                                  vm_capable=node.vm_capable))
3148                       for node in node_data_list)
3149
3150     # Gather OOB paths
3151     oob_paths = []
3152     for node in self.all_node_info.values():
3153       path = _SupportsOob(self.cfg, node)
3154       if path and path not in oob_paths:
3155         oob_paths.append(path)
3156
3157     if oob_paths:
3158       node_verify_param[constants.NV_OOB_PATHS] = oob_paths
3159
3160     for instance in self.my_inst_names:
3161       inst_config = self.my_inst_info[instance]
3162       if inst_config.admin_state == constants.ADMINST_OFFLINE:
3163         i_offline += 1
3164
3165       for nname in inst_config.all_nodes:
3166         if nname not in node_image:
3167           gnode = self.NodeImage(name=nname)
3168           gnode.ghost = (nname not in self.all_node_info)
3169           node_image[nname] = gnode
3170
3171       inst_config.MapLVsByNode(node_vol_should)
3172
3173       pnode = inst_config.primary_node
3174       node_image[pnode].pinst.append(instance)
3175
3176       for snode in inst_config.secondary_nodes:
3177         nimg = node_image[snode]
3178         nimg.sinst.append(instance)
3179         if pnode not in nimg.sbp:
3180           nimg.sbp[pnode] = []
3181         nimg.sbp[pnode].append(instance)
3182
3183     # At this point, we have the in-memory data structures complete,
3184     # except for the runtime information, which we'll gather next
3185
3186     # Due to the way our RPC system works, exact response times cannot be
3187     # guaranteed (e.g. a broken node could run into a timeout). By keeping the
3188     # time before and after executing the request, we can at least have a time
3189     # window.
3190     nvinfo_starttime = time.time()
3191     all_nvinfo = self.rpc.call_node_verify(self.my_node_names,
3192                                            node_verify_param,
3193                                            self.cfg.GetClusterName())
3194     nvinfo_endtime = time.time()
3195
3196     if self.extra_lv_nodes and vg_name is not None:
3197       extra_lv_nvinfo = \
3198           self.rpc.call_node_verify(self.extra_lv_nodes,
3199                                     {constants.NV_LVLIST: vg_name},
3200                                     self.cfg.GetClusterName())
3201     else:
3202       extra_lv_nvinfo = {}
3203
3204     all_drbd_map = self.cfg.ComputeDRBDMap()
3205
3206     feedback_fn("* Gathering disk information (%s nodes)" %
3207                 len(self.my_node_names))
3208     instdisk = self._CollectDiskInfo(self.my_node_names, node_image,
3209                                      self.my_inst_info)
3210
3211     feedback_fn("* Verifying configuration file consistency")
3212
3213     # If not all nodes are being checked, we need to make sure the master node
3214     # and a non-checked vm_capable node are in the list.
3215     absent_nodes = set(self.all_node_info).difference(self.my_node_info)
3216     if absent_nodes:
3217       vf_nvinfo = all_nvinfo.copy()
3218       vf_node_info = list(self.my_node_info.values())
3219       additional_nodes = []
3220       if master_node not in self.my_node_info:
3221         additional_nodes.append(master_node)
3222         vf_node_info.append(self.all_node_info[master_node])
3223       # Add the first vm_capable node we find which is not included,
3224       # excluding the master node (which we already have)
3225       for node in absent_nodes:
3226         nodeinfo = self.all_node_info[node]
3227         if (nodeinfo.vm_capable and not nodeinfo.offline and
3228             node != master_node):
3229           additional_nodes.append(node)
3230           vf_node_info.append(self.all_node_info[node])
3231           break
3232       key = constants.NV_FILELIST
3233       vf_nvinfo.update(self.rpc.call_node_verify(additional_nodes,
3234                                                  {key: node_verify_param[key]},
3235                                                  self.cfg.GetClusterName()))
3236     else:
3237       vf_nvinfo = all_nvinfo
3238       vf_node_info = self.my_node_info.values()
3239
3240     self._VerifyFiles(_ErrorIf, vf_node_info, master_node, vf_nvinfo, filemap)
3241
3242     feedback_fn("* Verifying node status")
3243
3244     refos_img = None
3245
3246     for node_i in node_data_list:
3247       node = node_i.name
3248       nimg = node_image[node]
3249
3250       if node_i.offline:
3251         if verbose:
3252           feedback_fn("* Skipping offline node %s" % (node,))
3253         n_offline += 1
3254         continue
3255
3256       if node == master_node:
3257         ntype = "master"
3258       elif node_i.master_candidate:
3259         ntype = "master candidate"
3260       elif node_i.drained:
3261         ntype = "drained"
3262         n_drained += 1
3263       else:
3264         ntype = "regular"
3265       if verbose:
3266         feedback_fn("* Verifying node %s (%s)" % (node, ntype))
3267
3268       msg = all_nvinfo[node].fail_msg
3269       _ErrorIf(msg, constants.CV_ENODERPC, node, "while contacting node: %s",
3270                msg)
3271       if msg:
3272         nimg.rpc_fail = True
3273         continue
3274
3275       nresult = all_nvinfo[node].payload
3276
3277       nimg.call_ok = self._VerifyNode(node_i, nresult)
3278       self._VerifyNodeTime(node_i, nresult, nvinfo_starttime, nvinfo_endtime)
3279       self._VerifyNodeNetwork(node_i, nresult)
3280       self._VerifyNodeUserScripts(node_i, nresult)
3281       self._VerifyOob(node_i, nresult)
3282
3283       if nimg.vm_capable:
3284         self._VerifyNodeLVM(node_i, nresult, vg_name)
3285         self._VerifyNodeDrbd(node_i, nresult, self.all_inst_info, drbd_helper,
3286                              all_drbd_map)
3287
3288         self._UpdateNodeVolumes(node_i, nresult, nimg, vg_name)
3289         self._UpdateNodeInstances(node_i, nresult, nimg)
3290         self._UpdateNodeInfo(node_i, nresult, nimg, vg_name)
3291         self._UpdateNodeOS(node_i, nresult, nimg)
3292
3293         if not nimg.os_fail:
3294           if refos_img is None:
3295             refos_img = nimg
3296           self._VerifyNodeOS(node_i, nimg, refos_img)
3297         self._VerifyNodeBridges(node_i, nresult, bridges)
3298
3299         # Check whether all running instancies are primary for the node. (This
3300         # can no longer be done from _VerifyInstance below, since some of the
3301         # wrong instances could be from other node groups.)
3302         non_primary_inst = set(nimg.instances).difference(nimg.pinst)
3303
3304         for inst in non_primary_inst:
3305           test = inst in self.all_inst_info
3306           _ErrorIf(test, constants.CV_EINSTANCEWRONGNODE, inst,
3307                    "instance should not run on node %s", node_i.name)
3308           _ErrorIf(not test, constants.CV_ENODEORPHANINSTANCE, node_i.name,
3309                    "node is running unknown instance %s", inst)
3310
3311     for node, result in extra_lv_nvinfo.items():
3312       self._UpdateNodeVolumes(self.all_node_info[node], result.payload,
3313                               node_image[node], vg_name)
3314
3315     feedback_fn("* Verifying instance status")
3316     for instance in self.my_inst_names:
3317       if verbose:
3318         feedback_fn("* Verifying instance %s" % instance)
3319       inst_config = self.my_inst_info[instance]
3320       self._VerifyInstance(instance, inst_config, node_image,
3321                            instdisk[instance])
3322       inst_nodes_offline = []
3323
3324       pnode = inst_config.primary_node
3325       pnode_img = node_image[pnode]
3326       _ErrorIf(pnode_img.rpc_fail and not pnode_img.offline,
3327                constants.CV_ENODERPC, pnode, "instance %s, connection to"
3328                " primary node failed", instance)
3329
3330       _ErrorIf(inst_config.admin_state == constants.ADMINST_UP and
3331                pnode_img.offline,
3332                constants.CV_EINSTANCEBADNODE, instance,
3333                "instance is marked as running and lives on offline node %s",
3334                inst_config.primary_node)
3335
3336       # If the instance is non-redundant we cannot survive losing its primary
3337       # node, so we are not N+1 compliant. On the other hand we have no disk
3338       # templates with more than one secondary so that situation is not well
3339       # supported either.
3340       # FIXME: does not support file-backed instances
3341       if not inst_config.secondary_nodes:
3342         i_non_redundant.append(instance)
3343
3344       _ErrorIf(len(inst_config.secondary_nodes) > 1,
3345                constants.CV_EINSTANCELAYOUT,
3346                instance, "instance has multiple secondary nodes: %s",
3347                utils.CommaJoin(inst_config.secondary_nodes),
3348                code=self.ETYPE_WARNING)
3349
3350       if inst_config.disk_template in constants.DTS_INT_MIRROR:
3351         pnode = inst_config.primary_node
3352         instance_nodes = utils.NiceSort(inst_config.all_nodes)
3353         instance_groups = {}
3354
3355         for node in instance_nodes:
3356           instance_groups.setdefault(self.all_node_info[node].group,
3357                                      []).append(node)
3358
3359         pretty_list = [
3360           "%s (group %s)" % (utils.CommaJoin(nodes), groupinfo[group].name)
3361           # Sort so that we always list the primary node first.
3362           for group, nodes in sorted(instance_groups.items(),
3363                                      key=lambda (_, nodes): pnode in nodes,
3364                                      reverse=True)]
3365
3366         self._ErrorIf(len(instance_groups) > 1,
3367                       constants.CV_EINSTANCESPLITGROUPS,
3368                       instance, "instance has primary and secondary nodes in"
3369                       " different groups: %s", utils.CommaJoin(pretty_list),
3370                       code=self.ETYPE_WARNING)
3371
3372       if not cluster.FillBE(inst_config)[constants.BE_AUTO_BALANCE]:
3373         i_non_a_balanced.append(instance)
3374
3375       for snode in inst_config.secondary_nodes:
3376         s_img = node_image[snode]
3377         _ErrorIf(s_img.rpc_fail and not s_img.offline, constants.CV_ENODERPC,
3378                  snode, "instance %s, connection to secondary node failed",
3379                  instance)
3380
3381         if s_img.offline:
3382           inst_nodes_offline.append(snode)
3383
3384       # warn that the instance lives on offline nodes
3385       _ErrorIf(inst_nodes_offline, constants.CV_EINSTANCEBADNODE, instance,
3386                "instance has offline secondary node(s) %s",
3387                utils.CommaJoin(inst_nodes_offline))
3388       # ... or ghost/non-vm_capable nodes
3389       for node in inst_config.all_nodes:
3390         _ErrorIf(node_image[node].ghost, constants.CV_EINSTANCEBADNODE,
3391                  instance, "instance lives on ghost node %s", node)
3392         _ErrorIf(not node_image[node].vm_capable, constants.CV_EINSTANCEBADNODE,
3393                  instance, "instance lives on non-vm_capable node %s", node)
3394
3395     feedback_fn("* Verifying orphan volumes")
3396     reserved = utils.FieldSet(*cluster.reserved_lvs)
3397
3398     # We will get spurious "unknown volume" warnings if any node of this group
3399     # is secondary for an instance whose primary is in another group. To avoid
3400     # them, we find these instances and add their volumes to node_vol_should.
3401     for inst in self.all_inst_info.values():
3402       for secondary in inst.secondary_nodes:
3403         if (secondary in self.my_node_info
3404             and inst.name not in self.my_inst_info):
3405           inst.MapLVsByNode(node_vol_should)
3406           break
3407
3408     self._VerifyOrphanVolumes(node_vol_should, node_image, reserved)
3409
3410     if constants.VERIFY_NPLUSONE_MEM not in self.op.skip_checks:
3411       feedback_fn("* Verifying N+1 Memory redundancy")
3412       self._VerifyNPlusOneMemory(node_image, self.my_inst_info)
3413
3414     feedback_fn("* Other Notes")
3415     if i_non_redundant:
3416       feedback_fn("  - NOTICE: %d non-redundant instance(s) found."
3417                   % len(i_non_redundant))
3418
3419     if i_non_a_balanced:
3420       feedback_fn("  - NOTICE: %d non-auto-balanced instance(s) found."
3421                   % len(i_non_a_balanced))
3422
3423     if i_offline:
3424       feedback_fn("  - NOTICE: %d offline instance(s) found." % i_offline)
3425
3426     if n_offline:
3427       feedback_fn("  - NOTICE: %d offline node(s) found." % n_offline)
3428
3429     if n_drained:
3430       feedback_fn("  - NOTICE: %d drained node(s) found." % n_drained)
3431
3432     return not self.bad
3433
3434   def HooksCallBack(self, phase, hooks_results, feedback_fn, lu_result):
3435     """Analyze the post-hooks' result
3436
3437     This method analyses the hook result, handles it, and sends some
3438     nicely-formatted feedback back to the user.
3439
3440     @param phase: one of L{constants.HOOKS_PHASE_POST} or
3441         L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
3442     @param hooks_results: the results of the multi-node hooks rpc call
3443     @param feedback_fn: function used send feedback back to the caller
3444     @param lu_result: previous Exec result
3445     @return: the new Exec result, based on the previous result
3446         and hook results
3447
3448     """
3449     # We only really run POST phase hooks, only for non-empty groups,
3450     # and are only interested in their results
3451     if not self.my_node_names:
3452       # empty node group
3453       pass
3454     elif phase == constants.HOOKS_PHASE_POST:
3455       # Used to change hooks' output to proper indentation
3456       feedback_fn("* Hooks Results")
3457       assert hooks_results, "invalid result from hooks"
3458
3459       for node_name in hooks_results:
3460         res = hooks_results[node_name]
3461         msg = res.fail_msg
3462         test = msg and not res.offline
3463         self._ErrorIf(test, constants.CV_ENODEHOOKS, node_name,
3464                       "Communication failure in hooks execution: %s", msg)
3465         if res.offline or msg:
3466           # No need to investigate payload if node is offline or gave
3467           # an error.
3468           continue
3469         for script, hkr, output in res.payload:
3470           test = hkr == constants.HKR_FAIL
3471           self._ErrorIf(test, constants.CV_ENODEHOOKS, node_name,
3472                         "Script %s failed, output:", script)
3473           if test:
3474             output = self._HOOKS_INDENT_RE.sub("      ", output)
3475             feedback_fn("%s" % output)
3476             lu_result = False
3477
3478     return lu_result
3479
3480
3481 class LUClusterVerifyDisks(NoHooksLU):
3482   """Verifies the cluster disks status.
3483
3484   """
3485   REQ_BGL = False
3486
3487   def ExpandNames(self):
3488     self.share_locks = _ShareAll()
3489     self.needed_locks = {
3490       locking.LEVEL_NODEGROUP: locking.ALL_SET,
3491       }
3492
3493   def Exec(self, feedback_fn):
3494     group_names = self.owned_locks(locking.LEVEL_NODEGROUP)
3495
3496     # Submit one instance of L{opcodes.OpGroupVerifyDisks} per node group
3497     return ResultWithJobs([[opcodes.OpGroupVerifyDisks(group_name=group)]
3498                            for group in group_names])
3499
3500
3501 class LUGroupVerifyDisks(NoHooksLU):
3502   """Verifies the status of all disks in a node group.
3503
3504   """
3505   REQ_BGL = False
3506
3507   def ExpandNames(self):
3508     # Raises errors.OpPrereqError on its own if group can't be found
3509     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
3510
3511     self.share_locks = _ShareAll()
3512     self.needed_locks = {
3513       locking.LEVEL_INSTANCE: [],
3514       locking.LEVEL_NODEGROUP: [],
3515       locking.LEVEL_NODE: [],
3516       }
3517
3518   def DeclareLocks(self, level):
3519     if level == locking.LEVEL_INSTANCE:
3520       assert not self.needed_locks[locking.LEVEL_INSTANCE]
3521
3522       # Lock instances optimistically, needs verification once node and group
3523       # locks have been acquired
3524       self.needed_locks[locking.LEVEL_INSTANCE] = \
3525         self.cfg.GetNodeGroupInstances(self.group_uuid)
3526
3527     elif level == locking.LEVEL_NODEGROUP:
3528       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
3529
3530       self.needed_locks[locking.LEVEL_NODEGROUP] = \
3531         set([self.group_uuid] +
3532             # Lock all groups used by instances optimistically; this requires
3533             # going via the node before it's locked, requiring verification
3534             # later on
3535             [group_uuid
3536              for instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
3537              for group_uuid in self.cfg.GetInstanceNodeGroups(instance_name)])
3538
3539     elif level == locking.LEVEL_NODE:
3540       # This will only lock the nodes in the group to be verified which contain
3541       # actual instances
3542       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
3543       self._LockInstancesNodes()
3544
3545       # Lock all nodes in group to be verified
3546       assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
3547       member_nodes = self.cfg.GetNodeGroup(self.group_uuid).members
3548       self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
3549
3550   def CheckPrereq(self):
3551     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
3552     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
3553     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
3554
3555     assert self.group_uuid in owned_groups
3556
3557     # Check if locked instances are still correct
3558     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
3559
3560     # Get instance information
3561     self.instances = dict(self.cfg.GetMultiInstanceInfo(owned_instances))
3562
3563     # Check if node groups for locked instances are still correct
3564     _CheckInstancesNodeGroups(self.cfg, self.instances,
3565                               owned_groups, owned_nodes, self.group_uuid)
3566
3567   def Exec(self, feedback_fn):
3568     """Verify integrity of cluster disks.
3569
3570     @rtype: tuple of three items
3571     @return: a tuple of (dict of node-to-node_error, list of instances
3572         which need activate-disks, dict of instance: (node, volume) for
3573         missing volumes
3574
3575     """
3576     res_nodes = {}
3577     res_instances = set()
3578     res_missing = {}
3579
3580     nv_dict = _MapInstanceDisksToNodes(
3581       [inst for inst in self.instances.values()
3582        if inst.admin_state == constants.ADMINST_UP])
3583
3584     if nv_dict:
3585       nodes = utils.NiceSort(set(self.owned_locks(locking.LEVEL_NODE)) &
3586                              set(self.cfg.GetVmCapableNodeList()))
3587
3588       node_lvs = self.rpc.call_lv_list(nodes, [])
3589
3590       for (node, node_res) in node_lvs.items():
3591         if node_res.offline:
3592           continue
3593
3594         msg = node_res.fail_msg
3595         if msg:
3596           logging.warning("Error enumerating LVs on node %s: %s", node, msg)
3597           res_nodes[node] = msg
3598           continue
3599
3600         for lv_name, (_, _, lv_online) in node_res.payload.items():
3601           inst = nv_dict.pop((node, lv_name), None)
3602           if not (lv_online or inst is None):
3603             res_instances.add(inst)
3604
3605       # any leftover items in nv_dict are missing LVs, let's arrange the data
3606       # better
3607       for key, inst in nv_dict.iteritems():
3608         res_missing.setdefault(inst, []).append(list(key))
3609
3610     return (res_nodes, list(res_instances), res_missing)
3611
3612
3613 class LUClusterRepairDiskSizes(NoHooksLU):
3614   """Verifies the cluster disks sizes.
3615
3616   """
3617   REQ_BGL = False
3618
3619   def ExpandNames(self):
3620     if self.op.instances:
3621       self.wanted_names = _GetWantedInstances(self, self.op.instances)
3622       self.needed_locks = {
3623         locking.LEVEL_NODE_RES: [],
3624         locking.LEVEL_INSTANCE: self.wanted_names,
3625         }
3626       self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
3627     else:
3628       self.wanted_names = None
3629       self.needed_locks = {
3630         locking.LEVEL_NODE_RES: locking.ALL_SET,
3631         locking.LEVEL_INSTANCE: locking.ALL_SET,
3632         }
3633     self.share_locks = {
3634       locking.LEVEL_NODE_RES: 1,
3635       locking.LEVEL_INSTANCE: 0,
3636       }
3637
3638   def DeclareLocks(self, level):
3639     if level == locking.LEVEL_NODE_RES and self.wanted_names is not None:
3640       self._LockInstancesNodes(primary_only=True, level=level)
3641
3642   def CheckPrereq(self):
3643     """Check prerequisites.
3644
3645     This only checks the optional instance list against the existing names.
3646
3647     """
3648     if self.wanted_names is None:
3649       self.wanted_names = self.owned_locks(locking.LEVEL_INSTANCE)
3650
3651     self.wanted_instances = \
3652         map(compat.snd, self.cfg.GetMultiInstanceInfo(self.wanted_names))
3653
3654   def _EnsureChildSizes(self, disk):
3655     """Ensure children of the disk have the needed disk size.
3656
3657     This is valid mainly for DRBD8 and fixes an issue where the
3658     children have smaller disk size.
3659
3660     @param disk: an L{ganeti.objects.Disk} object
3661
3662     """
3663     if disk.dev_type == constants.LD_DRBD8:
3664       assert disk.children, "Empty children for DRBD8?"
3665       fchild = disk.children[0]
3666       mismatch = fchild.size < disk.size
3667       if mismatch:
3668         self.LogInfo("Child disk has size %d, parent %d, fixing",
3669                      fchild.size, disk.size)
3670         fchild.size = disk.size
3671
3672       # and we recurse on this child only, not on the metadev
3673       return self._EnsureChildSizes(fchild) or mismatch
3674     else:
3675       return False
3676
3677   def Exec(self, feedback_fn):
3678     """Verify the size of cluster disks.
3679
3680     """
3681     # TODO: check child disks too
3682     # TODO: check differences in size between primary/secondary nodes
3683     per_node_disks = {}
3684     for instance in self.wanted_instances:
3685       pnode = instance.primary_node
3686       if pnode not in per_node_disks:
3687         per_node_disks[pnode] = []
3688       for idx, disk in enumerate(instance.disks):
3689         per_node_disks[pnode].append((instance, idx, disk))
3690
3691     assert not (frozenset(per_node_disks.keys()) -
3692                 self.owned_locks(locking.LEVEL_NODE_RES)), \
3693       "Not owning correct locks"
3694     assert not self.owned_locks(locking.LEVEL_NODE)
3695
3696     changed = []
3697     for node, dskl in per_node_disks.items():
3698       newl = [v[2].Copy() for v in dskl]
3699       for dsk in newl:
3700         self.cfg.SetDiskID(dsk, node)
3701       result = self.rpc.call_blockdev_getsize(node, newl)
3702       if result.fail_msg:
3703         self.LogWarning("Failure in blockdev_getsize call to node"
3704                         " %s, ignoring", node)
3705         continue
3706       if len(result.payload) != len(dskl):
3707         logging.warning("Invalid result from node %s: len(dksl)=%d,"
3708                         " result.payload=%s", node, len(dskl), result.payload)
3709         self.LogWarning("Invalid result from node %s, ignoring node results",
3710                         node)
3711         continue
3712       for ((instance, idx, disk), size) in zip(dskl, result.payload):
3713         if size is None:
3714           self.LogWarning("Disk %d of instance %s did not return size"
3715                           " information, ignoring", idx, instance.name)
3716           continue
3717         if not isinstance(size, (int, long)):
3718           self.LogWarning("Disk %d of instance %s did not return valid"
3719                           " size information, ignoring", idx, instance.name)
3720           continue
3721         size = size >> 20
3722         if size != disk.size:
3723           self.LogInfo("Disk %d of instance %s has mismatched size,"
3724                        " correcting: recorded %d, actual %d", idx,
3725                        instance.name, disk.size, size)
3726           disk.size = size
3727           self.cfg.Update(instance, feedback_fn)
3728           changed.append((instance.name, idx, size))
3729         if self._EnsureChildSizes(disk):
3730           self.cfg.Update(instance, feedback_fn)
3731           changed.append((instance.name, idx, disk.size))
3732     return changed
3733
3734
3735 class LUClusterRename(LogicalUnit):
3736   """Rename the cluster.
3737
3738   """
3739   HPATH = "cluster-rename"
3740   HTYPE = constants.HTYPE_CLUSTER
3741
3742   def BuildHooksEnv(self):
3743     """Build hooks env.
3744
3745     """
3746     return {
3747       "OP_TARGET": self.cfg.GetClusterName(),
3748       "NEW_NAME": self.op.name,
3749       }
3750
3751   def BuildHooksNodes(self):
3752     """Build hooks nodes.
3753
3754     """
3755     return ([self.cfg.GetMasterNode()], self.cfg.GetNodeList())
3756
3757   def CheckPrereq(self):
3758     """Verify that the passed name is a valid one.
3759
3760     """
3761     hostname = netutils.GetHostname(name=self.op.name,
3762                                     family=self.cfg.GetPrimaryIPFamily())
3763
3764     new_name = hostname.name
3765     self.ip = new_ip = hostname.ip
3766     old_name = self.cfg.GetClusterName()
3767     old_ip = self.cfg.GetMasterIP()
3768     if new_name == old_name and new_ip == old_ip:
3769       raise errors.OpPrereqError("Neither the name nor the IP address of the"
3770                                  " cluster has changed",
3771                                  errors.ECODE_INVAL)
3772     if new_ip != old_ip:
3773       if netutils.TcpPing(new_ip, constants.DEFAULT_NODED_PORT):
3774         raise errors.OpPrereqError("The given cluster IP address (%s) is"
3775                                    " reachable on the network" %
3776                                    new_ip, errors.ECODE_NOTUNIQUE)
3777
3778     self.op.name = new_name
3779
3780   def Exec(self, feedback_fn):
3781     """Rename the cluster.
3782
3783     """
3784     clustername = self.op.name
3785     new_ip = self.ip
3786
3787     # shutdown the master IP
3788     master_params = self.cfg.GetMasterNetworkParameters()
3789     ems = self.cfg.GetUseExternalMipScript()
3790     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
3791                                                      master_params, ems)
3792     result.Raise("Could not disable the master role")
3793
3794     try:
3795       cluster = self.cfg.GetClusterInfo()
3796       cluster.cluster_name = clustername
3797       cluster.master_ip = new_ip
3798       self.cfg.Update(cluster, feedback_fn)
3799
3800       # update the known hosts file
3801       ssh.WriteKnownHostsFile(self.cfg, pathutils.SSH_KNOWN_HOSTS_FILE)
3802       node_list = self.cfg.GetOnlineNodeList()
3803       try:
3804         node_list.remove(master_params.name)
3805       except ValueError:
3806         pass
3807       _UploadHelper(self, node_list, pathutils.SSH_KNOWN_HOSTS_FILE)
3808     finally:
3809       master_params.ip = new_ip
3810       result = self.rpc.call_node_activate_master_ip(master_params.name,
3811                                                      master_params, ems)
3812       msg = result.fail_msg
3813       if msg:
3814         self.LogWarning("Could not re-enable the master role on"
3815                         " the master, please restart manually: %s", msg)
3816
3817     return clustername
3818
3819
3820 def _ValidateNetmask(cfg, netmask):
3821   """Checks if a netmask is valid.
3822
3823   @type cfg: L{config.ConfigWriter}
3824   @param cfg: The cluster configuration
3825   @type netmask: int
3826   @param netmask: the netmask to be verified
3827   @raise errors.OpPrereqError: if the validation fails
3828
3829   """
3830   ip_family = cfg.GetPrimaryIPFamily()
3831   try:
3832     ipcls = netutils.IPAddress.GetClassFromIpFamily(ip_family)
3833   except errors.ProgrammerError:
3834     raise errors.OpPrereqError("Invalid primary ip family: %s." %
3835                                ip_family, errors.ECODE_INVAL)
3836   if not ipcls.ValidateNetmask(netmask):
3837     raise errors.OpPrereqError("CIDR netmask (%s) not valid" %
3838                                 (netmask), errors.ECODE_INVAL)
3839
3840
3841 class LUClusterSetParams(LogicalUnit):
3842   """Change the parameters of the cluster.
3843
3844   """
3845   HPATH = "cluster-modify"
3846   HTYPE = constants.HTYPE_CLUSTER
3847   REQ_BGL = False
3848
3849   def CheckArguments(self):
3850     """Check parameters
3851
3852     """
3853     if self.op.uid_pool:
3854       uidpool.CheckUidPool(self.op.uid_pool)
3855
3856     if self.op.add_uids:
3857       uidpool.CheckUidPool(self.op.add_uids)
3858
3859     if self.op.remove_uids:
3860       uidpool.CheckUidPool(self.op.remove_uids)
3861
3862     if self.op.master_netmask is not None:
3863       _ValidateNetmask(self.cfg, self.op.master_netmask)
3864
3865     if self.op.diskparams:
3866       for dt_params in self.op.diskparams.values():
3867         utils.ForceDictType(dt_params, constants.DISK_DT_TYPES)
3868       try:
3869         utils.VerifyDictOptions(self.op.diskparams, constants.DISK_DT_DEFAULTS)
3870       except errors.OpPrereqError, err:
3871         raise errors.OpPrereqError("While verify diskparams options: %s" % err,
3872                                    errors.ECODE_INVAL)
3873
3874   def ExpandNames(self):
3875     # FIXME: in the future maybe other cluster params won't require checking on
3876     # all nodes to be modified.
3877     self.needed_locks = {
3878       locking.LEVEL_NODE: locking.ALL_SET,
3879       locking.LEVEL_INSTANCE: locking.ALL_SET,
3880       locking.LEVEL_NODEGROUP: locking.ALL_SET,
3881     }
3882     self.share_locks = {
3883         locking.LEVEL_NODE: 1,
3884         locking.LEVEL_INSTANCE: 1,
3885         locking.LEVEL_NODEGROUP: 1,
3886     }
3887
3888   def BuildHooksEnv(self):
3889     """Build hooks env.
3890
3891     """
3892     return {
3893       "OP_TARGET": self.cfg.GetClusterName(),
3894       "NEW_VG_NAME": self.op.vg_name,
3895       }
3896
3897   def BuildHooksNodes(self):
3898     """Build hooks nodes.
3899
3900     """
3901     mn = self.cfg.GetMasterNode()
3902     return ([mn], [mn])
3903
3904   def CheckPrereq(self):
3905     """Check prerequisites.
3906
3907     This checks whether the given params don't conflict and
3908     if the given volume group is valid.
3909
3910     """
3911     if self.op.vg_name is not None and not self.op.vg_name:
3912       if self.cfg.HasAnyDiskOfType(constants.LD_LV):
3913         raise errors.OpPrereqError("Cannot disable lvm storage while lvm-based"
3914                                    " instances exist", errors.ECODE_INVAL)
3915
3916     if self.op.drbd_helper is not None and not self.op.drbd_helper:
3917       if self.cfg.HasAnyDiskOfType(constants.LD_DRBD8):
3918         raise errors.OpPrereqError("Cannot disable drbd helper while"
3919                                    " drbd-based instances exist",
3920                                    errors.ECODE_INVAL)
3921
3922     node_list = self.owned_locks(locking.LEVEL_NODE)
3923
3924     # if vg_name not None, checks given volume group on all nodes
3925     if self.op.vg_name:
3926       vglist = self.rpc.call_vg_list(node_list)
3927       for node in node_list:
3928         msg = vglist[node].fail_msg
3929         if msg:
3930           # ignoring down node
3931           self.LogWarning("Error while gathering data on node %s"
3932                           " (ignoring node): %s", node, msg)
3933           continue
3934         vgstatus = utils.CheckVolumeGroupSize(vglist[node].payload,
3935                                               self.op.vg_name,
3936                                               constants.MIN_VG_SIZE)
3937         if vgstatus:
3938           raise errors.OpPrereqError("Error on node '%s': %s" %
3939                                      (node, vgstatus), errors.ECODE_ENVIRON)
3940
3941     if self.op.drbd_helper:
3942       # checks given drbd helper on all nodes
3943       helpers = self.rpc.call_drbd_helper(node_list)
3944       for (node, ninfo) in self.cfg.GetMultiNodeInfo(node_list):
3945         if ninfo.offline:
3946           self.LogInfo("Not checking drbd helper on offline node %s", node)
3947           continue
3948         msg = helpers[node].fail_msg
3949         if msg:
3950           raise errors.OpPrereqError("Error checking drbd helper on node"
3951                                      " '%s': %s" % (node, msg),
3952                                      errors.ECODE_ENVIRON)
3953         node_helper = helpers[node].payload
3954         if node_helper != self.op.drbd_helper:
3955           raise errors.OpPrereqError("Error on node '%s': drbd helper is %s" %
3956                                      (node, node_helper), errors.ECODE_ENVIRON)
3957
3958     self.cluster = cluster = self.cfg.GetClusterInfo()
3959     # validate params changes
3960     if self.op.beparams:
3961       objects.UpgradeBeParams(self.op.beparams)
3962       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
3963       self.new_beparams = cluster.SimpleFillBE(self.op.beparams)
3964
3965     if self.op.ndparams:
3966       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
3967       self.new_ndparams = cluster.SimpleFillND(self.op.ndparams)
3968
3969       # TODO: we need a more general way to handle resetting
3970       # cluster-level parameters to default values
3971       if self.new_ndparams["oob_program"] == "":
3972         self.new_ndparams["oob_program"] = \
3973             constants.NDC_DEFAULTS[constants.ND_OOB_PROGRAM]
3974
3975     if self.op.hv_state:
3976       new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
3977                                             self.cluster.hv_state_static)
3978       self.new_hv_state = dict((hv, cluster.SimpleFillHvState(values))
3979                                for hv, values in new_hv_state.items())
3980
3981     if self.op.disk_state:
3982       new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state,
3983                                                 self.cluster.disk_state_static)
3984       self.new_disk_state = \
3985         dict((storage, dict((name, cluster.SimpleFillDiskState(values))
3986                             for name, values in svalues.items()))
3987              for storage, svalues in new_disk_state.items())
3988
3989     if self.op.ipolicy:
3990       self.new_ipolicy = _GetUpdatedIPolicy(cluster.ipolicy, self.op.ipolicy,
3991                                             group_policy=False)
3992
3993       all_instances = self.cfg.GetAllInstancesInfo().values()
3994       violations = set()
3995       for group in self.cfg.GetAllNodeGroupsInfo().values():
3996         instances = frozenset([inst for inst in all_instances
3997                                if compat.any(node in group.members
3998                                              for node in inst.all_nodes)])
3999         new_ipolicy = objects.FillIPolicy(self.new_ipolicy, group.ipolicy)
4000         ipol = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group)
4001         new = _ComputeNewInstanceViolations(ipol,
4002                                             new_ipolicy, instances)
4003         if new:
4004           violations.update(new)
4005
4006       if violations:
4007         self.LogWarning("After the ipolicy change the following instances"
4008                         " violate them: %s",
4009                         utils.CommaJoin(utils.NiceSort(violations)))
4010
4011     if self.op.nicparams:
4012       utils.ForceDictType(self.op.nicparams, constants.NICS_PARAMETER_TYPES)
4013       self.new_nicparams = cluster.SimpleFillNIC(self.op.nicparams)
4014       objects.NIC.CheckParameterSyntax(self.new_nicparams)
4015       nic_errors = []
4016
4017       # check all instances for consistency
4018       for instance in self.cfg.GetAllInstancesInfo().values():
4019         for nic_idx, nic in enumerate(instance.nics):
4020           params_copy = copy.deepcopy(nic.nicparams)
4021           params_filled = objects.FillDict(self.new_nicparams, params_copy)
4022
4023           # check parameter syntax
4024           try:
4025             objects.NIC.CheckParameterSyntax(params_filled)
4026           except errors.ConfigurationError, err:
4027             nic_errors.append("Instance %s, nic/%d: %s" %
4028                               (instance.name, nic_idx, err))
4029
4030           # if we're moving instances to routed, check that they have an ip
4031           target_mode = params_filled[constants.NIC_MODE]
4032           if target_mode == constants.NIC_MODE_ROUTED and not nic.ip:
4033             nic_errors.append("Instance %s, nic/%d: routed NIC with no ip"
4034                               " address" % (instance.name, nic_idx))
4035       if nic_errors:
4036         raise errors.OpPrereqError("Cannot apply the change, errors:\n%s" %
4037                                    "\n".join(nic_errors), errors.ECODE_INVAL)
4038
4039     # hypervisor list/parameters
4040     self.new_hvparams = new_hvp = objects.FillDict(cluster.hvparams, {})
4041     if self.op.hvparams:
4042       for hv_name, hv_dict in self.op.hvparams.items():
4043         if hv_name not in self.new_hvparams:
4044           self.new_hvparams[hv_name] = hv_dict
4045         else:
4046           self.new_hvparams[hv_name].update(hv_dict)
4047
4048     # disk template parameters
4049     self.new_diskparams = objects.FillDict(cluster.diskparams, {})
4050     if self.op.diskparams:
4051       for dt_name, dt_params in self.op.diskparams.items():
4052         if dt_name not in self.op.diskparams:
4053           self.new_diskparams[dt_name] = dt_params
4054         else:
4055           self.new_diskparams[dt_name].update(dt_params)
4056
4057     # os hypervisor parameters
4058     self.new_os_hvp = objects.FillDict(cluster.os_hvp, {})
4059     if self.op.os_hvp:
4060       for os_name, hvs in self.op.os_hvp.items():
4061         if os_name not in self.new_os_hvp:
4062           self.new_os_hvp[os_name] = hvs
4063         else:
4064           for hv_name, hv_dict in hvs.items():
4065             if hv_name not in self.new_os_hvp[os_name]:
4066               self.new_os_hvp[os_name][hv_name] = hv_dict
4067             else:
4068               self.new_os_hvp[os_name][hv_name].update(hv_dict)
4069
4070     # os parameters
4071     self.new_osp = objects.FillDict(cluster.osparams, {})
4072     if self.op.osparams:
4073       for os_name, osp in self.op.osparams.items():
4074         if os_name not in self.new_osp:
4075           self.new_osp[os_name] = {}
4076
4077         self.new_osp[os_name] = _GetUpdatedParams(self.new_osp[os_name], osp,
4078                                                   use_none=True)
4079
4080         if not self.new_osp[os_name]:
4081           # we removed all parameters
4082           del self.new_osp[os_name]
4083         else:
4084           # check the parameter validity (remote check)
4085           _CheckOSParams(self, False, [self.cfg.GetMasterNode()],
4086                          os_name, self.new_osp[os_name])
4087
4088     # changes to the hypervisor list
4089     if self.op.enabled_hypervisors is not None:
4090       self.hv_list = self.op.enabled_hypervisors
4091       for hv in self.hv_list:
4092         # if the hypervisor doesn't already exist in the cluster
4093         # hvparams, we initialize it to empty, and then (in both
4094         # cases) we make sure to fill the defaults, as we might not
4095         # have a complete defaults list if the hypervisor wasn't
4096         # enabled before
4097         if hv not in new_hvp:
4098           new_hvp[hv] = {}
4099         new_hvp[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], new_hvp[hv])
4100         utils.ForceDictType(new_hvp[hv], constants.HVS_PARAMETER_TYPES)
4101     else:
4102       self.hv_list = cluster.enabled_hypervisors
4103
4104     if self.op.hvparams or self.op.enabled_hypervisors is not None:
4105       # either the enabled list has changed, or the parameters have, validate
4106       for hv_name, hv_params in self.new_hvparams.items():
4107         if ((self.op.hvparams and hv_name in self.op.hvparams) or
4108             (self.op.enabled_hypervisors and
4109              hv_name in self.op.enabled_hypervisors)):
4110           # either this is a new hypervisor, or its parameters have changed
4111           hv_class = hypervisor.GetHypervisor(hv_name)
4112           utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
4113           hv_class.CheckParameterSyntax(hv_params)
4114           _CheckHVParams(self, node_list, hv_name, hv_params)
4115
4116     if self.op.os_hvp:
4117       # no need to check any newly-enabled hypervisors, since the
4118       # defaults have already been checked in the above code-block
4119       for os_name, os_hvp in self.new_os_hvp.items():
4120         for hv_name, hv_params in os_hvp.items():
4121           utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
4122           # we need to fill in the new os_hvp on top of the actual hv_p
4123           cluster_defaults = self.new_hvparams.get(hv_name, {})
4124           new_osp = objects.FillDict(cluster_defaults, hv_params)
4125           hv_class = hypervisor.GetHypervisor(hv_name)
4126           hv_class.CheckParameterSyntax(new_osp)
4127           _CheckHVParams(self, node_list, hv_name, new_osp)
4128
4129     if self.op.default_iallocator:
4130       alloc_script = utils.FindFile(self.op.default_iallocator,
4131                                     constants.IALLOCATOR_SEARCH_PATH,
4132                                     os.path.isfile)
4133       if alloc_script is None:
4134         raise errors.OpPrereqError("Invalid default iallocator script '%s'"
4135                                    " specified" % self.op.default_iallocator,
4136                                    errors.ECODE_INVAL)
4137
4138   def Exec(self, feedback_fn):
4139     """Change the parameters of the cluster.
4140
4141     """
4142     if self.op.vg_name is not None:
4143       new_volume = self.op.vg_name
4144       if not new_volume:
4145         new_volume = None
4146       if new_volume != self.cfg.GetVGName():
4147         self.cfg.SetVGName(new_volume)
4148       else:
4149         feedback_fn("Cluster LVM configuration already in desired"
4150                     " state, not changing")
4151     if self.op.drbd_helper is not None:
4152       new_helper = self.op.drbd_helper
4153       if not new_helper:
4154         new_helper = None
4155       if new_helper != self.cfg.GetDRBDHelper():
4156         self.cfg.SetDRBDHelper(new_helper)
4157       else:
4158         feedback_fn("Cluster DRBD helper already in desired state,"
4159                     " not changing")
4160     if self.op.hvparams:
4161       self.cluster.hvparams = self.new_hvparams
4162     if self.op.os_hvp:
4163       self.cluster.os_hvp = self.new_os_hvp
4164     if self.op.enabled_hypervisors is not None:
4165       self.cluster.hvparams = self.new_hvparams
4166       self.cluster.enabled_hypervisors = self.op.enabled_hypervisors
4167     if self.op.beparams:
4168       self.cluster.beparams[constants.PP_DEFAULT] = self.new_beparams
4169     if self.op.nicparams:
4170       self.cluster.nicparams[constants.PP_DEFAULT] = self.new_nicparams
4171     if self.op.ipolicy:
4172       self.cluster.ipolicy = self.new_ipolicy
4173     if self.op.osparams:
4174       self.cluster.osparams = self.new_osp
4175     if self.op.ndparams:
4176       self.cluster.ndparams = self.new_ndparams
4177     if self.op.diskparams:
4178       self.cluster.diskparams = self.new_diskparams
4179     if self.op.hv_state:
4180       self.cluster.hv_state_static = self.new_hv_state
4181     if self.op.disk_state:
4182       self.cluster.disk_state_static = self.new_disk_state
4183
4184     if self.op.candidate_pool_size is not None:
4185       self.cluster.candidate_pool_size = self.op.candidate_pool_size
4186       # we need to update the pool size here, otherwise the save will fail
4187       _AdjustCandidatePool(self, [])
4188
4189     if self.op.maintain_node_health is not None:
4190       if self.op.maintain_node_health and not constants.ENABLE_CONFD:
4191         feedback_fn("Note: CONFD was disabled at build time, node health"
4192                     " maintenance is not useful (still enabling it)")
4193       self.cluster.maintain_node_health = self.op.maintain_node_health
4194
4195     if self.op.prealloc_wipe_disks is not None:
4196       self.cluster.prealloc_wipe_disks = self.op.prealloc_wipe_disks
4197
4198     if self.op.add_uids is not None:
4199       uidpool.AddToUidPool(self.cluster.uid_pool, self.op.add_uids)
4200
4201     if self.op.remove_uids is not None:
4202       uidpool.RemoveFromUidPool(self.cluster.uid_pool, self.op.remove_uids)
4203
4204     if self.op.uid_pool is not None:
4205       self.cluster.uid_pool = self.op.uid_pool
4206
4207     if self.op.default_iallocator is not None:
4208       self.cluster.default_iallocator = self.op.default_iallocator
4209
4210     if self.op.reserved_lvs is not None:
4211       self.cluster.reserved_lvs = self.op.reserved_lvs
4212
4213     if self.op.use_external_mip_script is not None:
4214       self.cluster.use_external_mip_script = self.op.use_external_mip_script
4215
4216     def helper_os(aname, mods, desc):
4217       desc += " OS list"
4218       lst = getattr(self.cluster, aname)
4219       for key, val in mods:
4220         if key == constants.DDM_ADD:
4221           if val in lst:
4222             feedback_fn("OS %s already in %s, ignoring" % (val, desc))
4223           else:
4224             lst.append(val)
4225         elif key == constants.DDM_REMOVE:
4226           if val in lst:
4227             lst.remove(val)
4228           else:
4229             feedback_fn("OS %s not found in %s, ignoring" % (val, desc))
4230         else:
4231           raise errors.ProgrammerError("Invalid modification '%s'" % key)
4232
4233     if self.op.hidden_os:
4234       helper_os("hidden_os", self.op.hidden_os, "hidden")
4235
4236     if self.op.blacklisted_os:
4237       helper_os("blacklisted_os", self.op.blacklisted_os, "blacklisted")
4238
4239     if self.op.master_netdev:
4240       master_params = self.cfg.GetMasterNetworkParameters()
4241       ems = self.cfg.GetUseExternalMipScript()
4242       feedback_fn("Shutting down master ip on the current netdev (%s)" %
4243                   self.cluster.master_netdev)
4244       result = self.rpc.call_node_deactivate_master_ip(master_params.name,
4245                                                        master_params, ems)
4246       result.Raise("Could not disable the master ip")
4247       feedback_fn("Changing master_netdev from %s to %s" %
4248                   (master_params.netdev, self.op.master_netdev))
4249       self.cluster.master_netdev = self.op.master_netdev
4250
4251     if self.op.master_netmask:
4252       master_params = self.cfg.GetMasterNetworkParameters()
4253       feedback_fn("Changing master IP netmask to %s" % self.op.master_netmask)
4254       result = self.rpc.call_node_change_master_netmask(master_params.name,
4255                                                         master_params.netmask,
4256                                                         self.op.master_netmask,
4257                                                         master_params.ip,
4258                                                         master_params.netdev)
4259       if result.fail_msg:
4260         msg = "Could not change the master IP netmask: %s" % result.fail_msg
4261         feedback_fn(msg)
4262
4263       self.cluster.master_netmask = self.op.master_netmask
4264
4265     self.cfg.Update(self.cluster, feedback_fn)
4266
4267     if self.op.master_netdev:
4268       master_params = self.cfg.GetMasterNetworkParameters()
4269       feedback_fn("Starting the master ip on the new master netdev (%s)" %
4270                   self.op.master_netdev)
4271       ems = self.cfg.GetUseExternalMipScript()
4272       result = self.rpc.call_node_activate_master_ip(master_params.name,
4273                                                      master_params, ems)
4274       if result.fail_msg:
4275         self.LogWarning("Could not re-enable the master ip on"
4276                         " the master, please restart manually: %s",
4277                         result.fail_msg)
4278
4279
4280 def _UploadHelper(lu, nodes, fname):
4281   """Helper for uploading a file and showing warnings.
4282
4283   """
4284   if os.path.exists(fname):
4285     result = lu.rpc.call_upload_file(nodes, fname)
4286     for to_node, to_result in result.items():
4287       msg = to_result.fail_msg
4288       if msg:
4289         msg = ("Copy of file %s to node %s failed: %s" %
4290                (fname, to_node, msg))
4291         lu.proc.LogWarning(msg)
4292
4293
4294 def _ComputeAncillaryFiles(cluster, redist):
4295   """Compute files external to Ganeti which need to be consistent.
4296
4297   @type redist: boolean
4298   @param redist: Whether to include files which need to be redistributed
4299
4300   """
4301   # Compute files for all nodes
4302   files_all = set([
4303     pathutils.SSH_KNOWN_HOSTS_FILE,
4304     pathutils.CONFD_HMAC_KEY,
4305     pathutils.CLUSTER_DOMAIN_SECRET_FILE,
4306     pathutils.SPICE_CERT_FILE,
4307     pathutils.SPICE_CACERT_FILE,
4308     pathutils.RAPI_USERS_FILE,
4309     ])
4310
4311   if redist:
4312     # we need to ship at least the RAPI certificate
4313     files_all.add(pathutils.RAPI_CERT_FILE)
4314   else:
4315     files_all.update(pathutils.ALL_CERT_FILES)
4316     files_all.update(ssconf.SimpleStore().GetFileList())
4317
4318   if cluster.modify_etc_hosts:
4319     files_all.add(pathutils.ETC_HOSTS)
4320
4321   if cluster.use_external_mip_script:
4322     files_all.add(pathutils.EXTERNAL_MASTER_SETUP_SCRIPT)
4323
4324   # Files which are optional, these must:
4325   # - be present in one other category as well
4326   # - either exist or not exist on all nodes of that category (mc, vm all)
4327   files_opt = set([
4328     pathutils.RAPI_USERS_FILE,
4329     ])
4330
4331   # Files which should only be on master candidates
4332   files_mc = set()
4333
4334   if not redist:
4335     files_mc.add(pathutils.CLUSTER_CONF_FILE)
4336
4337   # File storage
4338   if (not redist and
4339       (constants.ENABLE_FILE_STORAGE or constants.ENABLE_SHARED_FILE_STORAGE)):
4340     files_all.add(pathutils.FILE_STORAGE_PATHS_FILE)
4341     files_opt.add(pathutils.FILE_STORAGE_PATHS_FILE)
4342
4343   # Files which should only be on VM-capable nodes
4344   files_vm = set(
4345     filename
4346     for hv_name in cluster.enabled_hypervisors
4347     for filename in hypervisor.GetHypervisor(hv_name).GetAncillaryFiles()[0])
4348
4349   files_opt |= set(
4350     filename
4351     for hv_name in cluster.enabled_hypervisors
4352     for filename in hypervisor.GetHypervisor(hv_name).GetAncillaryFiles()[1])
4353
4354   # Filenames in each category must be unique
4355   all_files_set = files_all | files_mc | files_vm
4356   assert (len(all_files_set) ==
4357           sum(map(len, [files_all, files_mc, files_vm]))), \
4358          "Found file listed in more than one file list"
4359
4360   # Optional files must be present in one other category
4361   assert all_files_set.issuperset(files_opt), \
4362          "Optional file not in a different required list"
4363
4364   # This one file should never ever be re-distributed via RPC
4365   assert not (redist and
4366               pathutils.FILE_STORAGE_PATHS_FILE in all_files_set)
4367
4368   return (files_all, files_opt, files_mc, files_vm)
4369
4370
4371 def _RedistributeAncillaryFiles(lu, additional_nodes=None, additional_vm=True):
4372   """Distribute additional files which are part of the cluster configuration.
4373
4374   ConfigWriter takes care of distributing the config and ssconf files, but
4375   there are more files which should be distributed to all nodes. This function
4376   makes sure those are copied.
4377
4378   @param lu: calling logical unit
4379   @param additional_nodes: list of nodes not in the config to distribute to
4380   @type additional_vm: boolean
4381   @param additional_vm: whether the additional nodes are vm-capable or not
4382
4383   """
4384   # Gather target nodes
4385   cluster = lu.cfg.GetClusterInfo()
4386   master_info = lu.cfg.GetNodeInfo(lu.cfg.GetMasterNode())
4387
4388   online_nodes = lu.cfg.GetOnlineNodeList()
4389   online_set = frozenset(online_nodes)
4390   vm_nodes = list(online_set.intersection(lu.cfg.GetVmCapableNodeList()))
4391
4392   if additional_nodes is not None:
4393     online_nodes.extend(additional_nodes)
4394     if additional_vm:
4395       vm_nodes.extend(additional_nodes)
4396
4397   # Never distribute to master node
4398   for nodelist in [online_nodes, vm_nodes]:
4399     if master_info.name in nodelist:
4400       nodelist.remove(master_info.name)
4401
4402   # Gather file lists
4403   (files_all, _, files_mc, files_vm) = \
4404     _ComputeAncillaryFiles(cluster, True)
4405
4406   # Never re-distribute configuration file from here
4407   assert not (pathutils.CLUSTER_CONF_FILE in files_all or
4408               pathutils.CLUSTER_CONF_FILE in files_vm)
4409   assert not files_mc, "Master candidates not handled in this function"
4410
4411   filemap = [
4412     (online_nodes, files_all),
4413     (vm_nodes, files_vm),
4414     ]
4415
4416   # Upload the files
4417   for (node_list, files) in filemap:
4418     for fname in files:
4419       _UploadHelper(lu, node_list, fname)
4420
4421
4422 class LUClusterRedistConf(NoHooksLU):
4423   """Force the redistribution of cluster configuration.
4424
4425   This is a very simple LU.
4426
4427   """
4428   REQ_BGL = False
4429
4430   def ExpandNames(self):
4431     self.needed_locks = {
4432       locking.LEVEL_NODE: locking.ALL_SET,
4433     }
4434     self.share_locks[locking.LEVEL_NODE] = 1
4435
4436   def Exec(self, feedback_fn):
4437     """Redistribute the configuration.
4438
4439     """
4440     self.cfg.Update(self.cfg.GetClusterInfo(), feedback_fn)
4441     _RedistributeAncillaryFiles(self)
4442
4443
4444 class LUClusterActivateMasterIp(NoHooksLU):
4445   """Activate the master IP on the master node.
4446
4447   """
4448   def Exec(self, feedback_fn):
4449     """Activate the master IP.
4450
4451     """
4452     master_params = self.cfg.GetMasterNetworkParameters()
4453     ems = self.cfg.GetUseExternalMipScript()
4454     result = self.rpc.call_node_activate_master_ip(master_params.name,
4455                                                    master_params, ems)
4456     result.Raise("Could not activate the master IP")
4457
4458
4459 class LUClusterDeactivateMasterIp(NoHooksLU):
4460   """Deactivate the master IP on the master node.
4461
4462   """
4463   def Exec(self, feedback_fn):
4464     """Deactivate the master IP.
4465
4466     """
4467     master_params = self.cfg.GetMasterNetworkParameters()
4468     ems = self.cfg.GetUseExternalMipScript()
4469     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
4470                                                      master_params, ems)
4471     result.Raise("Could not deactivate the master IP")
4472
4473
4474 def _WaitForSync(lu, instance, disks=None, oneshot=False):
4475   """Sleep and poll for an instance's disk to sync.
4476
4477   """
4478   if not instance.disks or disks is not None and not disks:
4479     return True
4480
4481   disks = _ExpandCheckDisks(instance, disks)
4482
4483   if not oneshot:
4484     lu.proc.LogInfo("Waiting for instance %s to sync disks." % instance.name)
4485
4486   node = instance.primary_node
4487
4488   for dev in disks:
4489     lu.cfg.SetDiskID(dev, node)
4490
4491   # TODO: Convert to utils.Retry
4492
4493   retries = 0
4494   degr_retries = 10 # in seconds, as we sleep 1 second each time
4495   while True:
4496     max_time = 0
4497     done = True
4498     cumul_degraded = False
4499     rstats = lu.rpc.call_blockdev_getmirrorstatus(node, (disks, instance))
4500     msg = rstats.fail_msg
4501     if msg:
4502       lu.LogWarning("Can't get any data from node %s: %s", node, msg)
4503       retries += 1
4504       if retries >= 10:
4505         raise errors.RemoteError("Can't contact node %s for mirror data,"
4506                                  " aborting." % node)
4507       time.sleep(6)
4508       continue
4509     rstats = rstats.payload
4510     retries = 0
4511     for i, mstat in enumerate(rstats):
4512       if mstat is None:
4513         lu.LogWarning("Can't compute data for node %s/%s",
4514                            node, disks[i].iv_name)
4515         continue
4516
4517       cumul_degraded = (cumul_degraded or
4518                         (mstat.is_degraded and mstat.sync_percent is None))
4519       if mstat.sync_percent is not None:
4520         done = False
4521         if mstat.estimated_time is not None:
4522           rem_time = ("%s remaining (estimated)" %
4523                       utils.FormatSeconds(mstat.estimated_time))
4524           max_time = mstat.estimated_time
4525         else:
4526           rem_time = "no time estimate"
4527         lu.proc.LogInfo("- device %s: %5.2f%% done, %s" %
4528                         (disks[i].iv_name, mstat.sync_percent, rem_time))
4529
4530     # if we're done but degraded, let's do a few small retries, to
4531     # make sure we see a stable and not transient situation; therefore
4532     # we force restart of the loop
4533     if (done or oneshot) and cumul_degraded and degr_retries > 0:
4534       logging.info("Degraded disks found, %d retries left", degr_retries)
4535       degr_retries -= 1
4536       time.sleep(1)
4537       continue
4538
4539     if done or oneshot:
4540       break
4541
4542     time.sleep(min(60, max_time))
4543
4544   if done:
4545     lu.proc.LogInfo("Instance %s's disks are in sync." % instance.name)
4546   return not cumul_degraded
4547
4548
4549 def _BlockdevFind(lu, node, dev, instance):
4550   """Wrapper around call_blockdev_find to annotate diskparams.
4551
4552   @param lu: A reference to the lu object
4553   @param node: The node to call out
4554   @param dev: The device to find
4555   @param instance: The instance object the device belongs to
4556   @returns The result of the rpc call
4557
4558   """
4559   (disk,) = _AnnotateDiskParams(instance, [dev], lu.cfg)
4560   return lu.rpc.call_blockdev_find(node, disk)
4561
4562
4563 def _CheckDiskConsistency(lu, instance, dev, node, on_primary, ldisk=False):
4564   """Wrapper around L{_CheckDiskConsistencyInner}.
4565
4566   """
4567   (disk,) = _AnnotateDiskParams(instance, [dev], lu.cfg)
4568   return _CheckDiskConsistencyInner(lu, instance, disk, node, on_primary,
4569                                     ldisk=ldisk)
4570
4571
4572 def _CheckDiskConsistencyInner(lu, instance, dev, node, on_primary,
4573                                ldisk=False):
4574   """Check that mirrors are not degraded.
4575
4576   @attention: The device has to be annotated already.
4577
4578   The ldisk parameter, if True, will change the test from the
4579   is_degraded attribute (which represents overall non-ok status for
4580   the device(s)) to the ldisk (representing the local storage status).
4581
4582   """
4583   lu.cfg.SetDiskID(dev, node)
4584
4585   result = True
4586
4587   if on_primary or dev.AssembleOnSecondary():
4588     rstats = lu.rpc.call_blockdev_find(node, dev)
4589     msg = rstats.fail_msg
4590     if msg:
4591       lu.LogWarning("Can't find disk on node %s: %s", node, msg)
4592       result = False
4593     elif not rstats.payload:
4594       lu.LogWarning("Can't find disk on node %s", node)
4595       result = False
4596     else:
4597       if ldisk:
4598         result = result and rstats.payload.ldisk_status == constants.LDS_OKAY
4599       else:
4600         result = result and not rstats.payload.is_degraded
4601
4602   if dev.children:
4603     for child in dev.children:
4604       result = result and _CheckDiskConsistencyInner(lu, instance, child, node,
4605                                                      on_primary)
4606
4607   return result
4608
4609
4610 class LUOobCommand(NoHooksLU):
4611   """Logical unit for OOB handling.
4612
4613   """
4614   REQ_BGL = False
4615   _SKIP_MASTER = (constants.OOB_POWER_OFF, constants.OOB_POWER_CYCLE)
4616
4617   def ExpandNames(self):
4618     """Gather locks we need.
4619
4620     """
4621     if self.op.node_names:
4622       self.op.node_names = _GetWantedNodes(self, self.op.node_names)
4623       lock_names = self.op.node_names
4624     else:
4625       lock_names = locking.ALL_SET
4626
4627     self.needed_locks = {
4628       locking.LEVEL_NODE: lock_names,
4629       }
4630
4631   def CheckPrereq(self):
4632     """Check prerequisites.
4633
4634     This checks:
4635      - the node exists in the configuration
4636      - OOB is supported
4637
4638     Any errors are signaled by raising errors.OpPrereqError.
4639
4640     """
4641     self.nodes = []
4642     self.master_node = self.cfg.GetMasterNode()
4643
4644     assert self.op.power_delay >= 0.0
4645
4646     if self.op.node_names:
4647       if (self.op.command in self._SKIP_MASTER and
4648           self.master_node in self.op.node_names):
4649         master_node_obj = self.cfg.GetNodeInfo(self.master_node)
4650         master_oob_handler = _SupportsOob(self.cfg, master_node_obj)
4651
4652         if master_oob_handler:
4653           additional_text = ("run '%s %s %s' if you want to operate on the"
4654                              " master regardless") % (master_oob_handler,
4655                                                       self.op.command,
4656                                                       self.master_node)
4657         else:
4658           additional_text = "it does not support out-of-band operations"
4659
4660         raise errors.OpPrereqError(("Operating on the master node %s is not"
4661                                     " allowed for %s; %s") %
4662                                    (self.master_node, self.op.command,
4663                                     additional_text), errors.ECODE_INVAL)
4664     else:
4665       self.op.node_names = self.cfg.GetNodeList()
4666       if self.op.command in self._SKIP_MASTER:
4667         self.op.node_names.remove(self.master_node)
4668
4669     if self.op.command in self._SKIP_MASTER:
4670       assert self.master_node not in self.op.node_names
4671
4672     for (node_name, node) in self.cfg.GetMultiNodeInfo(self.op.node_names):
4673       if node is None:
4674         raise errors.OpPrereqError("Node %s not found" % node_name,
4675                                    errors.ECODE_NOENT)
4676       else:
4677         self.nodes.append(node)
4678
4679       if (not self.op.ignore_status and
4680           (self.op.command == constants.OOB_POWER_OFF and not node.offline)):
4681         raise errors.OpPrereqError(("Cannot power off node %s because it is"
4682                                     " not marked offline") % node_name,
4683                                    errors.ECODE_STATE)
4684
4685   def Exec(self, feedback_fn):
4686     """Execute OOB and return result if we expect any.
4687
4688     """
4689     master_node = self.master_node
4690     ret = []
4691
4692     for idx, node in enumerate(utils.NiceSort(self.nodes,
4693                                               key=lambda node: node.name)):
4694       node_entry = [(constants.RS_NORMAL, node.name)]
4695       ret.append(node_entry)
4696
4697       oob_program = _SupportsOob(self.cfg, node)
4698
4699       if not oob_program:
4700         node_entry.append((constants.RS_UNAVAIL, None))
4701         continue
4702
4703       logging.info("Executing out-of-band command '%s' using '%s' on %s",
4704                    self.op.command, oob_program, node.name)
4705       result = self.rpc.call_run_oob(master_node, oob_program,
4706                                      self.op.command, node.name,
4707                                      self.op.timeout)
4708
4709       if result.fail_msg:
4710         self.LogWarning("Out-of-band RPC failed on node '%s': %s",
4711                         node.name, result.fail_msg)
4712         node_entry.append((constants.RS_NODATA, None))
4713       else:
4714         try:
4715           self._CheckPayload(result)
4716         except errors.OpExecError, err:
4717           self.LogWarning("Payload returned by node '%s' is not valid: %s",
4718                           node.name, err)
4719           node_entry.append((constants.RS_NODATA, None))
4720         else:
4721           if self.op.command == constants.OOB_HEALTH:
4722             # For health we should log important events
4723             for item, status in result.payload:
4724               if status in [constants.OOB_STATUS_WARNING,
4725                             constants.OOB_STATUS_CRITICAL]:
4726                 self.LogWarning("Item '%s' on node '%s' has status '%s'",
4727                                 item, node.name, status)
4728
4729           if self.op.command == constants.OOB_POWER_ON:
4730             node.powered = True
4731           elif self.op.command == constants.OOB_POWER_OFF:
4732             node.powered = False
4733           elif self.op.command == constants.OOB_POWER_STATUS:
4734             powered = result.payload[constants.OOB_POWER_STATUS_POWERED]
4735             if powered != node.powered:
4736               logging.warning(("Recorded power state (%s) of node '%s' does not"
4737                                " match actual power state (%s)"), node.powered,
4738                               node.name, powered)
4739
4740           # For configuration changing commands we should update the node
4741           if self.op.command in (constants.OOB_POWER_ON,
4742                                  constants.OOB_POWER_OFF):
4743             self.cfg.Update(node, feedback_fn)
4744
4745           node_entry.append((constants.RS_NORMAL, result.payload))
4746
4747           if (self.op.command == constants.OOB_POWER_ON and
4748               idx < len(self.nodes) - 1):
4749             time.sleep(self.op.power_delay)
4750
4751     return ret
4752
4753   def _CheckPayload(self, result):
4754     """Checks if the payload is valid.
4755
4756     @param result: RPC result
4757     @raises errors.OpExecError: If payload is not valid
4758
4759     """
4760     errs = []
4761     if self.op.command == constants.OOB_HEALTH:
4762       if not isinstance(result.payload, list):
4763         errs.append("command 'health' is expected to return a list but got %s" %
4764                     type(result.payload))
4765       else:
4766         for item, status in result.payload:
4767           if status not in constants.OOB_STATUSES:
4768             errs.append("health item '%s' has invalid status '%s'" %
4769                         (item, status))
4770
4771     if self.op.command == constants.OOB_POWER_STATUS:
4772       if not isinstance(result.payload, dict):
4773         errs.append("power-status is expected to return a dict but got %s" %
4774                     type(result.payload))
4775
4776     if self.op.command in [
4777       constants.OOB_POWER_ON,
4778       constants.OOB_POWER_OFF,
4779       constants.OOB_POWER_CYCLE,
4780       ]:
4781       if result.payload is not None:
4782         errs.append("%s is expected to not return payload but got '%s'" %
4783                     (self.op.command, result.payload))
4784
4785     if errs:
4786       raise errors.OpExecError("Check of out-of-band payload failed due to %s" %
4787                                utils.CommaJoin(errs))
4788
4789
4790 class _OsQuery(_QueryBase):
4791   FIELDS = query.OS_FIELDS
4792
4793   def ExpandNames(self, lu):
4794     # Lock all nodes in shared mode
4795     # Temporary removal of locks, should be reverted later
4796     # TODO: reintroduce locks when they are lighter-weight
4797     lu.needed_locks = {}
4798     #self.share_locks[locking.LEVEL_NODE] = 1
4799     #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
4800
4801     # The following variables interact with _QueryBase._GetNames
4802     if self.names:
4803       self.wanted = self.names
4804     else:
4805       self.wanted = locking.ALL_SET
4806
4807     self.do_locking = self.use_locking
4808
4809   def DeclareLocks(self, lu, level):
4810     pass
4811
4812   @staticmethod
4813   def _DiagnoseByOS(rlist):
4814     """Remaps a per-node return list into an a per-os per-node dictionary
4815
4816     @param rlist: a map with node names as keys and OS objects as values
4817
4818     @rtype: dict
4819     @return: a dictionary with osnames as keys and as value another
4820         map, with nodes as keys and tuples of (path, status, diagnose,
4821         variants, parameters, api_versions) as values, eg::
4822
4823           {"debian-etch": {"node1": [(/usr/lib/..., True, "", [], []),
4824                                      (/srv/..., False, "invalid api")],
4825                            "node2": [(/srv/..., True, "", [], [])]}
4826           }
4827
4828     """
4829     all_os = {}
4830     # we build here the list of nodes that didn't fail the RPC (at RPC
4831     # level), so that nodes with a non-responding node daemon don't
4832     # make all OSes invalid
4833     good_nodes = [node_name for node_name in rlist
4834                   if not rlist[node_name].fail_msg]
4835     for node_name, nr in rlist.items():
4836       if nr.fail_msg or not nr.payload:
4837         continue
4838       for (name, path, status, diagnose, variants,
4839            params, api_versions) in nr.payload:
4840         if name not in all_os:
4841           # build a list of nodes for this os containing empty lists
4842           # for each node in node_list
4843           all_os[name] = {}
4844           for nname in good_nodes:
4845             all_os[name][nname] = []
4846         # convert params from [name, help] to (name, help)
4847         params = [tuple(v) for v in params]
4848         all_os[name][node_name].append((path, status, diagnose,
4849                                         variants, params, api_versions))
4850     return all_os
4851
4852   def _GetQueryData(self, lu):
4853     """Computes the list of nodes and their attributes.
4854
4855     """
4856     # Locking is not used
4857     assert not (compat.any(lu.glm.is_owned(level)
4858                            for level in locking.LEVELS
4859                            if level != locking.LEVEL_CLUSTER) or
4860                 self.do_locking or self.use_locking)
4861
4862     valid_nodes = [node.name
4863                    for node in lu.cfg.GetAllNodesInfo().values()
4864                    if not node.offline and node.vm_capable]
4865     pol = self._DiagnoseByOS(lu.rpc.call_os_diagnose(valid_nodes))
4866     cluster = lu.cfg.GetClusterInfo()
4867
4868     data = {}
4869
4870     for (os_name, os_data) in pol.items():
4871       info = query.OsInfo(name=os_name, valid=True, node_status=os_data,
4872                           hidden=(os_name in cluster.hidden_os),
4873                           blacklisted=(os_name in cluster.blacklisted_os))
4874
4875       variants = set()
4876       parameters = set()
4877       api_versions = set()
4878
4879       for idx, osl in enumerate(os_data.values()):
4880         info.valid = bool(info.valid and osl and osl[0][1])
4881         if not info.valid:
4882           break
4883
4884         (node_variants, node_params, node_api) = osl[0][3:6]
4885         if idx == 0:
4886           # First entry
4887           variants.update(node_variants)
4888           parameters.update(node_params)
4889           api_versions.update(node_api)
4890         else:
4891           # Filter out inconsistent values
4892           variants.intersection_update(node_variants)
4893           parameters.intersection_update(node_params)
4894           api_versions.intersection_update(node_api)
4895
4896       info.variants = list(variants)
4897       info.parameters = list(parameters)
4898       info.api_versions = list(api_versions)
4899
4900       data[os_name] = info
4901
4902     # Prepare data in requested order
4903     return [data[name] for name in self._GetNames(lu, pol.keys(), None)
4904             if name in data]
4905
4906
4907 class LUOsDiagnose(NoHooksLU):
4908   """Logical unit for OS diagnose/query.
4909
4910   """
4911   REQ_BGL = False
4912
4913   @staticmethod
4914   def _BuildFilter(fields, names):
4915     """Builds a filter for querying OSes.
4916
4917     """
4918     name_filter = qlang.MakeSimpleFilter("name", names)
4919
4920     # Legacy behaviour: Hide hidden, blacklisted or invalid OSes if the
4921     # respective field is not requested
4922     status_filter = [[qlang.OP_NOT, [qlang.OP_TRUE, fname]]
4923                      for fname in ["hidden", "blacklisted"]
4924                      if fname not in fields]
4925     if "valid" not in fields:
4926       status_filter.append([qlang.OP_TRUE, "valid"])
4927
4928     if status_filter:
4929       status_filter.insert(0, qlang.OP_AND)
4930     else:
4931       status_filter = None
4932
4933     if name_filter and status_filter:
4934       return [qlang.OP_AND, name_filter, status_filter]
4935     elif name_filter:
4936       return name_filter
4937     else:
4938       return status_filter
4939
4940   def CheckArguments(self):
4941     self.oq = _OsQuery(self._BuildFilter(self.op.output_fields, self.op.names),
4942                        self.op.output_fields, False)
4943
4944   def ExpandNames(self):
4945     self.oq.ExpandNames(self)
4946
4947   def Exec(self, feedback_fn):
4948     return self.oq.OldStyleQuery(self)
4949
4950
4951 class LUNodeRemove(LogicalUnit):
4952   """Logical unit for removing a node.
4953
4954   """
4955   HPATH = "node-remove"
4956   HTYPE = constants.HTYPE_NODE
4957
4958   def BuildHooksEnv(self):
4959     """Build hooks env.
4960
4961     """
4962     return {
4963       "OP_TARGET": self.op.node_name,
4964       "NODE_NAME": self.op.node_name,
4965       }
4966
4967   def BuildHooksNodes(self):
4968     """Build hooks nodes.
4969
4970     This doesn't run on the target node in the pre phase as a failed
4971     node would then be impossible to remove.
4972
4973     """
4974     all_nodes = self.cfg.GetNodeList()
4975     try:
4976       all_nodes.remove(self.op.node_name)
4977     except ValueError:
4978       pass
4979     return (all_nodes, all_nodes)
4980
4981   def CheckPrereq(self):
4982     """Check prerequisites.
4983
4984     This checks:
4985      - the node exists in the configuration
4986      - it does not have primary or secondary instances
4987      - it's not the master
4988
4989     Any errors are signaled by raising errors.OpPrereqError.
4990
4991     """
4992     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
4993     node = self.cfg.GetNodeInfo(self.op.node_name)
4994     assert node is not None
4995
4996     masternode = self.cfg.GetMasterNode()
4997     if node.name == masternode:
4998       raise errors.OpPrereqError("Node is the master node, failover to another"
4999                                  " node is required", errors.ECODE_INVAL)
5000
5001     for instance_name, instance in self.cfg.GetAllInstancesInfo().items():
5002       if node.name in instance.all_nodes:
5003         raise errors.OpPrereqError("Instance %s is still running on the node,"
5004                                    " please remove first" % instance_name,
5005                                    errors.ECODE_INVAL)
5006     self.op.node_name = node.name
5007     self.node = node
5008
5009   def Exec(self, feedback_fn):
5010     """Removes the node from the cluster.
5011
5012     """
5013     node = self.node
5014     logging.info("Stopping the node daemon and removing configs from node %s",
5015                  node.name)
5016
5017     modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
5018
5019     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER), \
5020       "Not owning BGL"
5021
5022     # Promote nodes to master candidate as needed
5023     _AdjustCandidatePool(self, exceptions=[node.name])
5024     self.context.RemoveNode(node.name)
5025
5026     # Run post hooks on the node before it's removed
5027     _RunPostHook(self, node.name)
5028
5029     result = self.rpc.call_node_leave_cluster(node.name, modify_ssh_setup)
5030     msg = result.fail_msg
5031     if msg:
5032       self.LogWarning("Errors encountered on the remote node while leaving"
5033                       " the cluster: %s", msg)
5034
5035     # Remove node from our /etc/hosts
5036     if self.cfg.GetClusterInfo().modify_etc_hosts:
5037       master_node = self.cfg.GetMasterNode()
5038       result = self.rpc.call_etc_hosts_modify(master_node,
5039                                               constants.ETC_HOSTS_REMOVE,
5040                                               node.name, None)
5041       result.Raise("Can't update hosts file with new host data")
5042       _RedistributeAncillaryFiles(self)
5043
5044
5045 class _NodeQuery(_QueryBase):
5046   FIELDS = query.NODE_FIELDS
5047
5048   def ExpandNames(self, lu):
5049     lu.needed_locks = {}
5050     lu.share_locks = _ShareAll()
5051
5052     if self.names:
5053       self.wanted = _GetWantedNodes(lu, self.names)
5054     else:
5055       self.wanted = locking.ALL_SET
5056
5057     self.do_locking = (self.use_locking and
5058                        query.NQ_LIVE in self.requested_data)
5059
5060     if self.do_locking:
5061       # If any non-static field is requested we need to lock the nodes
5062       lu.needed_locks[locking.LEVEL_NODE] = self.wanted
5063
5064   def DeclareLocks(self, lu, level):
5065     pass
5066
5067   def _GetQueryData(self, lu):
5068     """Computes the list of nodes and their attributes.
5069
5070     """
5071     all_info = lu.cfg.GetAllNodesInfo()
5072
5073     nodenames = self._GetNames(lu, all_info.keys(), locking.LEVEL_NODE)
5074
5075     # Gather data as requested
5076     if query.NQ_LIVE in self.requested_data:
5077       # filter out non-vm_capable nodes
5078       toquery_nodes = [name for name in nodenames if all_info[name].vm_capable]
5079
5080       node_data = lu.rpc.call_node_info(toquery_nodes, [lu.cfg.GetVGName()],
5081                                         [lu.cfg.GetHypervisorType()])
5082       live_data = dict((name, rpc.MakeLegacyNodeInfo(nresult.payload))
5083                        for (name, nresult) in node_data.items()
5084                        if not nresult.fail_msg and nresult.payload)
5085     else:
5086       live_data = None
5087
5088     if query.NQ_INST in self.requested_data:
5089       node_to_primary = dict([(name, set()) for name in nodenames])
5090       node_to_secondary = dict([(name, set()) for name in nodenames])
5091
5092       inst_data = lu.cfg.GetAllInstancesInfo()
5093
5094       for inst in inst_data.values():
5095         if inst.primary_node in node_to_primary:
5096           node_to_primary[inst.primary_node].add(inst.name)
5097         for secnode in inst.secondary_nodes:
5098           if secnode in node_to_secondary:
5099             node_to_secondary[secnode].add(inst.name)
5100     else:
5101       node_to_primary = None
5102       node_to_secondary = None
5103
5104     if query.NQ_OOB in self.requested_data:
5105       oob_support = dict((name, bool(_SupportsOob(lu.cfg, node)))
5106                          for name, node in all_info.iteritems())
5107     else:
5108       oob_support = None
5109
5110     if query.NQ_GROUP in self.requested_data:
5111       groups = lu.cfg.GetAllNodeGroupsInfo()
5112     else:
5113       groups = {}
5114
5115     return query.NodeQueryData([all_info[name] for name in nodenames],
5116                                live_data, lu.cfg.GetMasterNode(),
5117                                node_to_primary, node_to_secondary, groups,
5118                                oob_support, lu.cfg.GetClusterInfo())
5119
5120
5121 class LUNodeQuery(NoHooksLU):
5122   """Logical unit for querying nodes.
5123
5124   """
5125   # pylint: disable=W0142
5126   REQ_BGL = False
5127
5128   def CheckArguments(self):
5129     self.nq = _NodeQuery(qlang.MakeSimpleFilter("name", self.op.names),
5130                          self.op.output_fields, self.op.use_locking)
5131
5132   def ExpandNames(self):
5133     self.nq.ExpandNames(self)
5134
5135   def DeclareLocks(self, level):
5136     self.nq.DeclareLocks(self, level)
5137
5138   def Exec(self, feedback_fn):
5139     return self.nq.OldStyleQuery(self)
5140
5141
5142 class LUNodeQueryvols(NoHooksLU):
5143   """Logical unit for getting volumes on node(s).
5144
5145   """
5146   REQ_BGL = False
5147   _FIELDS_DYNAMIC = utils.FieldSet("phys", "vg", "name", "size", "instance")
5148   _FIELDS_STATIC = utils.FieldSet("node")
5149
5150   def CheckArguments(self):
5151     _CheckOutputFields(static=self._FIELDS_STATIC,
5152                        dynamic=self._FIELDS_DYNAMIC,
5153                        selected=self.op.output_fields)
5154
5155   def ExpandNames(self):
5156     self.share_locks = _ShareAll()
5157     self.needed_locks = {}
5158
5159     if not self.op.nodes:
5160       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5161     else:
5162       self.needed_locks[locking.LEVEL_NODE] = \
5163         _GetWantedNodes(self, self.op.nodes)
5164
5165   def Exec(self, feedback_fn):
5166     """Computes the list of nodes and their attributes.
5167
5168     """
5169     nodenames = self.owned_locks(locking.LEVEL_NODE)
5170     volumes = self.rpc.call_node_volumes(nodenames)
5171
5172     ilist = self.cfg.GetAllInstancesInfo()
5173     vol2inst = _MapInstanceDisksToNodes(ilist.values())
5174
5175     output = []
5176     for node in nodenames:
5177       nresult = volumes[node]
5178       if nresult.offline:
5179         continue
5180       msg = nresult.fail_msg
5181       if msg:
5182         self.LogWarning("Can't compute volume data on node %s: %s", node, msg)
5183         continue
5184
5185       node_vols = sorted(nresult.payload,
5186                          key=operator.itemgetter("dev"))
5187
5188       for vol in node_vols:
5189         node_output = []
5190         for field in self.op.output_fields:
5191           if field == "node":
5192             val = node
5193           elif field == "phys":
5194             val = vol["dev"]
5195           elif field == "vg":
5196             val = vol["vg"]
5197           elif field == "name":
5198             val = vol["name"]
5199           elif field == "size":
5200             val = int(float(vol["size"]))
5201           elif field == "instance":
5202             val = vol2inst.get((node, vol["vg"] + "/" + vol["name"]), "-")
5203           else:
5204             raise errors.ParameterError(field)
5205           node_output.append(str(val))
5206
5207         output.append(node_output)
5208
5209     return output
5210
5211
5212 class LUNodeQueryStorage(NoHooksLU):
5213   """Logical unit for getting information on storage units on node(s).
5214
5215   """
5216   _FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
5217   REQ_BGL = False
5218
5219   def CheckArguments(self):
5220     _CheckOutputFields(static=self._FIELDS_STATIC,
5221                        dynamic=utils.FieldSet(*constants.VALID_STORAGE_FIELDS),
5222                        selected=self.op.output_fields)
5223
5224   def ExpandNames(self):
5225     self.share_locks = _ShareAll()
5226     self.needed_locks = {}
5227
5228     if self.op.nodes:
5229       self.needed_locks[locking.LEVEL_NODE] = \
5230         _GetWantedNodes(self, self.op.nodes)
5231     else:
5232       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5233
5234   def Exec(self, feedback_fn):
5235     """Computes the list of nodes and their attributes.
5236
5237     """
5238     self.nodes = self.owned_locks(locking.LEVEL_NODE)
5239
5240     # Always get name to sort by
5241     if constants.SF_NAME in self.op.output_fields:
5242       fields = self.op.output_fields[:]
5243     else:
5244       fields = [constants.SF_NAME] + self.op.output_fields
5245
5246     # Never ask for node or type as it's only known to the LU
5247     for extra in [constants.SF_NODE, constants.SF_TYPE]:
5248       while extra in fields:
5249         fields.remove(extra)
5250
5251     field_idx = dict([(name, idx) for (idx, name) in enumerate(fields)])
5252     name_idx = field_idx[constants.SF_NAME]
5253
5254     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
5255     data = self.rpc.call_storage_list(self.nodes,
5256                                       self.op.storage_type, st_args,
5257                                       self.op.name, fields)
5258
5259     result = []
5260
5261     for node in utils.NiceSort(self.nodes):
5262       nresult = data[node]
5263       if nresult.offline:
5264         continue
5265
5266       msg = nresult.fail_msg
5267       if msg:
5268         self.LogWarning("Can't get storage data from node %s: %s", node, msg)
5269         continue
5270
5271       rows = dict([(row[name_idx], row) for row in nresult.payload])
5272
5273       for name in utils.NiceSort(rows.keys()):
5274         row = rows[name]
5275
5276         out = []
5277
5278         for field in self.op.output_fields:
5279           if field == constants.SF_NODE:
5280             val = node
5281           elif field == constants.SF_TYPE:
5282             val = self.op.storage_type
5283           elif field in field_idx:
5284             val = row[field_idx[field]]
5285           else:
5286             raise errors.ParameterError(field)
5287
5288           out.append(val)
5289
5290         result.append(out)
5291
5292     return result
5293
5294
5295 class _InstanceQuery(_QueryBase):
5296   FIELDS = query.INSTANCE_FIELDS
5297
5298   def ExpandNames(self, lu):
5299     lu.needed_locks = {}
5300     lu.share_locks = _ShareAll()
5301
5302     if self.names:
5303       self.wanted = _GetWantedInstances(lu, self.names)
5304     else:
5305       self.wanted = locking.ALL_SET
5306
5307     self.do_locking = (self.use_locking and
5308                        query.IQ_LIVE in self.requested_data)
5309     if self.do_locking:
5310       lu.needed_locks[locking.LEVEL_INSTANCE] = self.wanted
5311       lu.needed_locks[locking.LEVEL_NODEGROUP] = []
5312       lu.needed_locks[locking.LEVEL_NODE] = []
5313       lu.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
5314
5315     self.do_grouplocks = (self.do_locking and
5316                           query.IQ_NODES in self.requested_data)
5317
5318   def DeclareLocks(self, lu, level):
5319     if self.do_locking:
5320       if level == locking.LEVEL_NODEGROUP and self.do_grouplocks:
5321         assert not lu.needed_locks[locking.LEVEL_NODEGROUP]
5322
5323         # Lock all groups used by instances optimistically; this requires going
5324         # via the node before it's locked, requiring verification later on
5325         lu.needed_locks[locking.LEVEL_NODEGROUP] = \
5326           set(group_uuid
5327               for instance_name in lu.owned_locks(locking.LEVEL_INSTANCE)
5328               for group_uuid in lu.cfg.GetInstanceNodeGroups(instance_name))
5329       elif level == locking.LEVEL_NODE:
5330         lu._LockInstancesNodes() # pylint: disable=W0212
5331
5332   @staticmethod
5333   def _CheckGroupLocks(lu):
5334     owned_instances = frozenset(lu.owned_locks(locking.LEVEL_INSTANCE))
5335     owned_groups = frozenset(lu.owned_locks(locking.LEVEL_NODEGROUP))
5336
5337     # Check if node groups for locked instances are still correct
5338     for instance_name in owned_instances:
5339       _CheckInstanceNodeGroups(lu.cfg, instance_name, owned_groups)
5340
5341   def _GetQueryData(self, lu):
5342     """Computes the list of instances and their attributes.
5343
5344     """
5345     if self.do_grouplocks:
5346       self._CheckGroupLocks(lu)
5347
5348     cluster = lu.cfg.GetClusterInfo()
5349     all_info = lu.cfg.GetAllInstancesInfo()
5350
5351     instance_names = self._GetNames(lu, all_info.keys(), locking.LEVEL_INSTANCE)
5352
5353     instance_list = [all_info[name] for name in instance_names]
5354     nodes = frozenset(itertools.chain(*(inst.all_nodes
5355                                         for inst in instance_list)))
5356     hv_list = list(set([inst.hypervisor for inst in instance_list]))
5357     bad_nodes = []
5358     offline_nodes = []
5359     wrongnode_inst = set()
5360
5361     # Gather data as requested
5362     if self.requested_data & set([query.IQ_LIVE, query.IQ_CONSOLE]):
5363       live_data = {}
5364       node_data = lu.rpc.call_all_instances_info(nodes, hv_list)
5365       for name in nodes:
5366         result = node_data[name]
5367         if result.offline:
5368           # offline nodes will be in both lists
5369           assert result.fail_msg
5370           offline_nodes.append(name)
5371         if result.fail_msg:
5372           bad_nodes.append(name)
5373         elif result.payload:
5374           for inst in result.payload:
5375             if inst in all_info:
5376               if all_info[inst].primary_node == name:
5377                 live_data.update(result.payload)
5378               else:
5379                 wrongnode_inst.add(inst)
5380             else:
5381               # orphan instance; we don't list it here as we don't
5382               # handle this case yet in the output of instance listing
5383               logging.warning("Orphan instance '%s' found on node %s",
5384                               inst, name)
5385         # else no instance is alive
5386     else:
5387       live_data = {}
5388
5389     if query.IQ_DISKUSAGE in self.requested_data:
5390       gmi = ganeti.masterd.instance
5391       disk_usage = dict((inst.name,
5392                          gmi.ComputeDiskSize(inst.disk_template,
5393                                              [{constants.IDISK_SIZE: disk.size}
5394                                               for disk in inst.disks]))
5395                         for inst in instance_list)
5396     else:
5397       disk_usage = None
5398
5399     if query.IQ_CONSOLE in self.requested_data:
5400       consinfo = {}
5401       for inst in instance_list:
5402         if inst.name in live_data:
5403           # Instance is running
5404           consinfo[inst.name] = _GetInstanceConsole(cluster, inst)
5405         else:
5406           consinfo[inst.name] = None
5407       assert set(consinfo.keys()) == set(instance_names)
5408     else:
5409       consinfo = None
5410
5411     if query.IQ_NODES in self.requested_data:
5412       node_names = set(itertools.chain(*map(operator.attrgetter("all_nodes"),
5413                                             instance_list)))
5414       nodes = dict(lu.cfg.GetMultiNodeInfo(node_names))
5415       groups = dict((uuid, lu.cfg.GetNodeGroup(uuid))
5416                     for uuid in set(map(operator.attrgetter("group"),
5417                                         nodes.values())))
5418     else:
5419       nodes = None
5420       groups = None
5421
5422     return query.InstanceQueryData(instance_list, lu.cfg.GetClusterInfo(),
5423                                    disk_usage, offline_nodes, bad_nodes,
5424                                    live_data, wrongnode_inst, consinfo,
5425                                    nodes, groups)
5426
5427
5428 class LUQuery(NoHooksLU):
5429   """Query for resources/items of a certain kind.
5430
5431   """
5432   # pylint: disable=W0142
5433   REQ_BGL = False
5434
5435   def CheckArguments(self):
5436     qcls = _GetQueryImplementation(self.op.what)
5437
5438     self.impl = qcls(self.op.qfilter, self.op.fields, self.op.use_locking)
5439
5440   def ExpandNames(self):
5441     self.impl.ExpandNames(self)
5442
5443   def DeclareLocks(self, level):
5444     self.impl.DeclareLocks(self, level)
5445
5446   def Exec(self, feedback_fn):
5447     return self.impl.NewStyleQuery(self)
5448
5449
5450 class LUQueryFields(NoHooksLU):
5451   """Query for resources/items of a certain kind.
5452
5453   """
5454   # pylint: disable=W0142
5455   REQ_BGL = False
5456
5457   def CheckArguments(self):
5458     self.qcls = _GetQueryImplementation(self.op.what)
5459
5460   def ExpandNames(self):
5461     self.needed_locks = {}
5462
5463   def Exec(self, feedback_fn):
5464     return query.QueryFields(self.qcls.FIELDS, self.op.fields)
5465
5466
5467 class LUNodeModifyStorage(NoHooksLU):
5468   """Logical unit for modifying a storage volume on a node.
5469
5470   """
5471   REQ_BGL = False
5472
5473   def CheckArguments(self):
5474     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5475
5476     storage_type = self.op.storage_type
5477
5478     try:
5479       modifiable = constants.MODIFIABLE_STORAGE_FIELDS[storage_type]
5480     except KeyError:
5481       raise errors.OpPrereqError("Storage units of type '%s' can not be"
5482                                  " modified" % storage_type,
5483                                  errors.ECODE_INVAL)
5484
5485     diff = set(self.op.changes.keys()) - modifiable
5486     if diff:
5487       raise errors.OpPrereqError("The following fields can not be modified for"
5488                                  " storage units of type '%s': %r" %
5489                                  (storage_type, list(diff)),
5490                                  errors.ECODE_INVAL)
5491
5492   def ExpandNames(self):
5493     self.needed_locks = {
5494       locking.LEVEL_NODE: self.op.node_name,
5495       }
5496
5497   def Exec(self, feedback_fn):
5498     """Computes the list of nodes and their attributes.
5499
5500     """
5501     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
5502     result = self.rpc.call_storage_modify(self.op.node_name,
5503                                           self.op.storage_type, st_args,
5504                                           self.op.name, self.op.changes)
5505     result.Raise("Failed to modify storage unit '%s' on %s" %
5506                  (self.op.name, self.op.node_name))
5507
5508
5509 class LUNodeAdd(LogicalUnit):
5510   """Logical unit for adding node to the cluster.
5511
5512   """
5513   HPATH = "node-add"
5514   HTYPE = constants.HTYPE_NODE
5515   _NFLAGS = ["master_capable", "vm_capable"]
5516
5517   def CheckArguments(self):
5518     self.primary_ip_family = self.cfg.GetPrimaryIPFamily()
5519     # validate/normalize the node name
5520     self.hostname = netutils.GetHostname(name=self.op.node_name,
5521                                          family=self.primary_ip_family)
5522     self.op.node_name = self.hostname.name
5523
5524     if self.op.readd and self.op.node_name == self.cfg.GetMasterNode():
5525       raise errors.OpPrereqError("Cannot readd the master node",
5526                                  errors.ECODE_STATE)
5527
5528     if self.op.readd and self.op.group:
5529       raise errors.OpPrereqError("Cannot pass a node group when a node is"
5530                                  " being readded", errors.ECODE_INVAL)
5531
5532   def BuildHooksEnv(self):
5533     """Build hooks env.
5534
5535     This will run on all nodes before, and on all nodes + the new node after.
5536
5537     """
5538     return {
5539       "OP_TARGET": self.op.node_name,
5540       "NODE_NAME": self.op.node_name,
5541       "NODE_PIP": self.op.primary_ip,
5542       "NODE_SIP": self.op.secondary_ip,
5543       "MASTER_CAPABLE": str(self.op.master_capable),
5544       "VM_CAPABLE": str(self.op.vm_capable),
5545       }
5546
5547   def BuildHooksNodes(self):
5548     """Build hooks nodes.
5549
5550     """
5551     # Exclude added node
5552     pre_nodes = list(set(self.cfg.GetNodeList()) - set([self.op.node_name]))
5553     post_nodes = pre_nodes + [self.op.node_name, ]
5554
5555     return (pre_nodes, post_nodes)
5556
5557   def CheckPrereq(self):
5558     """Check prerequisites.
5559
5560     This checks:
5561      - the new node is not already in the config
5562      - it is resolvable
5563      - its parameters (single/dual homed) matches the cluster
5564
5565     Any errors are signaled by raising errors.OpPrereqError.
5566
5567     """
5568     cfg = self.cfg
5569     hostname = self.hostname
5570     node = hostname.name
5571     primary_ip = self.op.primary_ip = hostname.ip
5572     if self.op.secondary_ip is None:
5573       if self.primary_ip_family == netutils.IP6Address.family:
5574         raise errors.OpPrereqError("When using a IPv6 primary address, a valid"
5575                                    " IPv4 address must be given as secondary",
5576                                    errors.ECODE_INVAL)
5577       self.op.secondary_ip = primary_ip
5578
5579     secondary_ip = self.op.secondary_ip
5580     if not netutils.IP4Address.IsValid(secondary_ip):
5581       raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
5582                                  " address" % secondary_ip, errors.ECODE_INVAL)
5583
5584     node_list = cfg.GetNodeList()
5585     if not self.op.readd and node in node_list:
5586       raise errors.OpPrereqError("Node %s is already in the configuration" %
5587                                  node, errors.ECODE_EXISTS)
5588     elif self.op.readd and node not in node_list:
5589       raise errors.OpPrereqError("Node %s is not in the configuration" % node,
5590                                  errors.ECODE_NOENT)
5591
5592     self.changed_primary_ip = False
5593
5594     for existing_node_name, existing_node in cfg.GetMultiNodeInfo(node_list):
5595       if self.op.readd and node == existing_node_name:
5596         if existing_node.secondary_ip != secondary_ip:
5597           raise errors.OpPrereqError("Readded node doesn't have the same IP"
5598                                      " address configuration as before",
5599                                      errors.ECODE_INVAL)
5600         if existing_node.primary_ip != primary_ip:
5601           self.changed_primary_ip = True
5602
5603         continue
5604
5605       if (existing_node.primary_ip == primary_ip or
5606           existing_node.secondary_ip == primary_ip or
5607           existing_node.primary_ip == secondary_ip or
5608           existing_node.secondary_ip == secondary_ip):
5609         raise errors.OpPrereqError("New node ip address(es) conflict with"
5610                                    " existing node %s" % existing_node.name,
5611                                    errors.ECODE_NOTUNIQUE)
5612
5613     # After this 'if' block, None is no longer a valid value for the
5614     # _capable op attributes
5615     if self.op.readd:
5616       old_node = self.cfg.GetNodeInfo(node)
5617       assert old_node is not None, "Can't retrieve locked node %s" % node
5618       for attr in self._NFLAGS:
5619         if getattr(self.op, attr) is None:
5620           setattr(self.op, attr, getattr(old_node, attr))
5621     else:
5622       for attr in self._NFLAGS:
5623         if getattr(self.op, attr) is None:
5624           setattr(self.op, attr, True)
5625
5626     if self.op.readd and not self.op.vm_capable:
5627       pri, sec = cfg.GetNodeInstances(node)
5628       if pri or sec:
5629         raise errors.OpPrereqError("Node %s being re-added with vm_capable"
5630                                    " flag set to false, but it already holds"
5631                                    " instances" % node,
5632                                    errors.ECODE_STATE)
5633
5634     # check that the type of the node (single versus dual homed) is the
5635     # same as for the master
5636     myself = cfg.GetNodeInfo(self.cfg.GetMasterNode())
5637     master_singlehomed = myself.secondary_ip == myself.primary_ip
5638     newbie_singlehomed = secondary_ip == primary_ip
5639     if master_singlehomed != newbie_singlehomed:
5640       if master_singlehomed:
5641         raise errors.OpPrereqError("The master has no secondary ip but the"
5642                                    " new node has one",
5643                                    errors.ECODE_INVAL)
5644       else:
5645         raise errors.OpPrereqError("The master has a secondary ip but the"
5646                                    " new node doesn't have one",
5647                                    errors.ECODE_INVAL)
5648
5649     # checks reachability
5650     if not netutils.TcpPing(primary_ip, constants.DEFAULT_NODED_PORT):
5651       raise errors.OpPrereqError("Node not reachable by ping",
5652                                  errors.ECODE_ENVIRON)
5653
5654     if not newbie_singlehomed:
5655       # check reachability from my secondary ip to newbie's secondary ip
5656       if not netutils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
5657                               source=myself.secondary_ip):
5658         raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
5659                                    " based ping to node daemon port",
5660                                    errors.ECODE_ENVIRON)
5661
5662     if self.op.readd:
5663       exceptions = [node]
5664     else:
5665       exceptions = []
5666
5667     if self.op.master_capable:
5668       self.master_candidate = _DecideSelfPromotion(self, exceptions=exceptions)
5669     else:
5670       self.master_candidate = False
5671
5672     if self.op.readd:
5673       self.new_node = old_node
5674     else:
5675       node_group = cfg.LookupNodeGroup(self.op.group)
5676       self.new_node = objects.Node(name=node,
5677                                    primary_ip=primary_ip,
5678                                    secondary_ip=secondary_ip,
5679                                    master_candidate=self.master_candidate,
5680                                    offline=False, drained=False,
5681                                    group=node_group)
5682
5683     if self.op.ndparams:
5684       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
5685
5686     if self.op.hv_state:
5687       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None)
5688
5689     if self.op.disk_state:
5690       self.new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state, None)
5691
5692     # TODO: If we need to have multiple DnsOnlyRunner we probably should make
5693     #       it a property on the base class.
5694     result = rpc.DnsOnlyRunner().call_version([node])[node]
5695     result.Raise("Can't get version information from node %s" % node)
5696     if constants.PROTOCOL_VERSION == result.payload:
5697       logging.info("Communication to node %s fine, sw version %s match",
5698                    node, result.payload)
5699     else:
5700       raise errors.OpPrereqError("Version mismatch master version %s,"
5701                                  " node version %s" %
5702                                  (constants.PROTOCOL_VERSION, result.payload),
5703                                  errors.ECODE_ENVIRON)
5704
5705   def Exec(self, feedback_fn):
5706     """Adds the new node to the cluster.
5707
5708     """
5709     new_node = self.new_node
5710     node = new_node.name
5711
5712     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER), \
5713       "Not owning BGL"
5714
5715     # We adding a new node so we assume it's powered
5716     new_node.powered = True
5717
5718     # for re-adds, reset the offline/drained/master-candidate flags;
5719     # we need to reset here, otherwise offline would prevent RPC calls
5720     # later in the procedure; this also means that if the re-add
5721     # fails, we are left with a non-offlined, broken node
5722     if self.op.readd:
5723       new_node.drained = new_node.offline = False # pylint: disable=W0201
5724       self.LogInfo("Readding a node, the offline/drained flags were reset")
5725       # if we demote the node, we do cleanup later in the procedure
5726       new_node.master_candidate = self.master_candidate
5727       if self.changed_primary_ip:
5728         new_node.primary_ip = self.op.primary_ip
5729
5730     # copy the master/vm_capable flags
5731     for attr in self._NFLAGS:
5732       setattr(new_node, attr, getattr(self.op, attr))
5733
5734     # notify the user about any possible mc promotion
5735     if new_node.master_candidate:
5736       self.LogInfo("Node will be a master candidate")
5737
5738     if self.op.ndparams:
5739       new_node.ndparams = self.op.ndparams
5740     else:
5741       new_node.ndparams = {}
5742
5743     if self.op.hv_state:
5744       new_node.hv_state_static = self.new_hv_state
5745
5746     if self.op.disk_state:
5747       new_node.disk_state_static = self.new_disk_state
5748
5749     # Add node to our /etc/hosts, and add key to known_hosts
5750     if self.cfg.GetClusterInfo().modify_etc_hosts:
5751       master_node = self.cfg.GetMasterNode()
5752       result = self.rpc.call_etc_hosts_modify(master_node,
5753                                               constants.ETC_HOSTS_ADD,
5754                                               self.hostname.name,
5755                                               self.hostname.ip)
5756       result.Raise("Can't update hosts file with new host data")
5757
5758     if new_node.secondary_ip != new_node.primary_ip:
5759       _CheckNodeHasSecondaryIP(self, new_node.name, new_node.secondary_ip,
5760                                False)
5761
5762     node_verify_list = [self.cfg.GetMasterNode()]
5763     node_verify_param = {
5764       constants.NV_NODELIST: ([node], {}),
5765       # TODO: do a node-net-test as well?
5766     }
5767
5768     result = self.rpc.call_node_verify(node_verify_list, node_verify_param,
5769                                        self.cfg.GetClusterName())
5770     for verifier in node_verify_list:
5771       result[verifier].Raise("Cannot communicate with node %s" % verifier)
5772       nl_payload = result[verifier].payload[constants.NV_NODELIST]
5773       if nl_payload:
5774         for failed in nl_payload:
5775           feedback_fn("ssh/hostname verification failed"
5776                       " (checking from %s): %s" %
5777                       (verifier, nl_payload[failed]))
5778         raise errors.OpExecError("ssh/hostname verification failed")
5779
5780     if self.op.readd:
5781       _RedistributeAncillaryFiles(self)
5782       self.context.ReaddNode(new_node)
5783       # make sure we redistribute the config
5784       self.cfg.Update(new_node, feedback_fn)
5785       # and make sure the new node will not have old files around
5786       if not new_node.master_candidate:
5787         result = self.rpc.call_node_demote_from_mc(new_node.name)
5788         msg = result.fail_msg
5789         if msg:
5790           self.LogWarning("Node failed to demote itself from master"
5791                           " candidate status: %s" % msg)
5792     else:
5793       _RedistributeAncillaryFiles(self, additional_nodes=[node],
5794                                   additional_vm=self.op.vm_capable)
5795       self.context.AddNode(new_node, self.proc.GetECId())
5796
5797
5798 class LUNodeSetParams(LogicalUnit):
5799   """Modifies the parameters of a node.
5800
5801   @cvar _F2R: a dictionary from tuples of flags (mc, drained, offline)
5802       to the node role (as _ROLE_*)
5803   @cvar _R2F: a dictionary from node role to tuples of flags
5804   @cvar _FLAGS: a list of attribute names corresponding to the flags
5805
5806   """
5807   HPATH = "node-modify"
5808   HTYPE = constants.HTYPE_NODE
5809   REQ_BGL = False
5810   (_ROLE_CANDIDATE, _ROLE_DRAINED, _ROLE_OFFLINE, _ROLE_REGULAR) = range(4)
5811   _F2R = {
5812     (True, False, False): _ROLE_CANDIDATE,
5813     (False, True, False): _ROLE_DRAINED,
5814     (False, False, True): _ROLE_OFFLINE,
5815     (False, False, False): _ROLE_REGULAR,
5816     }
5817   _R2F = dict((v, k) for k, v in _F2R.items())
5818   _FLAGS = ["master_candidate", "drained", "offline"]
5819
5820   def CheckArguments(self):
5821     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5822     all_mods = [self.op.offline, self.op.master_candidate, self.op.drained,
5823                 self.op.master_capable, self.op.vm_capable,
5824                 self.op.secondary_ip, self.op.ndparams, self.op.hv_state,
5825                 self.op.disk_state]
5826     if all_mods.count(None) == len(all_mods):
5827       raise errors.OpPrereqError("Please pass at least one modification",
5828                                  errors.ECODE_INVAL)
5829     if all_mods.count(True) > 1:
5830       raise errors.OpPrereqError("Can't set the node into more than one"
5831                                  " state at the same time",
5832                                  errors.ECODE_INVAL)
5833
5834     # Boolean value that tells us whether we might be demoting from MC
5835     self.might_demote = (self.op.master_candidate is False or
5836                          self.op.offline is True or
5837                          self.op.drained is True or
5838                          self.op.master_capable is False)
5839
5840     if self.op.secondary_ip:
5841       if not netutils.IP4Address.IsValid(self.op.secondary_ip):
5842         raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
5843                                    " address" % self.op.secondary_ip,
5844                                    errors.ECODE_INVAL)
5845
5846     self.lock_all = self.op.auto_promote and self.might_demote
5847     self.lock_instances = self.op.secondary_ip is not None
5848
5849   def _InstanceFilter(self, instance):
5850     """Filter for getting affected instances.
5851
5852     """
5853     return (instance.disk_template in constants.DTS_INT_MIRROR and
5854             self.op.node_name in instance.all_nodes)
5855
5856   def ExpandNames(self):
5857     if self.lock_all:
5858       self.needed_locks = {locking.LEVEL_NODE: locking.ALL_SET}
5859     else:
5860       self.needed_locks = {locking.LEVEL_NODE: self.op.node_name}
5861
5862     # Since modifying a node can have severe effects on currently running
5863     # operations the resource lock is at least acquired in shared mode
5864     self.needed_locks[locking.LEVEL_NODE_RES] = \
5865       self.needed_locks[locking.LEVEL_NODE]
5866
5867     # Get node resource and instance locks in shared mode; they are not used
5868     # for anything but read-only access
5869     self.share_locks[locking.LEVEL_NODE_RES] = 1
5870     self.share_locks[locking.LEVEL_INSTANCE] = 1
5871
5872     if self.lock_instances:
5873       self.needed_locks[locking.LEVEL_INSTANCE] = \
5874         frozenset(self.cfg.GetInstancesInfoByFilter(self._InstanceFilter))
5875
5876   def BuildHooksEnv(self):
5877     """Build hooks env.
5878
5879     This runs on the master node.
5880
5881     """
5882     return {
5883       "OP_TARGET": self.op.node_name,
5884       "MASTER_CANDIDATE": str(self.op.master_candidate),
5885       "OFFLINE": str(self.op.offline),
5886       "DRAINED": str(self.op.drained),
5887       "MASTER_CAPABLE": str(self.op.master_capable),
5888       "VM_CAPABLE": str(self.op.vm_capable),
5889       }
5890
5891   def BuildHooksNodes(self):
5892     """Build hooks nodes.
5893
5894     """
5895     nl = [self.cfg.GetMasterNode(), self.op.node_name]
5896     return (nl, nl)
5897
5898   def CheckPrereq(self):
5899     """Check prerequisites.
5900
5901     This only checks the instance list against the existing names.
5902
5903     """
5904     node = self.node = self.cfg.GetNodeInfo(self.op.node_name)
5905
5906     if self.lock_instances:
5907       affected_instances = \
5908         self.cfg.GetInstancesInfoByFilter(self._InstanceFilter)
5909
5910       # Verify instance locks
5911       owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
5912       wanted_instances = frozenset(affected_instances.keys())
5913       if wanted_instances - owned_instances:
5914         raise errors.OpPrereqError("Instances affected by changing node %s's"
5915                                    " secondary IP address have changed since"
5916                                    " locks were acquired, wanted '%s', have"
5917                                    " '%s'; retry the operation" %
5918                                    (self.op.node_name,
5919                                     utils.CommaJoin(wanted_instances),
5920                                     utils.CommaJoin(owned_instances)),
5921                                    errors.ECODE_STATE)
5922     else:
5923       affected_instances = None
5924
5925     if (self.op.master_candidate is not None or
5926         self.op.drained is not None or
5927         self.op.offline is not None):
5928       # we can't change the master's node flags
5929       if self.op.node_name == self.cfg.GetMasterNode():
5930         raise errors.OpPrereqError("The master role can be changed"
5931                                    " only via master-failover",
5932                                    errors.ECODE_INVAL)
5933
5934     if self.op.master_candidate and not node.master_capable:
5935       raise errors.OpPrereqError("Node %s is not master capable, cannot make"
5936                                  " it a master candidate" % node.name,
5937                                  errors.ECODE_STATE)
5938
5939     if self.op.vm_capable is False:
5940       (ipri, isec) = self.cfg.GetNodeInstances(self.op.node_name)
5941       if ipri or isec:
5942         raise errors.OpPrereqError("Node %s hosts instances, cannot unset"
5943                                    " the vm_capable flag" % node.name,
5944                                    errors.ECODE_STATE)
5945
5946     if node.master_candidate and self.might_demote and not self.lock_all:
5947       assert not self.op.auto_promote, "auto_promote set but lock_all not"
5948       # check if after removing the current node, we're missing master
5949       # candidates
5950       (mc_remaining, mc_should, _) = \
5951           self.cfg.GetMasterCandidateStats(exceptions=[node.name])
5952       if mc_remaining < mc_should:
5953         raise errors.OpPrereqError("Not enough master candidates, please"
5954                                    " pass auto promote option to allow"
5955                                    " promotion (--auto-promote or RAPI"
5956                                    " auto_promote=True)", errors.ECODE_STATE)
5957
5958     self.old_flags = old_flags = (node.master_candidate,
5959                                   node.drained, node.offline)
5960     assert old_flags in self._F2R, "Un-handled old flags %s" % str(old_flags)
5961     self.old_role = old_role = self._F2R[old_flags]
5962
5963     # Check for ineffective changes
5964     for attr in self._FLAGS:
5965       if (getattr(self.op, attr) is False and getattr(node, attr) is False):
5966         self.LogInfo("Ignoring request to unset flag %s, already unset", attr)
5967         setattr(self.op, attr, None)
5968
5969     # Past this point, any flag change to False means a transition
5970     # away from the respective state, as only real changes are kept
5971
5972     # TODO: We might query the real power state if it supports OOB
5973     if _SupportsOob(self.cfg, node):
5974       if self.op.offline is False and not (node.powered or
5975                                            self.op.powered is True):
5976         raise errors.OpPrereqError(("Node %s needs to be turned on before its"
5977                                     " offline status can be reset") %
5978                                    self.op.node_name, errors.ECODE_STATE)
5979     elif self.op.powered is not None:
5980       raise errors.OpPrereqError(("Unable to change powered state for node %s"
5981                                   " as it does not support out-of-band"
5982                                   " handling") % self.op.node_name,
5983                                  errors.ECODE_STATE)
5984
5985     # If we're being deofflined/drained, we'll MC ourself if needed
5986     if (self.op.drained is False or self.op.offline is False or
5987         (self.op.master_capable and not node.master_capable)):
5988       if _DecideSelfPromotion(self):
5989         self.op.master_candidate = True
5990         self.LogInfo("Auto-promoting node to master candidate")
5991
5992     # If we're no longer master capable, we'll demote ourselves from MC
5993     if self.op.master_capable is False and node.master_candidate:
5994       self.LogInfo("Demoting from master candidate")
5995       self.op.master_candidate = False
5996
5997     # Compute new role
5998     assert [getattr(self.op, attr) for attr in self._FLAGS].count(True) <= 1
5999     if self.op.master_candidate:
6000       new_role = self._ROLE_CANDIDATE
6001     elif self.op.drained:
6002       new_role = self._ROLE_DRAINED
6003     elif self.op.offline:
6004       new_role = self._ROLE_OFFLINE
6005     elif False in [self.op.master_candidate, self.op.drained, self.op.offline]:
6006       # False is still in new flags, which means we're un-setting (the
6007       # only) True flag
6008       new_role = self._ROLE_REGULAR
6009     else: # no new flags, nothing, keep old role
6010       new_role = old_role
6011
6012     self.new_role = new_role
6013
6014     if old_role == self._ROLE_OFFLINE and new_role != old_role:
6015       # Trying to transition out of offline status
6016       result = self.rpc.call_version([node.name])[node.name]
6017       if result.fail_msg:
6018         raise errors.OpPrereqError("Node %s is being de-offlined but fails"
6019                                    " to report its version: %s" %
6020                                    (node.name, result.fail_msg),
6021                                    errors.ECODE_STATE)
6022       else:
6023         self.LogWarning("Transitioning node from offline to online state"
6024                         " without using re-add. Please make sure the node"
6025                         " is healthy!")
6026
6027     # When changing the secondary ip, verify if this is a single-homed to
6028     # multi-homed transition or vice versa, and apply the relevant
6029     # restrictions.
6030     if self.op.secondary_ip:
6031       # Ok even without locking, because this can't be changed by any LU
6032       master = self.cfg.GetNodeInfo(self.cfg.GetMasterNode())
6033       master_singlehomed = master.secondary_ip == master.primary_ip
6034       if master_singlehomed and self.op.secondary_ip != node.primary_ip:
6035         if self.op.force and node.name == master.name:
6036           self.LogWarning("Transitioning from single-homed to multi-homed"
6037                           " cluster. All nodes will require a secondary ip.")
6038         else:
6039           raise errors.OpPrereqError("Changing the secondary ip on a"
6040                                      " single-homed cluster requires the"
6041                                      " --force option to be passed, and the"
6042                                      " target node to be the master",
6043                                      errors.ECODE_INVAL)
6044       elif not master_singlehomed and self.op.secondary_ip == node.primary_ip:
6045         if self.op.force and node.name == master.name:
6046           self.LogWarning("Transitioning from multi-homed to single-homed"
6047                           " cluster. Secondary IPs will have to be removed.")
6048         else:
6049           raise errors.OpPrereqError("Cannot set the secondary IP to be the"
6050                                      " same as the primary IP on a multi-homed"
6051                                      " cluster, unless the --force option is"
6052                                      " passed, and the target node is the"
6053                                      " master", errors.ECODE_INVAL)
6054
6055       assert not (frozenset(affected_instances) -
6056                   self.owned_locks(locking.LEVEL_INSTANCE))
6057
6058       if node.offline:
6059         if affected_instances:
6060           msg = ("Cannot change secondary IP address: offline node has"
6061                  " instances (%s) configured to use it" %
6062                  utils.CommaJoin(affected_instances.keys()))
6063           raise errors.OpPrereqError(msg, errors.ECODE_STATE)
6064       else:
6065         # On online nodes, check that no instances are running, and that
6066         # the node has the new ip and we can reach it.
6067         for instance in affected_instances.values():
6068           _CheckInstanceState(self, instance, INSTANCE_DOWN,
6069                               msg="cannot change secondary ip")
6070
6071         _CheckNodeHasSecondaryIP(self, node.name, self.op.secondary_ip, True)
6072         if master.name != node.name:
6073           # check reachability from master secondary ip to new secondary ip
6074           if not netutils.TcpPing(self.op.secondary_ip,
6075                                   constants.DEFAULT_NODED_PORT,
6076                                   source=master.secondary_ip):
6077             raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
6078                                        " based ping to node daemon port",
6079                                        errors.ECODE_ENVIRON)
6080
6081     if self.op.ndparams:
6082       new_ndparams = _GetUpdatedParams(self.node.ndparams, self.op.ndparams)
6083       utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES)
6084       self.new_ndparams = new_ndparams
6085
6086     if self.op.hv_state:
6087       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
6088                                                  self.node.hv_state_static)
6089
6090     if self.op.disk_state:
6091       self.new_disk_state = \
6092         _MergeAndVerifyDiskState(self.op.disk_state,
6093                                  self.node.disk_state_static)
6094
6095   def Exec(self, feedback_fn):
6096     """Modifies a node.
6097
6098     """
6099     node = self.node
6100     old_role = self.old_role
6101     new_role = self.new_role
6102
6103     result = []
6104
6105     if self.op.ndparams:
6106       node.ndparams = self.new_ndparams
6107
6108     if self.op.powered is not None:
6109       node.powered = self.op.powered
6110
6111     if self.op.hv_state:
6112       node.hv_state_static = self.new_hv_state
6113
6114     if self.op.disk_state:
6115       node.disk_state_static = self.new_disk_state
6116
6117     for attr in ["master_capable", "vm_capable"]:
6118       val = getattr(self.op, attr)
6119       if val is not None:
6120         setattr(node, attr, val)
6121         result.append((attr, str(val)))
6122
6123     if new_role != old_role:
6124       # Tell the node to demote itself, if no longer MC and not offline
6125       if old_role == self._ROLE_CANDIDATE and new_role != self._ROLE_OFFLINE:
6126         msg = self.rpc.call_node_demote_from_mc(node.name).fail_msg
6127         if msg:
6128           self.LogWarning("Node failed to demote itself: %s", msg)
6129
6130       new_flags = self._R2F[new_role]
6131       for of, nf, desc in zip(self.old_flags, new_flags, self._FLAGS):
6132         if of != nf:
6133           result.append((desc, str(nf)))
6134       (node.master_candidate, node.drained, node.offline) = new_flags
6135
6136       # we locked all nodes, we adjust the CP before updating this node
6137       if self.lock_all:
6138         _AdjustCandidatePool(self, [node.name])
6139
6140     if self.op.secondary_ip:
6141       node.secondary_ip = self.op.secondary_ip
6142       result.append(("secondary_ip", self.op.secondary_ip))
6143
6144     # this will trigger configuration file update, if needed
6145     self.cfg.Update(node, feedback_fn)
6146
6147     # this will trigger job queue propagation or cleanup if the mc
6148     # flag changed
6149     if [old_role, new_role].count(self._ROLE_CANDIDATE) == 1:
6150       self.context.ReaddNode(node)
6151
6152     return result
6153
6154
6155 class LUNodePowercycle(NoHooksLU):
6156   """Powercycles a node.
6157
6158   """
6159   REQ_BGL = False
6160
6161   def CheckArguments(self):
6162     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
6163     if self.op.node_name == self.cfg.GetMasterNode() and not self.op.force:
6164       raise errors.OpPrereqError("The node is the master and the force"
6165                                  " parameter was not set",
6166                                  errors.ECODE_INVAL)
6167
6168   def ExpandNames(self):
6169     """Locking for PowercycleNode.
6170
6171     This is a last-resort option and shouldn't block on other
6172     jobs. Therefore, we grab no locks.
6173
6174     """
6175     self.needed_locks = {}
6176
6177   def Exec(self, feedback_fn):
6178     """Reboots a node.
6179
6180     """
6181     result = self.rpc.call_node_powercycle(self.op.node_name,
6182                                            self.cfg.GetHypervisorType())
6183     result.Raise("Failed to schedule the reboot")
6184     return result.payload
6185
6186
6187 class LUClusterQuery(NoHooksLU):
6188   """Query cluster configuration.
6189
6190   """
6191   REQ_BGL = False
6192
6193   def ExpandNames(self):
6194     self.needed_locks = {}
6195
6196   def Exec(self, feedback_fn):
6197     """Return cluster config.
6198
6199     """
6200     cluster = self.cfg.GetClusterInfo()
6201     os_hvp = {}
6202
6203     # Filter just for enabled hypervisors
6204     for os_name, hv_dict in cluster.os_hvp.items():
6205       os_hvp[os_name] = {}
6206       for hv_name, hv_params in hv_dict.items():
6207         if hv_name in cluster.enabled_hypervisors:
6208           os_hvp[os_name][hv_name] = hv_params
6209
6210     # Convert ip_family to ip_version
6211     primary_ip_version = constants.IP4_VERSION
6212     if cluster.primary_ip_family == netutils.IP6Address.family:
6213       primary_ip_version = constants.IP6_VERSION
6214
6215     result = {
6216       "software_version": constants.RELEASE_VERSION,
6217       "protocol_version": constants.PROTOCOL_VERSION,
6218       "config_version": constants.CONFIG_VERSION,
6219       "os_api_version": max(constants.OS_API_VERSIONS),
6220       "export_version": constants.EXPORT_VERSION,
6221       "architecture": runtime.GetArchInfo(),
6222       "name": cluster.cluster_name,
6223       "master": cluster.master_node,
6224       "default_hypervisor": cluster.primary_hypervisor,
6225       "enabled_hypervisors": cluster.enabled_hypervisors,
6226       "hvparams": dict([(hypervisor_name, cluster.hvparams[hypervisor_name])
6227                         for hypervisor_name in cluster.enabled_hypervisors]),
6228       "os_hvp": os_hvp,
6229       "beparams": cluster.beparams,
6230       "osparams": cluster.osparams,
6231       "ipolicy": cluster.ipolicy,
6232       "nicparams": cluster.nicparams,
6233       "ndparams": cluster.ndparams,
6234       "diskparams": cluster.diskparams,
6235       "candidate_pool_size": cluster.candidate_pool_size,
6236       "master_netdev": cluster.master_netdev,
6237       "master_netmask": cluster.master_netmask,
6238       "use_external_mip_script": cluster.use_external_mip_script,
6239       "volume_group_name": cluster.volume_group_name,
6240       "drbd_usermode_helper": cluster.drbd_usermode_helper,
6241       "file_storage_dir": cluster.file_storage_dir,
6242       "shared_file_storage_dir": cluster.shared_file_storage_dir,
6243       "maintain_node_health": cluster.maintain_node_health,
6244       "ctime": cluster.ctime,
6245       "mtime": cluster.mtime,
6246       "uuid": cluster.uuid,
6247       "tags": list(cluster.GetTags()),
6248       "uid_pool": cluster.uid_pool,
6249       "default_iallocator": cluster.default_iallocator,
6250       "reserved_lvs": cluster.reserved_lvs,
6251       "primary_ip_version": primary_ip_version,
6252       "prealloc_wipe_disks": cluster.prealloc_wipe_disks,
6253       "hidden_os": cluster.hidden_os,
6254       "blacklisted_os": cluster.blacklisted_os,
6255       }
6256
6257     return result
6258
6259
6260 class LUClusterConfigQuery(NoHooksLU):
6261   """Return configuration values.
6262
6263   """
6264   REQ_BGL = False
6265
6266   def CheckArguments(self):
6267     self.cq = _ClusterQuery(None, self.op.output_fields, False)
6268
6269   def ExpandNames(self):
6270     self.cq.ExpandNames(self)
6271
6272   def DeclareLocks(self, level):
6273     self.cq.DeclareLocks(self, level)
6274
6275   def Exec(self, feedback_fn):
6276     result = self.cq.OldStyleQuery(self)
6277
6278     assert len(result) == 1
6279
6280     return result[0]
6281
6282
6283 class _ClusterQuery(_QueryBase):
6284   FIELDS = query.CLUSTER_FIELDS
6285
6286   #: Do not sort (there is only one item)
6287   SORT_FIELD = None
6288
6289   def ExpandNames(self, lu):
6290     lu.needed_locks = {}
6291
6292     # The following variables interact with _QueryBase._GetNames
6293     self.wanted = locking.ALL_SET
6294     self.do_locking = self.use_locking
6295
6296     if self.do_locking:
6297       raise errors.OpPrereqError("Can not use locking for cluster queries",
6298                                  errors.ECODE_INVAL)
6299
6300   def DeclareLocks(self, lu, level):
6301     pass
6302
6303   def _GetQueryData(self, lu):
6304     """Computes the list of nodes and their attributes.
6305
6306     """
6307     # Locking is not used
6308     assert not (compat.any(lu.glm.is_owned(level)
6309                            for level in locking.LEVELS
6310                            if level != locking.LEVEL_CLUSTER) or
6311                 self.do_locking or self.use_locking)
6312
6313     if query.CQ_CONFIG in self.requested_data:
6314       cluster = lu.cfg.GetClusterInfo()
6315     else:
6316       cluster = NotImplemented
6317
6318     if query.CQ_QUEUE_DRAINED in self.requested_data:
6319       drain_flag = os.path.exists(pathutils.JOB_QUEUE_DRAIN_FILE)
6320     else:
6321       drain_flag = NotImplemented
6322
6323     if query.CQ_WATCHER_PAUSE in self.requested_data:
6324       watcher_pause = utils.ReadWatcherPauseFile(pathutils.WATCHER_PAUSEFILE)
6325     else:
6326       watcher_pause = NotImplemented
6327
6328     return query.ClusterQueryData(cluster, drain_flag, watcher_pause)
6329
6330
6331 class LUInstanceActivateDisks(NoHooksLU):
6332   """Bring up an instance's disks.
6333
6334   """
6335   REQ_BGL = False
6336
6337   def ExpandNames(self):
6338     self._ExpandAndLockInstance()
6339     self.needed_locks[locking.LEVEL_NODE] = []
6340     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6341
6342   def DeclareLocks(self, level):
6343     if level == locking.LEVEL_NODE:
6344       self._LockInstancesNodes()
6345
6346   def CheckPrereq(self):
6347     """Check prerequisites.
6348
6349     This checks that the instance is in the cluster.
6350
6351     """
6352     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6353     assert self.instance is not None, \
6354       "Cannot retrieve locked instance %s" % self.op.instance_name
6355     _CheckNodeOnline(self, self.instance.primary_node)
6356
6357   def Exec(self, feedback_fn):
6358     """Activate the disks.
6359
6360     """
6361     disks_ok, disks_info = \
6362               _AssembleInstanceDisks(self, self.instance,
6363                                      ignore_size=self.op.ignore_size)
6364     if not disks_ok:
6365       raise errors.OpExecError("Cannot activate block devices")
6366
6367     if self.op.wait_for_sync:
6368       if not _WaitForSync(self, self.instance):
6369         raise errors.OpExecError("Some disks of the instance are degraded!")
6370
6371     return disks_info
6372
6373
6374 def _AssembleInstanceDisks(lu, instance, disks=None, ignore_secondaries=False,
6375                            ignore_size=False):
6376   """Prepare the block devices for an instance.
6377
6378   This sets up the block devices on all nodes.
6379
6380   @type lu: L{LogicalUnit}
6381   @param lu: the logical unit on whose behalf we execute
6382   @type instance: L{objects.Instance}
6383   @param instance: the instance for whose disks we assemble
6384   @type disks: list of L{objects.Disk} or None
6385   @param disks: which disks to assemble (or all, if None)
6386   @type ignore_secondaries: boolean
6387   @param ignore_secondaries: if true, errors on secondary nodes
6388       won't result in an error return from the function
6389   @type ignore_size: boolean
6390   @param ignore_size: if true, the current known size of the disk
6391       will not be used during the disk activation, useful for cases
6392       when the size is wrong
6393   @return: False if the operation failed, otherwise a list of
6394       (host, instance_visible_name, node_visible_name)
6395       with the mapping from node devices to instance devices
6396
6397   """
6398   device_info = []
6399   disks_ok = True
6400   iname = instance.name
6401   disks = _ExpandCheckDisks(instance, disks)
6402
6403   # With the two passes mechanism we try to reduce the window of
6404   # opportunity for the race condition of switching DRBD to primary
6405   # before handshaking occured, but we do not eliminate it
6406
6407   # The proper fix would be to wait (with some limits) until the
6408   # connection has been made and drbd transitions from WFConnection
6409   # into any other network-connected state (Connected, SyncTarget,
6410   # SyncSource, etc.)
6411
6412   # 1st pass, assemble on all nodes in secondary mode
6413   for idx, inst_disk in enumerate(disks):
6414     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
6415       if ignore_size:
6416         node_disk = node_disk.Copy()
6417         node_disk.UnsetSize()
6418       lu.cfg.SetDiskID(node_disk, node)
6419       result = lu.rpc.call_blockdev_assemble(node, (node_disk, instance), iname,
6420                                              False, idx)
6421       msg = result.fail_msg
6422       if msg:
6423         is_offline_secondary = (node in instance.secondary_nodes and
6424                                 result.offline)
6425         lu.proc.LogWarning("Could not prepare block device %s on node %s"
6426                            " (is_primary=False, pass=1): %s",
6427                            inst_disk.iv_name, node, msg)
6428         if not (ignore_secondaries or is_offline_secondary):
6429           disks_ok = False
6430
6431   # FIXME: race condition on drbd migration to primary
6432
6433   # 2nd pass, do only the primary node
6434   for idx, inst_disk in enumerate(disks):
6435     dev_path = None
6436
6437     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
6438       if node != instance.primary_node:
6439         continue
6440       if ignore_size:
6441         node_disk = node_disk.Copy()
6442         node_disk.UnsetSize()
6443       lu.cfg.SetDiskID(node_disk, node)
6444       result = lu.rpc.call_blockdev_assemble(node, (node_disk, instance), iname,
6445                                              True, idx)
6446       msg = result.fail_msg
6447       if msg:
6448         lu.proc.LogWarning("Could not prepare block device %s on node %s"
6449                            " (is_primary=True, pass=2): %s",
6450                            inst_disk.iv_name, node, msg)
6451         disks_ok = False
6452       else:
6453         dev_path = result.payload
6454
6455     device_info.append((instance.primary_node, inst_disk.iv_name, dev_path))
6456
6457   # leave the disks configured for the primary node
6458   # this is a workaround that would be fixed better by
6459   # improving the logical/physical id handling
6460   for disk in disks:
6461     lu.cfg.SetDiskID(disk, instance.primary_node)
6462
6463   return disks_ok, device_info
6464
6465
6466 def _StartInstanceDisks(lu, instance, force):
6467   """Start the disks of an instance.
6468
6469   """
6470   disks_ok, _ = _AssembleInstanceDisks(lu, instance,
6471                                            ignore_secondaries=force)
6472   if not disks_ok:
6473     _ShutdownInstanceDisks(lu, instance)
6474     if force is not None and not force:
6475       lu.proc.LogWarning("", hint="If the message above refers to a"
6476                          " secondary node,"
6477                          " you can retry the operation using '--force'.")
6478     raise errors.OpExecError("Disk consistency error")
6479
6480
6481 class LUInstanceDeactivateDisks(NoHooksLU):
6482   """Shutdown an instance's disks.
6483
6484   """
6485   REQ_BGL = False
6486
6487   def ExpandNames(self):
6488     self._ExpandAndLockInstance()
6489     self.needed_locks[locking.LEVEL_NODE] = []
6490     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6491
6492   def DeclareLocks(self, level):
6493     if level == locking.LEVEL_NODE:
6494       self._LockInstancesNodes()
6495
6496   def CheckPrereq(self):
6497     """Check prerequisites.
6498
6499     This checks that the instance is in the cluster.
6500
6501     """
6502     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6503     assert self.instance is not None, \
6504       "Cannot retrieve locked instance %s" % self.op.instance_name
6505
6506   def Exec(self, feedback_fn):
6507     """Deactivate the disks
6508
6509     """
6510     instance = self.instance
6511     if self.op.force:
6512       _ShutdownInstanceDisks(self, instance)
6513     else:
6514       _SafeShutdownInstanceDisks(self, instance)
6515
6516
6517 def _SafeShutdownInstanceDisks(lu, instance, disks=None):
6518   """Shutdown block devices of an instance.
6519
6520   This function checks if an instance is running, before calling
6521   _ShutdownInstanceDisks.
6522
6523   """
6524   _CheckInstanceState(lu, instance, INSTANCE_DOWN, msg="cannot shutdown disks")
6525   _ShutdownInstanceDisks(lu, instance, disks=disks)
6526
6527
6528 def _ExpandCheckDisks(instance, disks):
6529   """Return the instance disks selected by the disks list
6530
6531   @type disks: list of L{objects.Disk} or None
6532   @param disks: selected disks
6533   @rtype: list of L{objects.Disk}
6534   @return: selected instance disks to act on
6535
6536   """
6537   if disks is None:
6538     return instance.disks
6539   else:
6540     if not set(disks).issubset(instance.disks):
6541       raise errors.ProgrammerError("Can only act on disks belonging to the"
6542                                    " target instance")
6543     return disks
6544
6545
6546 def _ShutdownInstanceDisks(lu, instance, disks=None, ignore_primary=False):
6547   """Shutdown block devices of an instance.
6548
6549   This does the shutdown on all nodes of the instance.
6550
6551   If the ignore_primary is false, errors on the primary node are
6552   ignored.
6553
6554   """
6555   all_result = True
6556   disks = _ExpandCheckDisks(instance, disks)
6557
6558   for disk in disks:
6559     for node, top_disk in disk.ComputeNodeTree(instance.primary_node):
6560       lu.cfg.SetDiskID(top_disk, node)
6561       result = lu.rpc.call_blockdev_shutdown(node, (top_disk, instance))
6562       msg = result.fail_msg
6563       if msg:
6564         lu.LogWarning("Could not shutdown block device %s on node %s: %s",
6565                       disk.iv_name, node, msg)
6566         if ((node == instance.primary_node and not ignore_primary) or
6567             (node != instance.primary_node and not result.offline)):
6568           all_result = False
6569   return all_result
6570
6571
6572 def _CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name):
6573   """Checks if a node has enough free memory.
6574
6575   This function check if a given node has the needed amount of free
6576   memory. In case the node has less memory or we cannot get the
6577   information from the node, this function raise an OpPrereqError
6578   exception.
6579
6580   @type lu: C{LogicalUnit}
6581   @param lu: a logical unit from which we get configuration data
6582   @type node: C{str}
6583   @param node: the node to check
6584   @type reason: C{str}
6585   @param reason: string to use in the error message
6586   @type requested: C{int}
6587   @param requested: the amount of memory in MiB to check for
6588   @type hypervisor_name: C{str}
6589   @param hypervisor_name: the hypervisor to ask for memory stats
6590   @rtype: integer
6591   @return: node current free memory
6592   @raise errors.OpPrereqError: if the node doesn't have enough memory, or
6593       we cannot check the node
6594
6595   """
6596   nodeinfo = lu.rpc.call_node_info([node], None, [hypervisor_name])
6597   nodeinfo[node].Raise("Can't get data from node %s" % node,
6598                        prereq=True, ecode=errors.ECODE_ENVIRON)
6599   (_, _, (hv_info, )) = nodeinfo[node].payload
6600
6601   free_mem = hv_info.get("memory_free", None)
6602   if not isinstance(free_mem, int):
6603     raise errors.OpPrereqError("Can't compute free memory on node %s, result"
6604                                " was '%s'" % (node, free_mem),
6605                                errors.ECODE_ENVIRON)
6606   if requested > free_mem:
6607     raise errors.OpPrereqError("Not enough memory on node %s for %s:"
6608                                " needed %s MiB, available %s MiB" %
6609                                (node, reason, requested, free_mem),
6610                                errors.ECODE_NORES)
6611   return free_mem
6612
6613
6614 def _CheckNodesFreeDiskPerVG(lu, nodenames, req_sizes):
6615   """Checks if nodes have enough free disk space in the all VGs.
6616
6617   This function check if all given nodes have the needed amount of
6618   free disk. In case any node has less disk or we cannot get the
6619   information from the node, this function raise an OpPrereqError
6620   exception.
6621
6622   @type lu: C{LogicalUnit}
6623   @param lu: a logical unit from which we get configuration data
6624   @type nodenames: C{list}
6625   @param nodenames: the list of node names to check
6626   @type req_sizes: C{dict}
6627   @param req_sizes: the hash of vg and corresponding amount of disk in
6628       MiB to check for
6629   @raise errors.OpPrereqError: if the node doesn't have enough disk,
6630       or we cannot check the node
6631
6632   """
6633   for vg, req_size in req_sizes.items():
6634     _CheckNodesFreeDiskOnVG(lu, nodenames, vg, req_size)
6635
6636
6637 def _CheckNodesFreeDiskOnVG(lu, nodenames, vg, requested):
6638   """Checks if nodes have enough free disk space in the specified VG.
6639
6640   This function check if all given nodes have the needed amount of
6641   free disk. In case any node has less disk or we cannot get the
6642   information from the node, this function raise an OpPrereqError
6643   exception.
6644
6645   @type lu: C{LogicalUnit}
6646   @param lu: a logical unit from which we get configuration data
6647   @type nodenames: C{list}
6648   @param nodenames: the list of node names to check
6649   @type vg: C{str}
6650   @param vg: the volume group to check
6651   @type requested: C{int}
6652   @param requested: the amount of disk in MiB to check for
6653   @raise errors.OpPrereqError: if the node doesn't have enough disk,
6654       or we cannot check the node
6655
6656   """
6657   nodeinfo = lu.rpc.call_node_info(nodenames, [vg], None)
6658   for node in nodenames:
6659     info = nodeinfo[node]
6660     info.Raise("Cannot get current information from node %s" % node,
6661                prereq=True, ecode=errors.ECODE_ENVIRON)
6662     (_, (vg_info, ), _) = info.payload
6663     vg_free = vg_info.get("vg_free", None)
6664     if not isinstance(vg_free, int):
6665       raise errors.OpPrereqError("Can't compute free disk space on node"
6666                                  " %s for vg %s, result was '%s'" %
6667                                  (node, vg, vg_free), errors.ECODE_ENVIRON)
6668     if requested > vg_free:
6669       raise errors.OpPrereqError("Not enough disk space on target node %s"
6670                                  " vg %s: required %d MiB, available %d MiB" %
6671                                  (node, vg, requested, vg_free),
6672                                  errors.ECODE_NORES)
6673
6674
6675 def _CheckNodesPhysicalCPUs(lu, nodenames, requested, hypervisor_name):
6676   """Checks if nodes have enough physical CPUs
6677
6678   This function checks if all given nodes have the needed number of
6679   physical CPUs. In case any node has less CPUs or we cannot get the
6680   information from the node, this function raises an OpPrereqError
6681   exception.
6682
6683   @type lu: C{LogicalUnit}
6684   @param lu: a logical unit from which we get configuration data
6685   @type nodenames: C{list}
6686   @param nodenames: the list of node names to check
6687   @type requested: C{int}
6688   @param requested: the minimum acceptable number of physical CPUs
6689   @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
6690       or we cannot check the node
6691
6692   """
6693   nodeinfo = lu.rpc.call_node_info(nodenames, None, [hypervisor_name])
6694   for node in nodenames:
6695     info = nodeinfo[node]
6696     info.Raise("Cannot get current information from node %s" % node,
6697                prereq=True, ecode=errors.ECODE_ENVIRON)
6698     (_, _, (hv_info, )) = info.payload
6699     num_cpus = hv_info.get("cpu_total", None)
6700     if not isinstance(num_cpus, int):
6701       raise errors.OpPrereqError("Can't compute the number of physical CPUs"
6702                                  " on node %s, result was '%s'" %
6703                                  (node, num_cpus), errors.ECODE_ENVIRON)
6704     if requested > num_cpus:
6705       raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
6706                                  "required" % (node, num_cpus, requested),
6707                                  errors.ECODE_NORES)
6708
6709
6710 class LUInstanceStartup(LogicalUnit):
6711   """Starts an instance.
6712
6713   """
6714   HPATH = "instance-start"
6715   HTYPE = constants.HTYPE_INSTANCE
6716   REQ_BGL = False
6717
6718   def CheckArguments(self):
6719     # extra beparams
6720     if self.op.beparams:
6721       # fill the beparams dict
6722       objects.UpgradeBeParams(self.op.beparams)
6723       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
6724
6725   def ExpandNames(self):
6726     self._ExpandAndLockInstance()
6727     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
6728
6729   def DeclareLocks(self, level):
6730     if level == locking.LEVEL_NODE_RES:
6731       self._LockInstancesNodes(primary_only=True, level=locking.LEVEL_NODE_RES)
6732
6733   def BuildHooksEnv(self):
6734     """Build hooks env.
6735
6736     This runs on master, primary and secondary nodes of the instance.
6737
6738     """
6739     env = {
6740       "FORCE": self.op.force,
6741       }
6742
6743     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
6744
6745     return env
6746
6747   def BuildHooksNodes(self):
6748     """Build hooks nodes.
6749
6750     """
6751     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6752     return (nl, nl)
6753
6754   def CheckPrereq(self):
6755     """Check prerequisites.
6756
6757     This checks that the instance is in the cluster.
6758
6759     """
6760     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6761     assert self.instance is not None, \
6762       "Cannot retrieve locked instance %s" % self.op.instance_name
6763
6764     # extra hvparams
6765     if self.op.hvparams:
6766       # check hypervisor parameter syntax (locally)
6767       cluster = self.cfg.GetClusterInfo()
6768       utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
6769       filled_hvp = cluster.FillHV(instance)
6770       filled_hvp.update(self.op.hvparams)
6771       hv_type = hypervisor.GetHypervisor(instance.hypervisor)
6772       hv_type.CheckParameterSyntax(filled_hvp)
6773       _CheckHVParams(self, instance.all_nodes, instance.hypervisor, filled_hvp)
6774
6775     _CheckInstanceState(self, instance, INSTANCE_ONLINE)
6776
6777     self.primary_offline = self.cfg.GetNodeInfo(instance.primary_node).offline
6778
6779     if self.primary_offline and self.op.ignore_offline_nodes:
6780       self.proc.LogWarning("Ignoring offline primary node")
6781
6782       if self.op.hvparams or self.op.beparams:
6783         self.proc.LogWarning("Overridden parameters are ignored")
6784     else:
6785       _CheckNodeOnline(self, instance.primary_node)
6786
6787       bep = self.cfg.GetClusterInfo().FillBE(instance)
6788       bep.update(self.op.beparams)
6789
6790       # check bridges existence
6791       _CheckInstanceBridgesExist(self, instance)
6792
6793       remote_info = self.rpc.call_instance_info(instance.primary_node,
6794                                                 instance.name,
6795                                                 instance.hypervisor)
6796       remote_info.Raise("Error checking node %s" % instance.primary_node,
6797                         prereq=True, ecode=errors.ECODE_ENVIRON)
6798       if not remote_info.payload: # not running already
6799         _CheckNodeFreeMemory(self, instance.primary_node,
6800                              "starting instance %s" % instance.name,
6801                              bep[constants.BE_MINMEM], instance.hypervisor)
6802
6803   def Exec(self, feedback_fn):
6804     """Start the instance.
6805
6806     """
6807     instance = self.instance
6808     force = self.op.force
6809
6810     if not self.op.no_remember:
6811       self.cfg.MarkInstanceUp(instance.name)
6812
6813     if self.primary_offline:
6814       assert self.op.ignore_offline_nodes
6815       self.proc.LogInfo("Primary node offline, marked instance as started")
6816     else:
6817       node_current = instance.primary_node
6818
6819       _StartInstanceDisks(self, instance, force)
6820
6821       result = \
6822         self.rpc.call_instance_start(node_current,
6823                                      (instance, self.op.hvparams,
6824                                       self.op.beparams),
6825                                      self.op.startup_paused)
6826       msg = result.fail_msg
6827       if msg:
6828         _ShutdownInstanceDisks(self, instance)
6829         raise errors.OpExecError("Could not start instance: %s" % msg)
6830
6831
6832 class LUInstanceReboot(LogicalUnit):
6833   """Reboot an instance.
6834
6835   """
6836   HPATH = "instance-reboot"
6837   HTYPE = constants.HTYPE_INSTANCE
6838   REQ_BGL = False
6839
6840   def ExpandNames(self):
6841     self._ExpandAndLockInstance()
6842
6843   def BuildHooksEnv(self):
6844     """Build hooks env.
6845
6846     This runs on master, primary and secondary nodes of the instance.
6847
6848     """
6849     env = {
6850       "IGNORE_SECONDARIES": self.op.ignore_secondaries,
6851       "REBOOT_TYPE": self.op.reboot_type,
6852       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
6853       }
6854
6855     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
6856
6857     return env
6858
6859   def BuildHooksNodes(self):
6860     """Build hooks nodes.
6861
6862     """
6863     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6864     return (nl, nl)
6865
6866   def CheckPrereq(self):
6867     """Check prerequisites.
6868
6869     This checks that the instance is in the cluster.
6870
6871     """
6872     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6873     assert self.instance is not None, \
6874       "Cannot retrieve locked instance %s" % self.op.instance_name
6875     _CheckInstanceState(self, instance, INSTANCE_ONLINE)
6876     _CheckNodeOnline(self, instance.primary_node)
6877
6878     # check bridges existence
6879     _CheckInstanceBridgesExist(self, instance)
6880
6881   def Exec(self, feedback_fn):
6882     """Reboot the instance.
6883
6884     """
6885     instance = self.instance
6886     ignore_secondaries = self.op.ignore_secondaries
6887     reboot_type = self.op.reboot_type
6888
6889     remote_info = self.rpc.call_instance_info(instance.primary_node,
6890                                               instance.name,
6891                                               instance.hypervisor)
6892     remote_info.Raise("Error checking node %s" % instance.primary_node)
6893     instance_running = bool(remote_info.payload)
6894
6895     node_current = instance.primary_node
6896
6897     if instance_running and reboot_type in [constants.INSTANCE_REBOOT_SOFT,
6898                                             constants.INSTANCE_REBOOT_HARD]:
6899       for disk in instance.disks:
6900         self.cfg.SetDiskID(disk, node_current)
6901       result = self.rpc.call_instance_reboot(node_current, instance,
6902                                              reboot_type,
6903                                              self.op.shutdown_timeout)
6904       result.Raise("Could not reboot instance")
6905     else:
6906       if instance_running:
6907         result = self.rpc.call_instance_shutdown(node_current, instance,
6908                                                  self.op.shutdown_timeout)
6909         result.Raise("Could not shutdown instance for full reboot")
6910         _ShutdownInstanceDisks(self, instance)
6911       else:
6912         self.LogInfo("Instance %s was already stopped, starting now",
6913                      instance.name)
6914       _StartInstanceDisks(self, instance, ignore_secondaries)
6915       result = self.rpc.call_instance_start(node_current,
6916                                             (instance, None, None), False)
6917       msg = result.fail_msg
6918       if msg:
6919         _ShutdownInstanceDisks(self, instance)
6920         raise errors.OpExecError("Could not start instance for"
6921                                  " full reboot: %s" % msg)
6922
6923     self.cfg.MarkInstanceUp(instance.name)
6924
6925
6926 class LUInstanceShutdown(LogicalUnit):
6927   """Shutdown an instance.
6928
6929   """
6930   HPATH = "instance-stop"
6931   HTYPE = constants.HTYPE_INSTANCE
6932   REQ_BGL = False
6933
6934   def ExpandNames(self):
6935     self._ExpandAndLockInstance()
6936
6937   def BuildHooksEnv(self):
6938     """Build hooks env.
6939
6940     This runs on master, primary and secondary nodes of the instance.
6941
6942     """
6943     env = _BuildInstanceHookEnvByObject(self, self.instance)
6944     env["TIMEOUT"] = self.op.timeout
6945     return env
6946
6947   def BuildHooksNodes(self):
6948     """Build hooks nodes.
6949
6950     """
6951     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6952     return (nl, nl)
6953
6954   def CheckPrereq(self):
6955     """Check prerequisites.
6956
6957     This checks that the instance is in the cluster.
6958
6959     """
6960     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6961     assert self.instance is not None, \
6962       "Cannot retrieve locked instance %s" % self.op.instance_name
6963
6964     _CheckInstanceState(self, self.instance, INSTANCE_ONLINE)
6965
6966     self.primary_offline = \
6967       self.cfg.GetNodeInfo(self.instance.primary_node).offline
6968
6969     if self.primary_offline and self.op.ignore_offline_nodes:
6970       self.proc.LogWarning("Ignoring offline primary node")
6971     else:
6972       _CheckNodeOnline(self, self.instance.primary_node)
6973
6974   def Exec(self, feedback_fn):
6975     """Shutdown the instance.
6976
6977     """
6978     instance = self.instance
6979     node_current = instance.primary_node
6980     timeout = self.op.timeout
6981
6982     if not self.op.no_remember:
6983       self.cfg.MarkInstanceDown(instance.name)
6984
6985     if self.primary_offline:
6986       assert self.op.ignore_offline_nodes
6987       self.proc.LogInfo("Primary node offline, marked instance as stopped")
6988     else:
6989       result = self.rpc.call_instance_shutdown(node_current, instance, timeout)
6990       msg = result.fail_msg
6991       if msg:
6992         self.proc.LogWarning("Could not shutdown instance: %s" % msg)
6993
6994       _ShutdownInstanceDisks(self, instance)
6995
6996
6997 class LUInstanceReinstall(LogicalUnit):
6998   """Reinstall an instance.
6999
7000   """
7001   HPATH = "instance-reinstall"
7002   HTYPE = constants.HTYPE_INSTANCE
7003   REQ_BGL = False
7004
7005   def ExpandNames(self):
7006     self._ExpandAndLockInstance()
7007
7008   def BuildHooksEnv(self):
7009     """Build hooks env.
7010
7011     This runs on master, primary and secondary nodes of the instance.
7012
7013     """
7014     return _BuildInstanceHookEnvByObject(self, self.instance)
7015
7016   def BuildHooksNodes(self):
7017     """Build hooks nodes.
7018
7019     """
7020     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7021     return (nl, nl)
7022
7023   def CheckPrereq(self):
7024     """Check prerequisites.
7025
7026     This checks that the instance is in the cluster and is not running.
7027
7028     """
7029     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7030     assert instance is not None, \
7031       "Cannot retrieve locked instance %s" % self.op.instance_name
7032     _CheckNodeOnline(self, instance.primary_node, "Instance primary node"
7033                      " offline, cannot reinstall")
7034
7035     if instance.disk_template == constants.DT_DISKLESS:
7036       raise errors.OpPrereqError("Instance '%s' has no disks" %
7037                                  self.op.instance_name,
7038                                  errors.ECODE_INVAL)
7039     _CheckInstanceState(self, instance, INSTANCE_DOWN, msg="cannot reinstall")
7040
7041     if self.op.os_type is not None:
7042       # OS verification
7043       pnode = _ExpandNodeName(self.cfg, instance.primary_node)
7044       _CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant)
7045       instance_os = self.op.os_type
7046     else:
7047       instance_os = instance.os
7048
7049     nodelist = list(instance.all_nodes)
7050
7051     if self.op.osparams:
7052       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
7053       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
7054       self.os_inst = i_osdict # the new dict (without defaults)
7055     else:
7056       self.os_inst = None
7057
7058     self.instance = instance
7059
7060   def Exec(self, feedback_fn):
7061     """Reinstall the instance.
7062
7063     """
7064     inst = self.instance
7065
7066     if self.op.os_type is not None:
7067       feedback_fn("Changing OS to '%s'..." % self.op.os_type)
7068       inst.os = self.op.os_type
7069       # Write to configuration
7070       self.cfg.Update(inst, feedback_fn)
7071
7072     _StartInstanceDisks(self, inst, None)
7073     try:
7074       feedback_fn("Running the instance OS create scripts...")
7075       # FIXME: pass debug option from opcode to backend
7076       result = self.rpc.call_instance_os_add(inst.primary_node,
7077                                              (inst, self.os_inst), True,
7078                                              self.op.debug_level)
7079       result.Raise("Could not install OS for instance %s on node %s" %
7080                    (inst.name, inst.primary_node))
7081     finally:
7082       _ShutdownInstanceDisks(self, inst)
7083
7084
7085 class LUInstanceRecreateDisks(LogicalUnit):
7086   """Recreate an instance's missing disks.
7087
7088   """
7089   HPATH = "instance-recreate-disks"
7090   HTYPE = constants.HTYPE_INSTANCE
7091   REQ_BGL = False
7092
7093   _MODIFYABLE = frozenset([
7094     constants.IDISK_SIZE,
7095     constants.IDISK_MODE,
7096     ])
7097
7098   # New or changed disk parameters may have different semantics
7099   assert constants.IDISK_PARAMS == (_MODIFYABLE | frozenset([
7100     constants.IDISK_ADOPT,
7101
7102     # TODO: Implement support changing VG while recreating
7103     constants.IDISK_VG,
7104     constants.IDISK_METAVG,
7105     ]))
7106
7107   def _RunAllocator(self):
7108     """Run the allocator based on input opcode.
7109
7110     """
7111     be_full = self.cfg.GetClusterInfo().FillBE(self.instance)
7112
7113     # FIXME
7114     # The allocator should actually run in "relocate" mode, but current
7115     # allocators don't support relocating all the nodes of an instance at
7116     # the same time. As a workaround we use "allocate" mode, but this is
7117     # suboptimal for two reasons:
7118     # - The instance name passed to the allocator is present in the list of
7119     #   existing instances, so there could be a conflict within the
7120     #   internal structures of the allocator. This doesn't happen with the
7121     #   current allocators, but it's a liability.
7122     # - The allocator counts the resources used by the instance twice: once
7123     #   because the instance exists already, and once because it tries to
7124     #   allocate a new instance.
7125     # The allocator could choose some of the nodes on which the instance is
7126     # running, but that's not a problem. If the instance nodes are broken,
7127     # they should be already be marked as drained or offline, and hence
7128     # skipped by the allocator. If instance disks have been lost for other
7129     # reasons, then recreating the disks on the same nodes should be fine.
7130     disk_template = self.instance.disk_template
7131     spindle_use = be_full[constants.BE_SPINDLE_USE]
7132     req = iallocator.IAReqInstanceAlloc(name=self.op.instance_name,
7133                                         disk_template=disk_template,
7134                                         tags=list(self.instance.GetTags()),
7135                                         os=self.instance.os,
7136                                         nics=[{}],
7137                                         vcpus=be_full[constants.BE_VCPUS],
7138                                         memory=be_full[constants.BE_MAXMEM],
7139                                         spindle_use=spindle_use,
7140                                         disks=[{constants.IDISK_SIZE: d.size,
7141                                                 constants.IDISK_MODE: d.mode}
7142                                                 for d in self.instance.disks],
7143                                         hypervisor=self.instance.hypervisor)
7144     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
7145
7146     ial.Run(self.op.iallocator)
7147
7148     assert req.RequiredNodes() == len(self.instance.all_nodes)
7149
7150     if not ial.success:
7151       raise errors.OpPrereqError("Can't compute nodes using iallocator '%s':"
7152                                  " %s" % (self.op.iallocator, ial.info),
7153                                  errors.ECODE_NORES)
7154
7155     self.op.nodes = ial.result
7156     self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
7157                  self.op.instance_name, self.op.iallocator,
7158                  utils.CommaJoin(ial.result))
7159
7160   def CheckArguments(self):
7161     if self.op.disks and ht.TPositiveInt(self.op.disks[0]):
7162       # Normalize and convert deprecated list of disk indices
7163       self.op.disks = [(idx, {}) for idx in sorted(frozenset(self.op.disks))]
7164
7165     duplicates = utils.FindDuplicates(map(compat.fst, self.op.disks))
7166     if duplicates:
7167       raise errors.OpPrereqError("Some disks have been specified more than"
7168                                  " once: %s" % utils.CommaJoin(duplicates),
7169                                  errors.ECODE_INVAL)
7170
7171     # We don't want _CheckIAllocatorOrNode selecting the default iallocator
7172     # when neither iallocator nor nodes are specified
7173     if self.op.iallocator or self.op.nodes:
7174       _CheckIAllocatorOrNode(self, "iallocator", "nodes")
7175
7176     for (idx, params) in self.op.disks:
7177       utils.ForceDictType(params, constants.IDISK_PARAMS_TYPES)
7178       unsupported = frozenset(params.keys()) - self._MODIFYABLE
7179       if unsupported:
7180         raise errors.OpPrereqError("Parameters for disk %s try to change"
7181                                    " unmodifyable parameter(s): %s" %
7182                                    (idx, utils.CommaJoin(unsupported)),
7183                                    errors.ECODE_INVAL)
7184
7185   def ExpandNames(self):
7186     self._ExpandAndLockInstance()
7187     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
7188     if self.op.nodes:
7189       self.op.nodes = [_ExpandNodeName(self.cfg, n) for n in self.op.nodes]
7190       self.needed_locks[locking.LEVEL_NODE] = list(self.op.nodes)
7191     else:
7192       self.needed_locks[locking.LEVEL_NODE] = []
7193       if self.op.iallocator:
7194         # iallocator will select a new node in the same group
7195         self.needed_locks[locking.LEVEL_NODEGROUP] = []
7196     self.needed_locks[locking.LEVEL_NODE_RES] = []
7197
7198   def DeclareLocks(self, level):
7199     if level == locking.LEVEL_NODEGROUP:
7200       assert self.op.iallocator is not None
7201       assert not self.op.nodes
7202       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
7203       self.share_locks[locking.LEVEL_NODEGROUP] = 1
7204       # Lock the primary group used by the instance optimistically; this
7205       # requires going via the node before it's locked, requiring
7206       # verification later on
7207       self.needed_locks[locking.LEVEL_NODEGROUP] = \
7208         self.cfg.GetInstanceNodeGroups(self.op.instance_name, primary_only=True)
7209
7210     elif level == locking.LEVEL_NODE:
7211       # If an allocator is used, then we lock all the nodes in the current
7212       # instance group, as we don't know yet which ones will be selected;
7213       # if we replace the nodes without using an allocator, locks are
7214       # already declared in ExpandNames; otherwise, we need to lock all the
7215       # instance nodes for disk re-creation
7216       if self.op.iallocator:
7217         assert not self.op.nodes
7218         assert not self.needed_locks[locking.LEVEL_NODE]
7219         assert len(self.owned_locks(locking.LEVEL_NODEGROUP)) == 1
7220
7221         # Lock member nodes of the group of the primary node
7222         for group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP):
7223           self.needed_locks[locking.LEVEL_NODE].extend(
7224             self.cfg.GetNodeGroup(group_uuid).members)
7225       elif not self.op.nodes:
7226         self._LockInstancesNodes(primary_only=False)
7227     elif level == locking.LEVEL_NODE_RES:
7228       # Copy node locks
7229       self.needed_locks[locking.LEVEL_NODE_RES] = \
7230         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
7231
7232   def BuildHooksEnv(self):
7233     """Build hooks env.
7234
7235     This runs on master, primary and secondary nodes of the instance.
7236
7237     """
7238     return _BuildInstanceHookEnvByObject(self, self.instance)
7239
7240   def BuildHooksNodes(self):
7241     """Build hooks nodes.
7242
7243     """
7244     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7245     return (nl, nl)
7246
7247   def CheckPrereq(self):
7248     """Check prerequisites.
7249
7250     This checks that the instance is in the cluster and is not running.
7251
7252     """
7253     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7254     assert instance is not None, \
7255       "Cannot retrieve locked instance %s" % self.op.instance_name
7256     if self.op.nodes:
7257       if len(self.op.nodes) != len(instance.all_nodes):
7258         raise errors.OpPrereqError("Instance %s currently has %d nodes, but"
7259                                    " %d replacement nodes were specified" %
7260                                    (instance.name, len(instance.all_nodes),
7261                                     len(self.op.nodes)),
7262                                    errors.ECODE_INVAL)
7263       assert instance.disk_template != constants.DT_DRBD8 or \
7264           len(self.op.nodes) == 2
7265       assert instance.disk_template != constants.DT_PLAIN or \
7266           len(self.op.nodes) == 1
7267       primary_node = self.op.nodes[0]
7268     else:
7269       primary_node = instance.primary_node
7270     if not self.op.iallocator:
7271       _CheckNodeOnline(self, primary_node)
7272
7273     if instance.disk_template == constants.DT_DISKLESS:
7274       raise errors.OpPrereqError("Instance '%s' has no disks" %
7275                                  self.op.instance_name, errors.ECODE_INVAL)
7276
7277     # Verify if node group locks are still correct
7278     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
7279     if owned_groups:
7280       # Node group locks are acquired only for the primary node (and only
7281       # when the allocator is used)
7282       _CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups,
7283                                primary_only=True)
7284
7285     # if we replace nodes *and* the old primary is offline, we don't
7286     # check the instance state
7287     old_pnode = self.cfg.GetNodeInfo(instance.primary_node)
7288     if not ((self.op.iallocator or self.op.nodes) and old_pnode.offline):
7289       _CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
7290                           msg="cannot recreate disks")
7291
7292     if self.op.disks:
7293       self.disks = dict(self.op.disks)
7294     else:
7295       self.disks = dict((idx, {}) for idx in range(len(instance.disks)))
7296
7297     maxidx = max(self.disks.keys())
7298     if maxidx >= len(instance.disks):
7299       raise errors.OpPrereqError("Invalid disk index '%s'" % maxidx,
7300                                  errors.ECODE_INVAL)
7301
7302     if ((self.op.nodes or self.op.iallocator) and
7303         sorted(self.disks.keys()) != range(len(instance.disks))):
7304       raise errors.OpPrereqError("Can't recreate disks partially and"
7305                                  " change the nodes at the same time",
7306                                  errors.ECODE_INVAL)
7307
7308     self.instance = instance
7309
7310     if self.op.iallocator:
7311       self._RunAllocator()
7312       # Release unneeded node and node resource locks
7313       _ReleaseLocks(self, locking.LEVEL_NODE, keep=self.op.nodes)
7314       _ReleaseLocks(self, locking.LEVEL_NODE_RES, keep=self.op.nodes)
7315
7316   def Exec(self, feedback_fn):
7317     """Recreate the disks.
7318
7319     """
7320     instance = self.instance
7321
7322     assert (self.owned_locks(locking.LEVEL_NODE) ==
7323             self.owned_locks(locking.LEVEL_NODE_RES))
7324
7325     to_skip = []
7326     mods = [] # keeps track of needed changes
7327
7328     for idx, disk in enumerate(instance.disks):
7329       try:
7330         changes = self.disks[idx]
7331       except KeyError:
7332         # Disk should not be recreated
7333         to_skip.append(idx)
7334         continue
7335
7336       # update secondaries for disks, if needed
7337       if self.op.nodes and disk.dev_type == constants.LD_DRBD8:
7338         # need to update the nodes and minors
7339         assert len(self.op.nodes) == 2
7340         assert len(disk.logical_id) == 6 # otherwise disk internals
7341                                          # have changed
7342         (_, _, old_port, _, _, old_secret) = disk.logical_id
7343         new_minors = self.cfg.AllocateDRBDMinor(self.op.nodes, instance.name)
7344         new_id = (self.op.nodes[0], self.op.nodes[1], old_port,
7345                   new_minors[0], new_minors[1], old_secret)
7346         assert len(disk.logical_id) == len(new_id)
7347       else:
7348         new_id = None
7349
7350       mods.append((idx, new_id, changes))
7351
7352     # now that we have passed all asserts above, we can apply the mods
7353     # in a single run (to avoid partial changes)
7354     for idx, new_id, changes in mods:
7355       disk = instance.disks[idx]
7356       if new_id is not None:
7357         assert disk.dev_type == constants.LD_DRBD8
7358         disk.logical_id = new_id
7359       if changes:
7360         disk.Update(size=changes.get(constants.IDISK_SIZE, None),
7361                     mode=changes.get(constants.IDISK_MODE, None))
7362
7363     # change primary node, if needed
7364     if self.op.nodes:
7365       instance.primary_node = self.op.nodes[0]
7366       self.LogWarning("Changing the instance's nodes, you will have to"
7367                       " remove any disks left on the older nodes manually")
7368
7369     if self.op.nodes:
7370       self.cfg.Update(instance, feedback_fn)
7371
7372     # All touched nodes must be locked
7373     mylocks = self.owned_locks(locking.LEVEL_NODE)
7374     assert mylocks.issuperset(frozenset(instance.all_nodes))
7375     _CreateDisks(self, instance, to_skip=to_skip)
7376
7377
7378 class LUInstanceRename(LogicalUnit):
7379   """Rename an instance.
7380
7381   """
7382   HPATH = "instance-rename"
7383   HTYPE = constants.HTYPE_INSTANCE
7384
7385   def CheckArguments(self):
7386     """Check arguments.
7387
7388     """
7389     if self.op.ip_check and not self.op.name_check:
7390       # TODO: make the ip check more flexible and not depend on the name check
7391       raise errors.OpPrereqError("IP address check requires a name check",
7392                                  errors.ECODE_INVAL)
7393
7394   def BuildHooksEnv(self):
7395     """Build hooks env.
7396
7397     This runs on master, primary and secondary nodes of the instance.
7398
7399     """
7400     env = _BuildInstanceHookEnvByObject(self, self.instance)
7401     env["INSTANCE_NEW_NAME"] = self.op.new_name
7402     return env
7403
7404   def BuildHooksNodes(self):
7405     """Build hooks nodes.
7406
7407     """
7408     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7409     return (nl, nl)
7410
7411   def CheckPrereq(self):
7412     """Check prerequisites.
7413
7414     This checks that the instance is in the cluster and is not running.
7415
7416     """
7417     self.op.instance_name = _ExpandInstanceName(self.cfg,
7418                                                 self.op.instance_name)
7419     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7420     assert instance is not None
7421     _CheckNodeOnline(self, instance.primary_node)
7422     _CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
7423                         msg="cannot rename")
7424     self.instance = instance
7425
7426     new_name = self.op.new_name
7427     if self.op.name_check:
7428       hostname = netutils.GetHostname(name=new_name)
7429       if hostname.name != new_name:
7430         self.LogInfo("Resolved given name '%s' to '%s'", new_name,
7431                      hostname.name)
7432       if not utils.MatchNameComponent(self.op.new_name, [hostname.name]):
7433         raise errors.OpPrereqError(("Resolved hostname '%s' does not look the"
7434                                     " same as given hostname '%s'") %
7435                                     (hostname.name, self.op.new_name),
7436                                     errors.ECODE_INVAL)
7437       new_name = self.op.new_name = hostname.name
7438       if (self.op.ip_check and
7439           netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
7440         raise errors.OpPrereqError("IP %s of instance %s already in use" %
7441                                    (hostname.ip, new_name),
7442                                    errors.ECODE_NOTUNIQUE)
7443
7444     instance_list = self.cfg.GetInstanceList()
7445     if new_name in instance_list and new_name != instance.name:
7446       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
7447                                  new_name, errors.ECODE_EXISTS)
7448
7449   def Exec(self, feedback_fn):
7450     """Rename the instance.
7451
7452     """
7453     inst = self.instance
7454     old_name = inst.name
7455
7456     rename_file_storage = False
7457     if (inst.disk_template in constants.DTS_FILEBASED and
7458         self.op.new_name != inst.name):
7459       old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
7460       rename_file_storage = True
7461
7462     self.cfg.RenameInstance(inst.name, self.op.new_name)
7463     # Change the instance lock. This is definitely safe while we hold the BGL.
7464     # Otherwise the new lock would have to be added in acquired mode.
7465     assert self.REQ_BGL
7466     self.glm.remove(locking.LEVEL_INSTANCE, old_name)
7467     self.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
7468
7469     # re-read the instance from the configuration after rename
7470     inst = self.cfg.GetInstanceInfo(self.op.new_name)
7471
7472     if rename_file_storage:
7473       new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
7474       result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
7475                                                      old_file_storage_dir,
7476                                                      new_file_storage_dir)
7477       result.Raise("Could not rename on node %s directory '%s' to '%s'"
7478                    " (but the instance has been renamed in Ganeti)" %
7479                    (inst.primary_node, old_file_storage_dir,
7480                     new_file_storage_dir))
7481
7482     _StartInstanceDisks(self, inst, None)
7483     try:
7484       result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
7485                                                  old_name, self.op.debug_level)
7486       msg = result.fail_msg
7487       if msg:
7488         msg = ("Could not run OS rename script for instance %s on node %s"
7489                " (but the instance has been renamed in Ganeti): %s" %
7490                (inst.name, inst.primary_node, msg))
7491         self.proc.LogWarning(msg)
7492     finally:
7493       _ShutdownInstanceDisks(self, inst)
7494
7495     return inst.name
7496
7497
7498 class LUInstanceRemove(LogicalUnit):
7499   """Remove an instance.
7500
7501   """
7502   HPATH = "instance-remove"
7503   HTYPE = constants.HTYPE_INSTANCE
7504   REQ_BGL = False
7505
7506   def ExpandNames(self):
7507     self._ExpandAndLockInstance()
7508     self.needed_locks[locking.LEVEL_NODE] = []
7509     self.needed_locks[locking.LEVEL_NODE_RES] = []
7510     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7511
7512   def DeclareLocks(self, level):
7513     if level == locking.LEVEL_NODE:
7514       self._LockInstancesNodes()
7515     elif level == locking.LEVEL_NODE_RES:
7516       # Copy node locks
7517       self.needed_locks[locking.LEVEL_NODE_RES] = \
7518         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
7519
7520   def BuildHooksEnv(self):
7521     """Build hooks env.
7522
7523     This runs on master, primary and secondary nodes of the instance.
7524
7525     """
7526     env = _BuildInstanceHookEnvByObject(self, self.instance)
7527     env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
7528     return env
7529
7530   def BuildHooksNodes(self):
7531     """Build hooks nodes.
7532
7533     """
7534     nl = [self.cfg.GetMasterNode()]
7535     nl_post = list(self.instance.all_nodes) + nl
7536     return (nl, nl_post)
7537
7538   def CheckPrereq(self):
7539     """Check prerequisites.
7540
7541     This checks that the instance is in the cluster.
7542
7543     """
7544     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7545     assert self.instance is not None, \
7546       "Cannot retrieve locked instance %s" % self.op.instance_name
7547
7548   def Exec(self, feedback_fn):
7549     """Remove the instance.
7550
7551     """
7552     instance = self.instance
7553     logging.info("Shutting down instance %s on node %s",
7554                  instance.name, instance.primary_node)
7555
7556     result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
7557                                              self.op.shutdown_timeout)
7558     msg = result.fail_msg
7559     if msg:
7560       if self.op.ignore_failures:
7561         feedback_fn("Warning: can't shutdown instance: %s" % msg)
7562       else:
7563         raise errors.OpExecError("Could not shutdown instance %s on"
7564                                  " node %s: %s" %
7565                                  (instance.name, instance.primary_node, msg))
7566
7567     assert (self.owned_locks(locking.LEVEL_NODE) ==
7568             self.owned_locks(locking.LEVEL_NODE_RES))
7569     assert not (set(instance.all_nodes) -
7570                 self.owned_locks(locking.LEVEL_NODE)), \
7571       "Not owning correct locks"
7572
7573     _RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
7574
7575
7576 def _RemoveInstance(lu, feedback_fn, instance, ignore_failures):
7577   """Utility function to remove an instance.
7578
7579   """
7580   logging.info("Removing block devices for instance %s", instance.name)
7581
7582   if not _RemoveDisks(lu, instance, ignore_failures=ignore_failures):
7583     if not ignore_failures:
7584       raise errors.OpExecError("Can't remove instance's disks")
7585     feedback_fn("Warning: can't remove instance's disks")
7586
7587   logging.info("Removing instance %s out of cluster config", instance.name)
7588
7589   lu.cfg.RemoveInstance(instance.name)
7590
7591   assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \
7592     "Instance lock removal conflict"
7593
7594   # Remove lock for the instance
7595   lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
7596
7597
7598 class LUInstanceQuery(NoHooksLU):
7599   """Logical unit for querying instances.
7600
7601   """
7602   # pylint: disable=W0142
7603   REQ_BGL = False
7604
7605   def CheckArguments(self):
7606     self.iq = _InstanceQuery(qlang.MakeSimpleFilter("name", self.op.names),
7607                              self.op.output_fields, self.op.use_locking)
7608
7609   def ExpandNames(self):
7610     self.iq.ExpandNames(self)
7611
7612   def DeclareLocks(self, level):
7613     self.iq.DeclareLocks(self, level)
7614
7615   def Exec(self, feedback_fn):
7616     return self.iq.OldStyleQuery(self)
7617
7618
7619 class LUInstanceFailover(LogicalUnit):
7620   """Failover an instance.
7621
7622   """
7623   HPATH = "instance-failover"
7624   HTYPE = constants.HTYPE_INSTANCE
7625   REQ_BGL = False
7626
7627   def CheckArguments(self):
7628     """Check the arguments.
7629
7630     """
7631     self.iallocator = getattr(self.op, "iallocator", None)
7632     self.target_node = getattr(self.op, "target_node", None)
7633
7634   def ExpandNames(self):
7635     self._ExpandAndLockInstance()
7636
7637     if self.op.target_node is not None:
7638       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7639
7640     self.needed_locks[locking.LEVEL_NODE] = []
7641     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7642
7643     self.needed_locks[locking.LEVEL_NODE_RES] = []
7644     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
7645
7646     ignore_consistency = self.op.ignore_consistency
7647     shutdown_timeout = self.op.shutdown_timeout
7648     self._migrater = TLMigrateInstance(self, self.op.instance_name,
7649                                        cleanup=False,
7650                                        failover=True,
7651                                        ignore_consistency=ignore_consistency,
7652                                        shutdown_timeout=shutdown_timeout,
7653                                        ignore_ipolicy=self.op.ignore_ipolicy)
7654     self.tasklets = [self._migrater]
7655
7656   def DeclareLocks(self, level):
7657     if level == locking.LEVEL_NODE:
7658       instance = self.context.cfg.GetInstanceInfo(self.op.instance_name)
7659       if instance.disk_template in constants.DTS_EXT_MIRROR:
7660         if self.op.target_node is None:
7661           self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7662         else:
7663           self.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
7664                                                    self.op.target_node]
7665         del self.recalculate_locks[locking.LEVEL_NODE]
7666       else:
7667         self._LockInstancesNodes()
7668     elif level == locking.LEVEL_NODE_RES:
7669       # Copy node locks
7670       self.needed_locks[locking.LEVEL_NODE_RES] = \
7671         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
7672
7673   def BuildHooksEnv(self):
7674     """Build hooks env.
7675
7676     This runs on master, primary and secondary nodes of the instance.
7677
7678     """
7679     instance = self._migrater.instance
7680     source_node = instance.primary_node
7681     target_node = self.op.target_node
7682     env = {
7683       "IGNORE_CONSISTENCY": self.op.ignore_consistency,
7684       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
7685       "OLD_PRIMARY": source_node,
7686       "NEW_PRIMARY": target_node,
7687       }
7688
7689     if instance.disk_template in constants.DTS_INT_MIRROR:
7690       env["OLD_SECONDARY"] = instance.secondary_nodes[0]
7691       env["NEW_SECONDARY"] = source_node
7692     else:
7693       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = ""
7694
7695     env.update(_BuildInstanceHookEnvByObject(self, instance))
7696
7697     return env
7698
7699   def BuildHooksNodes(self):
7700     """Build hooks nodes.
7701
7702     """
7703     instance = self._migrater.instance
7704     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
7705     return (nl, nl + [instance.primary_node])
7706
7707
7708 class LUInstanceMigrate(LogicalUnit):
7709   """Migrate an instance.
7710
7711   This is migration without shutting down, compared to the failover,
7712   which is done with shutdown.
7713
7714   """
7715   HPATH = "instance-migrate"
7716   HTYPE = constants.HTYPE_INSTANCE
7717   REQ_BGL = False
7718
7719   def ExpandNames(self):
7720     self._ExpandAndLockInstance()
7721
7722     if self.op.target_node is not None:
7723       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7724
7725     self.needed_locks[locking.LEVEL_NODE] = []
7726     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7727
7728     self.needed_locks[locking.LEVEL_NODE] = []
7729     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7730
7731     self._migrater = \
7732       TLMigrateInstance(self, self.op.instance_name,
7733                         cleanup=self.op.cleanup,
7734                         failover=False,
7735                         fallback=self.op.allow_failover,
7736                         allow_runtime_changes=self.op.allow_runtime_changes,
7737                         ignore_ipolicy=self.op.ignore_ipolicy)
7738     self.tasklets = [self._migrater]
7739
7740   def DeclareLocks(self, level):
7741     if level == locking.LEVEL_NODE:
7742       instance = self.context.cfg.GetInstanceInfo(self.op.instance_name)
7743       if instance.disk_template in constants.DTS_EXT_MIRROR:
7744         if self.op.target_node is None:
7745           self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7746         else:
7747           self.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
7748                                                    self.op.target_node]
7749         del self.recalculate_locks[locking.LEVEL_NODE]
7750       else:
7751         self._LockInstancesNodes()
7752     elif level == locking.LEVEL_NODE_RES:
7753       # Copy node locks
7754       self.needed_locks[locking.LEVEL_NODE_RES] = \
7755         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
7756
7757   def BuildHooksEnv(self):
7758     """Build hooks env.
7759
7760     This runs on master, primary and secondary nodes of the instance.
7761
7762     """
7763     instance = self._migrater.instance
7764     source_node = instance.primary_node
7765     target_node = self.op.target_node
7766     env = _BuildInstanceHookEnvByObject(self, instance)
7767     env.update({
7768       "MIGRATE_LIVE": self._migrater.live,
7769       "MIGRATE_CLEANUP": self.op.cleanup,
7770       "OLD_PRIMARY": source_node,
7771       "NEW_PRIMARY": target_node,
7772       "ALLOW_RUNTIME_CHANGES": self.op.allow_runtime_changes,
7773       })
7774
7775     if instance.disk_template in constants.DTS_INT_MIRROR:
7776       env["OLD_SECONDARY"] = target_node
7777       env["NEW_SECONDARY"] = source_node
7778     else:
7779       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = None
7780
7781     return env
7782
7783   def BuildHooksNodes(self):
7784     """Build hooks nodes.
7785
7786     """
7787     instance = self._migrater.instance
7788     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
7789     return (nl, nl + [instance.primary_node])
7790
7791
7792 class LUInstanceMove(LogicalUnit):
7793   """Move an instance by data-copying.
7794
7795   """
7796   HPATH = "instance-move"
7797   HTYPE = constants.HTYPE_INSTANCE
7798   REQ_BGL = False
7799
7800   def ExpandNames(self):
7801     self._ExpandAndLockInstance()
7802     target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7803     self.op.target_node = target_node
7804     self.needed_locks[locking.LEVEL_NODE] = [target_node]
7805     self.needed_locks[locking.LEVEL_NODE_RES] = []
7806     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
7807
7808   def DeclareLocks(self, level):
7809     if level == locking.LEVEL_NODE:
7810       self._LockInstancesNodes(primary_only=True)
7811     elif level == locking.LEVEL_NODE_RES:
7812       # Copy node locks
7813       self.needed_locks[locking.LEVEL_NODE_RES] = \
7814         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
7815
7816   def BuildHooksEnv(self):
7817     """Build hooks env.
7818
7819     This runs on master, primary and secondary nodes of the instance.
7820
7821     """
7822     env = {
7823       "TARGET_NODE": self.op.target_node,
7824       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
7825       }
7826     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
7827     return env
7828
7829   def BuildHooksNodes(self):
7830     """Build hooks nodes.
7831
7832     """
7833     nl = [
7834       self.cfg.GetMasterNode(),
7835       self.instance.primary_node,
7836       self.op.target_node,
7837       ]
7838     return (nl, nl)
7839
7840   def CheckPrereq(self):
7841     """Check prerequisites.
7842
7843     This checks that the instance is in the cluster.
7844
7845     """
7846     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7847     assert self.instance is not None, \
7848       "Cannot retrieve locked instance %s" % self.op.instance_name
7849
7850     node = self.cfg.GetNodeInfo(self.op.target_node)
7851     assert node is not None, \
7852       "Cannot retrieve locked node %s" % self.op.target_node
7853
7854     self.target_node = target_node = node.name
7855
7856     if target_node == instance.primary_node:
7857       raise errors.OpPrereqError("Instance %s is already on the node %s" %
7858                                  (instance.name, target_node),
7859                                  errors.ECODE_STATE)
7860
7861     bep = self.cfg.GetClusterInfo().FillBE(instance)
7862
7863     for idx, dsk in enumerate(instance.disks):
7864       if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
7865         raise errors.OpPrereqError("Instance disk %d has a complex layout,"
7866                                    " cannot copy" % idx, errors.ECODE_STATE)
7867
7868     _CheckNodeOnline(self, target_node)
7869     _CheckNodeNotDrained(self, target_node)
7870     _CheckNodeVmCapable(self, target_node)
7871     cluster = self.cfg.GetClusterInfo()
7872     group_info = self.cfg.GetNodeGroup(node.group)
7873     ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
7874     _CheckTargetNodeIPolicy(self, ipolicy, instance, node,
7875                             ignore=self.op.ignore_ipolicy)
7876
7877     if instance.admin_state == constants.ADMINST_UP:
7878       # check memory requirements on the secondary node
7879       _CheckNodeFreeMemory(self, target_node, "failing over instance %s" %
7880                            instance.name, bep[constants.BE_MAXMEM],
7881                            instance.hypervisor)
7882     else:
7883       self.LogInfo("Not checking memory on the secondary node as"
7884                    " instance will not be started")
7885
7886     # check bridge existance
7887     _CheckInstanceBridgesExist(self, instance, node=target_node)
7888
7889   def Exec(self, feedback_fn):
7890     """Move an instance.
7891
7892     The move is done by shutting it down on its present node, copying
7893     the data over (slow) and starting it on the new node.
7894
7895     """
7896     instance = self.instance
7897
7898     source_node = instance.primary_node
7899     target_node = self.target_node
7900
7901     self.LogInfo("Shutting down instance %s on source node %s",
7902                  instance.name, source_node)
7903
7904     assert (self.owned_locks(locking.LEVEL_NODE) ==
7905             self.owned_locks(locking.LEVEL_NODE_RES))
7906
7907     result = self.rpc.call_instance_shutdown(source_node, instance,
7908                                              self.op.shutdown_timeout)
7909     msg = result.fail_msg
7910     if msg:
7911       if self.op.ignore_consistency:
7912         self.proc.LogWarning("Could not shutdown instance %s on node %s."
7913                              " Proceeding anyway. Please make sure node"
7914                              " %s is down. Error details: %s",
7915                              instance.name, source_node, source_node, msg)
7916       else:
7917         raise errors.OpExecError("Could not shutdown instance %s on"
7918                                  " node %s: %s" %
7919                                  (instance.name, source_node, msg))
7920
7921     # create the target disks
7922     try:
7923       _CreateDisks(self, instance, target_node=target_node)
7924     except errors.OpExecError:
7925       self.LogWarning("Device creation failed, reverting...")
7926       try:
7927         _RemoveDisks(self, instance, target_node=target_node)
7928       finally:
7929         self.cfg.ReleaseDRBDMinors(instance.name)
7930         raise
7931
7932     cluster_name = self.cfg.GetClusterInfo().cluster_name
7933
7934     errs = []
7935     # activate, get path, copy the data over
7936     for idx, disk in enumerate(instance.disks):
7937       self.LogInfo("Copying data for disk %d", idx)
7938       result = self.rpc.call_blockdev_assemble(target_node, (disk, instance),
7939                                                instance.name, True, idx)
7940       if result.fail_msg:
7941         self.LogWarning("Can't assemble newly created disk %d: %s",
7942                         idx, result.fail_msg)
7943         errs.append(result.fail_msg)
7944         break
7945       dev_path = result.payload
7946       result = self.rpc.call_blockdev_export(source_node, (disk, instance),
7947                                              target_node, dev_path,
7948                                              cluster_name)
7949       if result.fail_msg:
7950         self.LogWarning("Can't copy data over for disk %d: %s",
7951                         idx, result.fail_msg)
7952         errs.append(result.fail_msg)
7953         break
7954
7955     if errs:
7956       self.LogWarning("Some disks failed to copy, aborting")
7957       try:
7958         _RemoveDisks(self, instance, target_node=target_node)
7959       finally:
7960         self.cfg.ReleaseDRBDMinors(instance.name)
7961         raise errors.OpExecError("Errors during disk copy: %s" %
7962                                  (",".join(errs),))
7963
7964     instance.primary_node = target_node
7965     self.cfg.Update(instance, feedback_fn)
7966
7967     self.LogInfo("Removing the disks on the original node")
7968     _RemoveDisks(self, instance, target_node=source_node)
7969
7970     # Only start the instance if it's marked as up
7971     if instance.admin_state == constants.ADMINST_UP:
7972       self.LogInfo("Starting instance %s on node %s",
7973                    instance.name, target_node)
7974
7975       disks_ok, _ = _AssembleInstanceDisks(self, instance,
7976                                            ignore_secondaries=True)
7977       if not disks_ok:
7978         _ShutdownInstanceDisks(self, instance)
7979         raise errors.OpExecError("Can't activate the instance's disks")
7980
7981       result = self.rpc.call_instance_start(target_node,
7982                                             (instance, None, None), False)
7983       msg = result.fail_msg
7984       if msg:
7985         _ShutdownInstanceDisks(self, instance)
7986         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
7987                                  (instance.name, target_node, msg))
7988
7989
7990 class LUNodeMigrate(LogicalUnit):
7991   """Migrate all instances from a node.
7992
7993   """
7994   HPATH = "node-migrate"
7995   HTYPE = constants.HTYPE_NODE
7996   REQ_BGL = False
7997
7998   def CheckArguments(self):
7999     pass
8000
8001   def ExpandNames(self):
8002     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
8003
8004     self.share_locks = _ShareAll()
8005     self.needed_locks = {
8006       locking.LEVEL_NODE: [self.op.node_name],
8007       }
8008
8009   def BuildHooksEnv(self):
8010     """Build hooks env.
8011
8012     This runs on the master, the primary and all the secondaries.
8013
8014     """
8015     return {
8016       "NODE_NAME": self.op.node_name,
8017       "ALLOW_RUNTIME_CHANGES": self.op.allow_runtime_changes,
8018       }
8019
8020   def BuildHooksNodes(self):
8021     """Build hooks nodes.
8022
8023     """
8024     nl = [self.cfg.GetMasterNode()]
8025     return (nl, nl)
8026
8027   def CheckPrereq(self):
8028     pass
8029
8030   def Exec(self, feedback_fn):
8031     # Prepare jobs for migration instances
8032     allow_runtime_changes = self.op.allow_runtime_changes
8033     jobs = [
8034       [opcodes.OpInstanceMigrate(instance_name=inst.name,
8035                                  mode=self.op.mode,
8036                                  live=self.op.live,
8037                                  iallocator=self.op.iallocator,
8038                                  target_node=self.op.target_node,
8039                                  allow_runtime_changes=allow_runtime_changes,
8040                                  ignore_ipolicy=self.op.ignore_ipolicy)]
8041       for inst in _GetNodePrimaryInstances(self.cfg, self.op.node_name)
8042       ]
8043
8044     # TODO: Run iallocator in this opcode and pass correct placement options to
8045     # OpInstanceMigrate. Since other jobs can modify the cluster between
8046     # running the iallocator and the actual migration, a good consistency model
8047     # will have to be found.
8048
8049     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
8050             frozenset([self.op.node_name]))
8051
8052     return ResultWithJobs(jobs)
8053
8054
8055 class TLMigrateInstance(Tasklet):
8056   """Tasklet class for instance migration.
8057
8058   @type live: boolean
8059   @ivar live: whether the migration will be done live or non-live;
8060       this variable is initalized only after CheckPrereq has run
8061   @type cleanup: boolean
8062   @ivar cleanup: Wheater we cleanup from a failed migration
8063   @type iallocator: string
8064   @ivar iallocator: The iallocator used to determine target_node
8065   @type target_node: string
8066   @ivar target_node: If given, the target_node to reallocate the instance to
8067   @type failover: boolean
8068   @ivar failover: Whether operation results in failover or migration
8069   @type fallback: boolean
8070   @ivar fallback: Whether fallback to failover is allowed if migration not
8071                   possible
8072   @type ignore_consistency: boolean
8073   @ivar ignore_consistency: Wheter we should ignore consistency between source
8074                             and target node
8075   @type shutdown_timeout: int
8076   @ivar shutdown_timeout: In case of failover timeout of the shutdown
8077   @type ignore_ipolicy: bool
8078   @ivar ignore_ipolicy: If true, we can ignore instance policy when migrating
8079
8080   """
8081
8082   # Constants
8083   _MIGRATION_POLL_INTERVAL = 1      # seconds
8084   _MIGRATION_FEEDBACK_INTERVAL = 10 # seconds
8085
8086   def __init__(self, lu, instance_name, cleanup=False,
8087                failover=False, fallback=False,
8088                ignore_consistency=False,
8089                allow_runtime_changes=True,
8090                shutdown_timeout=constants.DEFAULT_SHUTDOWN_TIMEOUT,
8091                ignore_ipolicy=False):
8092     """Initializes this class.
8093
8094     """
8095     Tasklet.__init__(self, lu)
8096
8097     # Parameters
8098     self.instance_name = instance_name
8099     self.cleanup = cleanup
8100     self.live = False # will be overridden later
8101     self.failover = failover
8102     self.fallback = fallback
8103     self.ignore_consistency = ignore_consistency
8104     self.shutdown_timeout = shutdown_timeout
8105     self.ignore_ipolicy = ignore_ipolicy
8106     self.allow_runtime_changes = allow_runtime_changes
8107
8108   def CheckPrereq(self):
8109     """Check prerequisites.
8110
8111     This checks that the instance is in the cluster.
8112
8113     """
8114     instance_name = _ExpandInstanceName(self.lu.cfg, self.instance_name)
8115     instance = self.cfg.GetInstanceInfo(instance_name)
8116     assert instance is not None
8117     self.instance = instance
8118     cluster = self.cfg.GetClusterInfo()
8119
8120     if (not self.cleanup and
8121         not instance.admin_state == constants.ADMINST_UP and
8122         not self.failover and self.fallback):
8123       self.lu.LogInfo("Instance is marked down or offline, fallback allowed,"
8124                       " switching to failover")
8125       self.failover = True
8126
8127     if instance.disk_template not in constants.DTS_MIRRORED:
8128       if self.failover:
8129         text = "failovers"
8130       else:
8131         text = "migrations"
8132       raise errors.OpPrereqError("Instance's disk layout '%s' does not allow"
8133                                  " %s" % (instance.disk_template, text),
8134                                  errors.ECODE_STATE)
8135
8136     if instance.disk_template in constants.DTS_EXT_MIRROR:
8137       _CheckIAllocatorOrNode(self.lu, "iallocator", "target_node")
8138
8139       if self.lu.op.iallocator:
8140         self._RunAllocator()
8141       else:
8142         # We set set self.target_node as it is required by
8143         # BuildHooksEnv
8144         self.target_node = self.lu.op.target_node
8145
8146       # Check that the target node is correct in terms of instance policy
8147       nodeinfo = self.cfg.GetNodeInfo(self.target_node)
8148       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
8149       ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
8150                                                               group_info)
8151       _CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo,
8152                               ignore=self.ignore_ipolicy)
8153
8154       # self.target_node is already populated, either directly or by the
8155       # iallocator run
8156       target_node = self.target_node
8157       if self.target_node == instance.primary_node:
8158         raise errors.OpPrereqError("Cannot migrate instance %s"
8159                                    " to its primary (%s)" %
8160                                    (instance.name, instance.primary_node),
8161                                    errors.ECODE_STATE)
8162
8163       if len(self.lu.tasklets) == 1:
8164         # It is safe to release locks only when we're the only tasklet
8165         # in the LU
8166         _ReleaseLocks(self.lu, locking.LEVEL_NODE,
8167                       keep=[instance.primary_node, self.target_node])
8168
8169     else:
8170       secondary_nodes = instance.secondary_nodes
8171       if not secondary_nodes:
8172         raise errors.ConfigurationError("No secondary node but using"
8173                                         " %s disk template" %
8174                                         instance.disk_template)
8175       target_node = secondary_nodes[0]
8176       if self.lu.op.iallocator or (self.lu.op.target_node and
8177                                    self.lu.op.target_node != target_node):
8178         if self.failover:
8179           text = "failed over"
8180         else:
8181           text = "migrated"
8182         raise errors.OpPrereqError("Instances with disk template %s cannot"
8183                                    " be %s to arbitrary nodes"
8184                                    " (neither an iallocator nor a target"
8185                                    " node can be passed)" %
8186                                    (instance.disk_template, text),
8187                                    errors.ECODE_INVAL)
8188       nodeinfo = self.cfg.GetNodeInfo(target_node)
8189       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
8190       ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
8191                                                               group_info)
8192       _CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo,
8193                               ignore=self.ignore_ipolicy)
8194
8195     i_be = cluster.FillBE(instance)
8196
8197     # check memory requirements on the secondary node
8198     if (not self.cleanup and
8199          (not self.failover or instance.admin_state == constants.ADMINST_UP)):
8200       self.tgt_free_mem = _CheckNodeFreeMemory(self.lu, target_node,
8201                                                "migrating instance %s" %
8202                                                instance.name,
8203                                                i_be[constants.BE_MINMEM],
8204                                                instance.hypervisor)
8205     else:
8206       self.lu.LogInfo("Not checking memory on the secondary node as"
8207                       " instance will not be started")
8208
8209     # check if failover must be forced instead of migration
8210     if (not self.cleanup and not self.failover and
8211         i_be[constants.BE_ALWAYS_FAILOVER]):
8212       self.lu.LogInfo("Instance configured to always failover; fallback"
8213                       " to failover")
8214       self.failover = True
8215
8216     # check bridge existance
8217     _CheckInstanceBridgesExist(self.lu, instance, node=target_node)
8218
8219     if not self.cleanup:
8220       _CheckNodeNotDrained(self.lu, target_node)
8221       if not self.failover:
8222         result = self.rpc.call_instance_migratable(instance.primary_node,
8223                                                    instance)
8224         if result.fail_msg and self.fallback:
8225           self.lu.LogInfo("Can't migrate, instance offline, fallback to"
8226                           " failover")
8227           self.failover = True
8228         else:
8229           result.Raise("Can't migrate, please use failover",
8230                        prereq=True, ecode=errors.ECODE_STATE)
8231
8232     assert not (self.failover and self.cleanup)
8233
8234     if not self.failover:
8235       if self.lu.op.live is not None and self.lu.op.mode is not None:
8236         raise errors.OpPrereqError("Only one of the 'live' and 'mode'"
8237                                    " parameters are accepted",
8238                                    errors.ECODE_INVAL)
8239       if self.lu.op.live is not None:
8240         if self.lu.op.live:
8241           self.lu.op.mode = constants.HT_MIGRATION_LIVE
8242         else:
8243           self.lu.op.mode = constants.HT_MIGRATION_NONLIVE
8244         # reset the 'live' parameter to None so that repeated
8245         # invocations of CheckPrereq do not raise an exception
8246         self.lu.op.live = None
8247       elif self.lu.op.mode is None:
8248         # read the default value from the hypervisor
8249         i_hv = cluster.FillHV(self.instance, skip_globals=False)
8250         self.lu.op.mode = i_hv[constants.HV_MIGRATION_MODE]
8251
8252       self.live = self.lu.op.mode == constants.HT_MIGRATION_LIVE
8253     else:
8254       # Failover is never live
8255       self.live = False
8256
8257     if not (self.failover or self.cleanup):
8258       remote_info = self.rpc.call_instance_info(instance.primary_node,
8259                                                 instance.name,
8260                                                 instance.hypervisor)
8261       remote_info.Raise("Error checking instance on node %s" %
8262                         instance.primary_node)
8263       instance_running = bool(remote_info.payload)
8264       if instance_running:
8265         self.current_mem = int(remote_info.payload["memory"])
8266
8267   def _RunAllocator(self):
8268     """Run the allocator based on input opcode.
8269
8270     """
8271     # FIXME: add a self.ignore_ipolicy option
8272     req = iallocator.IAReqRelocate(name=self.instance_name,
8273                                    relocate_from=[self.instance.primary_node])
8274     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
8275
8276     ial.Run(self.lu.op.iallocator)
8277
8278     if not ial.success:
8279       raise errors.OpPrereqError("Can't compute nodes using"
8280                                  " iallocator '%s': %s" %
8281                                  (self.lu.op.iallocator, ial.info),
8282                                  errors.ECODE_NORES)
8283     self.target_node = ial.result[0]
8284     self.lu.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
8285                     self.instance_name, self.lu.op.iallocator,
8286                     utils.CommaJoin(ial.result))
8287
8288   def _WaitUntilSync(self):
8289     """Poll with custom rpc for disk sync.
8290
8291     This uses our own step-based rpc call.
8292
8293     """
8294     self.feedback_fn("* wait until resync is done")
8295     all_done = False
8296     while not all_done:
8297       all_done = True
8298       result = self.rpc.call_drbd_wait_sync(self.all_nodes,
8299                                             self.nodes_ip,
8300                                             (self.instance.disks,
8301                                              self.instance))
8302       min_percent = 100
8303       for node, nres in result.items():
8304         nres.Raise("Cannot resync disks on node %s" % node)
8305         node_done, node_percent = nres.payload
8306         all_done = all_done and node_done
8307         if node_percent is not None:
8308           min_percent = min(min_percent, node_percent)
8309       if not all_done:
8310         if min_percent < 100:
8311           self.feedback_fn("   - progress: %.1f%%" % min_percent)
8312         time.sleep(2)
8313
8314   def _EnsureSecondary(self, node):
8315     """Demote a node to secondary.
8316
8317     """
8318     self.feedback_fn("* switching node %s to secondary mode" % node)
8319
8320     for dev in self.instance.disks:
8321       self.cfg.SetDiskID(dev, node)
8322
8323     result = self.rpc.call_blockdev_close(node, self.instance.name,
8324                                           self.instance.disks)
8325     result.Raise("Cannot change disk to secondary on node %s" % node)
8326
8327   def _GoStandalone(self):
8328     """Disconnect from the network.
8329
8330     """
8331     self.feedback_fn("* changing into standalone mode")
8332     result = self.rpc.call_drbd_disconnect_net(self.all_nodes, self.nodes_ip,
8333                                                self.instance.disks)
8334     for node, nres in result.items():
8335       nres.Raise("Cannot disconnect disks node %s" % node)
8336
8337   def _GoReconnect(self, multimaster):
8338     """Reconnect to the network.
8339
8340     """
8341     if multimaster:
8342       msg = "dual-master"
8343     else:
8344       msg = "single-master"
8345     self.feedback_fn("* changing disks into %s mode" % msg)
8346     result = self.rpc.call_drbd_attach_net(self.all_nodes, self.nodes_ip,
8347                                            (self.instance.disks, self.instance),
8348                                            self.instance.name, multimaster)
8349     for node, nres in result.items():
8350       nres.Raise("Cannot change disks config on node %s" % node)
8351
8352   def _ExecCleanup(self):
8353     """Try to cleanup after a failed migration.
8354
8355     The cleanup is done by:
8356       - check that the instance is running only on one node
8357         (and update the config if needed)
8358       - change disks on its secondary node to secondary
8359       - wait until disks are fully synchronized
8360       - disconnect from the network
8361       - change disks into single-master mode
8362       - wait again until disks are fully synchronized
8363
8364     """
8365     instance = self.instance
8366     target_node = self.target_node
8367     source_node = self.source_node
8368
8369     # check running on only one node
8370     self.feedback_fn("* checking where the instance actually runs"
8371                      " (if this hangs, the hypervisor might be in"
8372                      " a bad state)")
8373     ins_l = self.rpc.call_instance_list(self.all_nodes, [instance.hypervisor])
8374     for node, result in ins_l.items():
8375       result.Raise("Can't contact node %s" % node)
8376
8377     runningon_source = instance.name in ins_l[source_node].payload
8378     runningon_target = instance.name in ins_l[target_node].payload
8379
8380     if runningon_source and runningon_target:
8381       raise errors.OpExecError("Instance seems to be running on two nodes,"
8382                                " or the hypervisor is confused; you will have"
8383                                " to ensure manually that it runs only on one"
8384                                " and restart this operation")
8385
8386     if not (runningon_source or runningon_target):
8387       raise errors.OpExecError("Instance does not seem to be running at all;"
8388                                " in this case it's safer to repair by"
8389                                " running 'gnt-instance stop' to ensure disk"
8390                                " shutdown, and then restarting it")
8391
8392     if runningon_target:
8393       # the migration has actually succeeded, we need to update the config
8394       self.feedback_fn("* instance running on secondary node (%s),"
8395                        " updating config" % target_node)
8396       instance.primary_node = target_node
8397       self.cfg.Update(instance, self.feedback_fn)
8398       demoted_node = source_node
8399     else:
8400       self.feedback_fn("* instance confirmed to be running on its"
8401                        " primary node (%s)" % source_node)
8402       demoted_node = target_node
8403
8404     if instance.disk_template in constants.DTS_INT_MIRROR:
8405       self._EnsureSecondary(demoted_node)
8406       try:
8407         self._WaitUntilSync()
8408       except errors.OpExecError:
8409         # we ignore here errors, since if the device is standalone, it
8410         # won't be able to sync
8411         pass
8412       self._GoStandalone()
8413       self._GoReconnect(False)
8414       self._WaitUntilSync()
8415
8416     self.feedback_fn("* done")
8417
8418   def _RevertDiskStatus(self):
8419     """Try to revert the disk status after a failed migration.
8420
8421     """
8422     target_node = self.target_node
8423     if self.instance.disk_template in constants.DTS_EXT_MIRROR:
8424       return
8425
8426     try:
8427       self._EnsureSecondary(target_node)
8428       self._GoStandalone()
8429       self._GoReconnect(False)
8430       self._WaitUntilSync()
8431     except errors.OpExecError, err:
8432       self.lu.LogWarning("Migration failed and I can't reconnect the drives,"
8433                          " please try to recover the instance manually;"
8434                          " error '%s'" % str(err))
8435
8436   def _AbortMigration(self):
8437     """Call the hypervisor code to abort a started migration.
8438
8439     """
8440     instance = self.instance
8441     target_node = self.target_node
8442     source_node = self.source_node
8443     migration_info = self.migration_info
8444
8445     abort_result = self.rpc.call_instance_finalize_migration_dst(target_node,
8446                                                                  instance,
8447                                                                  migration_info,
8448                                                                  False)
8449     abort_msg = abort_result.fail_msg
8450     if abort_msg:
8451       logging.error("Aborting migration failed on target node %s: %s",
8452                     target_node, abort_msg)
8453       # Don't raise an exception here, as we stil have to try to revert the
8454       # disk status, even if this step failed.
8455
8456     abort_result = self.rpc.call_instance_finalize_migration_src(
8457       source_node, instance, False, self.live)
8458     abort_msg = abort_result.fail_msg
8459     if abort_msg:
8460       logging.error("Aborting migration failed on source node %s: %s",
8461                     source_node, abort_msg)
8462
8463   def _ExecMigration(self):
8464     """Migrate an instance.
8465
8466     The migrate is done by:
8467       - change the disks into dual-master mode
8468       - wait until disks are fully synchronized again
8469       - migrate the instance
8470       - change disks on the new secondary node (the old primary) to secondary
8471       - wait until disks are fully synchronized
8472       - change disks into single-master mode
8473
8474     """
8475     instance = self.instance
8476     target_node = self.target_node
8477     source_node = self.source_node
8478
8479     # Check for hypervisor version mismatch and warn the user.
8480     nodeinfo = self.rpc.call_node_info([source_node, target_node],
8481                                        None, [self.instance.hypervisor])
8482     for ninfo in nodeinfo.values():
8483       ninfo.Raise("Unable to retrieve node information from node '%s'" %
8484                   ninfo.node)
8485     (_, _, (src_info, )) = nodeinfo[source_node].payload
8486     (_, _, (dst_info, )) = nodeinfo[target_node].payload
8487
8488     if ((constants.HV_NODEINFO_KEY_VERSION in src_info) and
8489         (constants.HV_NODEINFO_KEY_VERSION in dst_info)):
8490       src_version = src_info[constants.HV_NODEINFO_KEY_VERSION]
8491       dst_version = dst_info[constants.HV_NODEINFO_KEY_VERSION]
8492       if src_version != dst_version:
8493         self.feedback_fn("* warning: hypervisor version mismatch between"
8494                          " source (%s) and target (%s) node" %
8495                          (src_version, dst_version))
8496
8497     self.feedback_fn("* checking disk consistency between source and target")
8498     for (idx, dev) in enumerate(instance.disks):
8499       if not _CheckDiskConsistency(self.lu, instance, dev, target_node, False):
8500         raise errors.OpExecError("Disk %s is degraded or not fully"
8501                                  " synchronized on target node,"
8502                                  " aborting migration" % idx)
8503
8504     if self.current_mem > self.tgt_free_mem:
8505       if not self.allow_runtime_changes:
8506         raise errors.OpExecError("Memory ballooning not allowed and not enough"
8507                                  " free memory to fit instance %s on target"
8508                                  " node %s (have %dMB, need %dMB)" %
8509                                  (instance.name, target_node,
8510                                   self.tgt_free_mem, self.current_mem))
8511       self.feedback_fn("* setting instance memory to %s" % self.tgt_free_mem)
8512       rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
8513                                                      instance,
8514                                                      self.tgt_free_mem)
8515       rpcres.Raise("Cannot modify instance runtime memory")
8516
8517     # First get the migration information from the remote node
8518     result = self.rpc.call_migration_info(source_node, instance)
8519     msg = result.fail_msg
8520     if msg:
8521       log_err = ("Failed fetching source migration information from %s: %s" %
8522                  (source_node, msg))
8523       logging.error(log_err)
8524       raise errors.OpExecError(log_err)
8525
8526     self.migration_info = migration_info = result.payload
8527
8528     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
8529       # Then switch the disks to master/master mode
8530       self._EnsureSecondary(target_node)
8531       self._GoStandalone()
8532       self._GoReconnect(True)
8533       self._WaitUntilSync()
8534
8535     self.feedback_fn("* preparing %s to accept the instance" % target_node)
8536     result = self.rpc.call_accept_instance(target_node,
8537                                            instance,
8538                                            migration_info,
8539                                            self.nodes_ip[target_node])
8540
8541     msg = result.fail_msg
8542     if msg:
8543       logging.error("Instance pre-migration failed, trying to revert"
8544                     " disk status: %s", msg)
8545       self.feedback_fn("Pre-migration failed, aborting")
8546       self._AbortMigration()
8547       self._RevertDiskStatus()
8548       raise errors.OpExecError("Could not pre-migrate instance %s: %s" %
8549                                (instance.name, msg))
8550
8551     self.feedback_fn("* migrating instance to %s" % target_node)
8552     result = self.rpc.call_instance_migrate(source_node, instance,
8553                                             self.nodes_ip[target_node],
8554                                             self.live)
8555     msg = result.fail_msg
8556     if msg:
8557       logging.error("Instance migration failed, trying to revert"
8558                     " disk status: %s", msg)
8559       self.feedback_fn("Migration failed, aborting")
8560       self._AbortMigration()
8561       self._RevertDiskStatus()
8562       raise errors.OpExecError("Could not migrate instance %s: %s" %
8563                                (instance.name, msg))
8564
8565     self.feedback_fn("* starting memory transfer")
8566     last_feedback = time.time()
8567     while True:
8568       result = self.rpc.call_instance_get_migration_status(source_node,
8569                                                            instance)
8570       msg = result.fail_msg
8571       ms = result.payload   # MigrationStatus instance
8572       if msg or (ms.status in constants.HV_MIGRATION_FAILED_STATUSES):
8573         logging.error("Instance migration failed, trying to revert"
8574                       " disk status: %s", msg)
8575         self.feedback_fn("Migration failed, aborting")
8576         self._AbortMigration()
8577         self._RevertDiskStatus()
8578         raise errors.OpExecError("Could not migrate instance %s: %s" %
8579                                  (instance.name, msg))
8580
8581       if result.payload.status != constants.HV_MIGRATION_ACTIVE:
8582         self.feedback_fn("* memory transfer complete")
8583         break
8584
8585       if (utils.TimeoutExpired(last_feedback,
8586                                self._MIGRATION_FEEDBACK_INTERVAL) and
8587           ms.transferred_ram is not None):
8588         mem_progress = 100 * float(ms.transferred_ram) / float(ms.total_ram)
8589         self.feedback_fn("* memory transfer progress: %.2f %%" % mem_progress)
8590         last_feedback = time.time()
8591
8592       time.sleep(self._MIGRATION_POLL_INTERVAL)
8593
8594     result = self.rpc.call_instance_finalize_migration_src(source_node,
8595                                                            instance,
8596                                                            True,
8597                                                            self.live)
8598     msg = result.fail_msg
8599     if msg:
8600       logging.error("Instance migration succeeded, but finalization failed"
8601                     " on the source node: %s", msg)
8602       raise errors.OpExecError("Could not finalize instance migration: %s" %
8603                                msg)
8604
8605     instance.primary_node = target_node
8606
8607     # distribute new instance config to the other nodes
8608     self.cfg.Update(instance, self.feedback_fn)
8609
8610     result = self.rpc.call_instance_finalize_migration_dst(target_node,
8611                                                            instance,
8612                                                            migration_info,
8613                                                            True)
8614     msg = result.fail_msg
8615     if msg:
8616       logging.error("Instance migration succeeded, but finalization failed"
8617                     " on the target node: %s", msg)
8618       raise errors.OpExecError("Could not finalize instance migration: %s" %
8619                                msg)
8620
8621     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
8622       self._EnsureSecondary(source_node)
8623       self._WaitUntilSync()
8624       self._GoStandalone()
8625       self._GoReconnect(False)
8626       self._WaitUntilSync()
8627
8628     # If the instance's disk template is `rbd' and there was a successful
8629     # migration, unmap the device from the source node.
8630     if self.instance.disk_template == constants.DT_RBD:
8631       disks = _ExpandCheckDisks(instance, instance.disks)
8632       self.feedback_fn("* unmapping instance's disks from %s" % source_node)
8633       for disk in disks:
8634         result = self.rpc.call_blockdev_shutdown(source_node, (disk, instance))
8635         msg = result.fail_msg
8636         if msg:
8637           logging.error("Migration was successful, but couldn't unmap the"
8638                         " block device %s on source node %s: %s",
8639                         disk.iv_name, source_node, msg)
8640           logging.error("You need to unmap the device %s manually on %s",
8641                         disk.iv_name, source_node)
8642
8643     self.feedback_fn("* done")
8644
8645   def _ExecFailover(self):
8646     """Failover an instance.
8647
8648     The failover is done by shutting it down on its present node and
8649     starting it on the secondary.
8650
8651     """
8652     instance = self.instance
8653     primary_node = self.cfg.GetNodeInfo(instance.primary_node)
8654
8655     source_node = instance.primary_node
8656     target_node = self.target_node
8657
8658     if instance.admin_state == constants.ADMINST_UP:
8659       self.feedback_fn("* checking disk consistency between source and target")
8660       for (idx, dev) in enumerate(instance.disks):
8661         # for drbd, these are drbd over lvm
8662         if not _CheckDiskConsistency(self.lu, instance, dev, target_node,
8663                                      False):
8664           if primary_node.offline:
8665             self.feedback_fn("Node %s is offline, ignoring degraded disk %s on"
8666                              " target node %s" %
8667                              (primary_node.name, idx, target_node))
8668           elif not self.ignore_consistency:
8669             raise errors.OpExecError("Disk %s is degraded on target node,"
8670                                      " aborting failover" % idx)
8671     else:
8672       self.feedback_fn("* not checking disk consistency as instance is not"
8673                        " running")
8674
8675     self.feedback_fn("* shutting down instance on source node")
8676     logging.info("Shutting down instance %s on node %s",
8677                  instance.name, source_node)
8678
8679     result = self.rpc.call_instance_shutdown(source_node, instance,
8680                                              self.shutdown_timeout)
8681     msg = result.fail_msg
8682     if msg:
8683       if self.ignore_consistency or primary_node.offline:
8684         self.lu.LogWarning("Could not shutdown instance %s on node %s,"
8685                            " proceeding anyway; please make sure node"
8686                            " %s is down; error details: %s",
8687                            instance.name, source_node, source_node, msg)
8688       else:
8689         raise errors.OpExecError("Could not shutdown instance %s on"
8690                                  " node %s: %s" %
8691                                  (instance.name, source_node, msg))
8692
8693     self.feedback_fn("* deactivating the instance's disks on source node")
8694     if not _ShutdownInstanceDisks(self.lu, instance, ignore_primary=True):
8695       raise errors.OpExecError("Can't shut down the instance's disks")
8696
8697     instance.primary_node = target_node
8698     # distribute new instance config to the other nodes
8699     self.cfg.Update(instance, self.feedback_fn)
8700
8701     # Only start the instance if it's marked as up
8702     if instance.admin_state == constants.ADMINST_UP:
8703       self.feedback_fn("* activating the instance's disks on target node %s" %
8704                        target_node)
8705       logging.info("Starting instance %s on node %s",
8706                    instance.name, target_node)
8707
8708       disks_ok, _ = _AssembleInstanceDisks(self.lu, instance,
8709                                            ignore_secondaries=True)
8710       if not disks_ok:
8711         _ShutdownInstanceDisks(self.lu, instance)
8712         raise errors.OpExecError("Can't activate the instance's disks")
8713
8714       self.feedback_fn("* starting the instance on the target node %s" %
8715                        target_node)
8716       result = self.rpc.call_instance_start(target_node, (instance, None, None),
8717                                             False)
8718       msg = result.fail_msg
8719       if msg:
8720         _ShutdownInstanceDisks(self.lu, instance)
8721         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
8722                                  (instance.name, target_node, msg))
8723
8724   def Exec(self, feedback_fn):
8725     """Perform the migration.
8726
8727     """
8728     self.feedback_fn = feedback_fn
8729     self.source_node = self.instance.primary_node
8730
8731     # FIXME: if we implement migrate-to-any in DRBD, this needs fixing
8732     if self.instance.disk_template in constants.DTS_INT_MIRROR:
8733       self.target_node = self.instance.secondary_nodes[0]
8734       # Otherwise self.target_node has been populated either
8735       # directly, or through an iallocator.
8736
8737     self.all_nodes = [self.source_node, self.target_node]
8738     self.nodes_ip = dict((name, node.secondary_ip) for (name, node)
8739                          in self.cfg.GetMultiNodeInfo(self.all_nodes))
8740
8741     if self.failover:
8742       feedback_fn("Failover instance %s" % self.instance.name)
8743       self._ExecFailover()
8744     else:
8745       feedback_fn("Migrating instance %s" % self.instance.name)
8746
8747       if self.cleanup:
8748         return self._ExecCleanup()
8749       else:
8750         return self._ExecMigration()
8751
8752
8753 def _CreateBlockDev(lu, node, instance, device, force_create, info,
8754                     force_open):
8755   """Wrapper around L{_CreateBlockDevInner}.
8756
8757   This method annotates the root device first.
8758
8759   """
8760   (disk,) = _AnnotateDiskParams(instance, [device], lu.cfg)
8761   return _CreateBlockDevInner(lu, node, instance, disk, force_create, info,
8762                               force_open)
8763
8764
8765 def _CreateBlockDevInner(lu, node, instance, device, force_create,
8766                          info, force_open):
8767   """Create a tree of block devices on a given node.
8768
8769   If this device type has to be created on secondaries, create it and
8770   all its children.
8771
8772   If not, just recurse to children keeping the same 'force' value.
8773
8774   @attention: The device has to be annotated already.
8775
8776   @param lu: the lu on whose behalf we execute
8777   @param node: the node on which to create the device
8778   @type instance: L{objects.Instance}
8779   @param instance: the instance which owns the device
8780   @type device: L{objects.Disk}
8781   @param device: the device to create
8782   @type force_create: boolean
8783   @param force_create: whether to force creation of this device; this
8784       will be change to True whenever we find a device which has
8785       CreateOnSecondary() attribute
8786   @param info: the extra 'metadata' we should attach to the device
8787       (this will be represented as a LVM tag)
8788   @type force_open: boolean
8789   @param force_open: this parameter will be passes to the
8790       L{backend.BlockdevCreate} function where it specifies
8791       whether we run on primary or not, and it affects both
8792       the child assembly and the device own Open() execution
8793
8794   """
8795   if device.CreateOnSecondary():
8796     force_create = True
8797
8798   if device.children:
8799     for child in device.children:
8800       _CreateBlockDevInner(lu, node, instance, child, force_create,
8801                            info, force_open)
8802
8803   if not force_create:
8804     return
8805
8806   _CreateSingleBlockDev(lu, node, instance, device, info, force_open)
8807
8808
8809 def _CreateSingleBlockDev(lu, node, instance, device, info, force_open):
8810   """Create a single block device on a given node.
8811
8812   This will not recurse over children of the device, so they must be
8813   created in advance.
8814
8815   @param lu: the lu on whose behalf we execute
8816   @param node: the node on which to create the device
8817   @type instance: L{objects.Instance}
8818   @param instance: the instance which owns the device
8819   @type device: L{objects.Disk}
8820   @param device: the device to create
8821   @param info: the extra 'metadata' we should attach to the device
8822       (this will be represented as a LVM tag)
8823   @type force_open: boolean
8824   @param force_open: this parameter will be passes to the
8825       L{backend.BlockdevCreate} function where it specifies
8826       whether we run on primary or not, and it affects both
8827       the child assembly and the device own Open() execution
8828
8829   """
8830   lu.cfg.SetDiskID(device, node)
8831   result = lu.rpc.call_blockdev_create(node, device, device.size,
8832                                        instance.name, force_open, info)
8833   result.Raise("Can't create block device %s on"
8834                " node %s for instance %s" % (device, node, instance.name))
8835   if device.physical_id is None:
8836     device.physical_id = result.payload
8837
8838
8839 def _GenerateUniqueNames(lu, exts):
8840   """Generate a suitable LV name.
8841
8842   This will generate a logical volume name for the given instance.
8843
8844   """
8845   results = []
8846   for val in exts:
8847     new_id = lu.cfg.GenerateUniqueID(lu.proc.GetECId())
8848     results.append("%s%s" % (new_id, val))
8849   return results
8850
8851
8852 def _GenerateDRBD8Branch(lu, primary, secondary, size, vgnames, names,
8853                          iv_name, p_minor, s_minor):
8854   """Generate a drbd8 device complete with its children.
8855
8856   """
8857   assert len(vgnames) == len(names) == 2
8858   port = lu.cfg.AllocatePort()
8859   shared_secret = lu.cfg.GenerateDRBDSecret(lu.proc.GetECId())
8860
8861   dev_data = objects.Disk(dev_type=constants.LD_LV, size=size,
8862                           logical_id=(vgnames[0], names[0]),
8863                           params={})
8864   dev_meta = objects.Disk(dev_type=constants.LD_LV,
8865                           size=constants.DRBD_META_SIZE,
8866                           logical_id=(vgnames[1], names[1]),
8867                           params={})
8868   drbd_dev = objects.Disk(dev_type=constants.LD_DRBD8, size=size,
8869                           logical_id=(primary, secondary, port,
8870                                       p_minor, s_minor,
8871                                       shared_secret),
8872                           children=[dev_data, dev_meta],
8873                           iv_name=iv_name, params={})
8874   return drbd_dev
8875
8876
8877 _DISK_TEMPLATE_NAME_PREFIX = {
8878   constants.DT_PLAIN: "",
8879   constants.DT_RBD: ".rbd",
8880   }
8881
8882
8883 _DISK_TEMPLATE_DEVICE_TYPE = {
8884   constants.DT_PLAIN: constants.LD_LV,
8885   constants.DT_FILE: constants.LD_FILE,
8886   constants.DT_SHARED_FILE: constants.LD_FILE,
8887   constants.DT_BLOCK: constants.LD_BLOCKDEV,
8888   constants.DT_RBD: constants.LD_RBD,
8889   }
8890
8891
8892 def _GenerateDiskTemplate(
8893   lu, template_name, instance_name, primary_node, secondary_nodes,
8894   disk_info, file_storage_dir, file_driver, base_index,
8895   feedback_fn, full_disk_params, _req_file_storage=opcodes.RequireFileStorage,
8896   _req_shr_file_storage=opcodes.RequireSharedFileStorage):
8897   """Generate the entire disk layout for a given template type.
8898
8899   """
8900   #TODO: compute space requirements
8901
8902   vgname = lu.cfg.GetVGName()
8903   disk_count = len(disk_info)
8904   disks = []
8905
8906   if template_name == constants.DT_DISKLESS:
8907     pass
8908   elif template_name == constants.DT_DRBD8:
8909     if len(secondary_nodes) != 1:
8910       raise errors.ProgrammerError("Wrong template configuration")
8911     remote_node = secondary_nodes[0]
8912     minors = lu.cfg.AllocateDRBDMinor(
8913       [primary_node, remote_node] * len(disk_info), instance_name)
8914
8915     (drbd_params, _, _) = objects.Disk.ComputeLDParams(template_name,
8916                                                        full_disk_params)
8917     drbd_default_metavg = drbd_params[constants.LDP_DEFAULT_METAVG]
8918
8919     names = []
8920     for lv_prefix in _GenerateUniqueNames(lu, [".disk%d" % (base_index + i)
8921                                                for i in range(disk_count)]):
8922       names.append(lv_prefix + "_data")
8923       names.append(lv_prefix + "_meta")
8924     for idx, disk in enumerate(disk_info):
8925       disk_index = idx + base_index
8926       data_vg = disk.get(constants.IDISK_VG, vgname)
8927       meta_vg = disk.get(constants.IDISK_METAVG, drbd_default_metavg)
8928       disk_dev = _GenerateDRBD8Branch(lu, primary_node, remote_node,
8929                                       disk[constants.IDISK_SIZE],
8930                                       [data_vg, meta_vg],
8931                                       names[idx * 2:idx * 2 + 2],
8932                                       "disk/%d" % disk_index,
8933                                       minors[idx * 2], minors[idx * 2 + 1])
8934       disk_dev.mode = disk[constants.IDISK_MODE]
8935       disks.append(disk_dev)
8936   else:
8937     if secondary_nodes:
8938       raise errors.ProgrammerError("Wrong template configuration")
8939
8940     if template_name == constants.DT_FILE:
8941       _req_file_storage()
8942     elif template_name == constants.DT_SHARED_FILE:
8943       _req_shr_file_storage()
8944
8945     name_prefix = _DISK_TEMPLATE_NAME_PREFIX.get(template_name, None)
8946     if name_prefix is None:
8947       names = None
8948     else:
8949       names = _GenerateUniqueNames(lu, ["%s.disk%s" %
8950                                         (name_prefix, base_index + i)
8951                                         for i in range(disk_count)])
8952
8953     if template_name == constants.DT_PLAIN:
8954       def logical_id_fn(idx, _, disk):
8955         vg = disk.get(constants.IDISK_VG, vgname)
8956         return (vg, names[idx])
8957     elif template_name in (constants.DT_FILE, constants.DT_SHARED_FILE):
8958       logical_id_fn = \
8959         lambda _, disk_index, disk: (file_driver,
8960                                      "%s/disk%d" % (file_storage_dir,
8961                                                     disk_index))
8962     elif template_name == constants.DT_BLOCK:
8963       logical_id_fn = \
8964         lambda idx, disk_index, disk: (constants.BLOCKDEV_DRIVER_MANUAL,
8965                                        disk[constants.IDISK_ADOPT])
8966     elif template_name == constants.DT_RBD:
8967       logical_id_fn = lambda idx, _, disk: ("rbd", names[idx])
8968     else:
8969       raise errors.ProgrammerError("Unknown disk template '%s'" % template_name)
8970
8971     dev_type = _DISK_TEMPLATE_DEVICE_TYPE[template_name]
8972
8973     for idx, disk in enumerate(disk_info):
8974       disk_index = idx + base_index
8975       size = disk[constants.IDISK_SIZE]
8976       feedback_fn("* disk %s, size %s" %
8977                   (disk_index, utils.FormatUnit(size, "h")))
8978       disks.append(objects.Disk(dev_type=dev_type, size=size,
8979                                 logical_id=logical_id_fn(idx, disk_index, disk),
8980                                 iv_name="disk/%d" % disk_index,
8981                                 mode=disk[constants.IDISK_MODE],
8982                                 params={}))
8983
8984   return disks
8985
8986
8987 def _GetInstanceInfoText(instance):
8988   """Compute that text that should be added to the disk's metadata.
8989
8990   """
8991   return "originstname+%s" % instance.name
8992
8993
8994 def _CalcEta(time_taken, written, total_size):
8995   """Calculates the ETA based on size written and total size.
8996
8997   @param time_taken: The time taken so far
8998   @param written: amount written so far
8999   @param total_size: The total size of data to be written
9000   @return: The remaining time in seconds
9001
9002   """
9003   avg_time = time_taken / float(written)
9004   return (total_size - written) * avg_time
9005
9006
9007 def _WipeDisks(lu, instance, disks=None):
9008   """Wipes instance disks.
9009
9010   @type lu: L{LogicalUnit}
9011   @param lu: the logical unit on whose behalf we execute
9012   @type instance: L{objects.Instance}
9013   @param instance: the instance whose disks we should create
9014   @return: the success of the wipe
9015
9016   """
9017   node = instance.primary_node
9018
9019   if disks is None:
9020     disks = [(idx, disk, 0)
9021              for (idx, disk) in enumerate(instance.disks)]
9022
9023   for (_, device, _) in disks:
9024     lu.cfg.SetDiskID(device, node)
9025
9026   logging.info("Pausing synchronization of disks of instance '%s'",
9027                instance.name)
9028   result = lu.rpc.call_blockdev_pause_resume_sync(node,
9029                                                   (map(compat.snd, disks),
9030                                                    instance),
9031                                                   True)
9032   result.Raise("Failed to pause disk synchronization on node '%s'" % node)
9033
9034   for idx, success in enumerate(result.payload):
9035     if not success:
9036       logging.warn("Pausing synchronization of disk %s of instance '%s'"
9037                    " failed", idx, instance.name)
9038
9039   try:
9040     for (idx, device, offset) in disks:
9041       # The wipe size is MIN_WIPE_CHUNK_PERCENT % of the instance disk but
9042       # MAX_WIPE_CHUNK at max. Truncating to integer to avoid rounding errors.
9043       wipe_chunk_size = \
9044         int(min(constants.MAX_WIPE_CHUNK,
9045                 device.size / 100.0 * constants.MIN_WIPE_CHUNK_PERCENT))
9046
9047       size = device.size
9048       last_output = 0
9049       start_time = time.time()
9050
9051       if offset == 0:
9052         info_text = ""
9053       else:
9054         info_text = (" (from %s to %s)" %
9055                      (utils.FormatUnit(offset, "h"),
9056                       utils.FormatUnit(size, "h")))
9057
9058       lu.LogInfo("* Wiping disk %s%s", idx, info_text)
9059
9060       logging.info("Wiping disk %d for instance %s on node %s using"
9061                    " chunk size %s", idx, instance.name, node, wipe_chunk_size)
9062
9063       while offset < size:
9064         wipe_size = min(wipe_chunk_size, size - offset)
9065
9066         logging.debug("Wiping disk %d, offset %s, chunk %s",
9067                       idx, offset, wipe_size)
9068
9069         result = lu.rpc.call_blockdev_wipe(node, (device, instance), offset,
9070                                            wipe_size)
9071         result.Raise("Could not wipe disk %d at offset %d for size %d" %
9072                      (idx, offset, wipe_size))
9073
9074         now = time.time()
9075         offset += wipe_size
9076         if now - last_output >= 60:
9077           eta = _CalcEta(now - start_time, offset, size)
9078           lu.LogInfo(" - done: %.1f%% ETA: %s",
9079                      offset / float(size) * 100, utils.FormatSeconds(eta))
9080           last_output = now
9081   finally:
9082     logging.info("Resuming synchronization of disks for instance '%s'",
9083                  instance.name)
9084
9085     result = lu.rpc.call_blockdev_pause_resume_sync(node,
9086                                                     (map(compat.snd, disks),
9087                                                      instance),
9088                                                     False)
9089
9090     if result.fail_msg:
9091       lu.LogWarning("Failed to resume disk synchronization on node '%s': %s",
9092                     node, result.fail_msg)
9093     else:
9094       for idx, success in enumerate(result.payload):
9095         if not success:
9096           lu.LogWarning("Resuming synchronization of disk %s of instance '%s'"
9097                         " failed", idx, instance.name)
9098
9099
9100 def _CreateDisks(lu, instance, to_skip=None, target_node=None):
9101   """Create all disks for an instance.
9102
9103   This abstracts away some work from AddInstance.
9104
9105   @type lu: L{LogicalUnit}
9106   @param lu: the logical unit on whose behalf we execute
9107   @type instance: L{objects.Instance}
9108   @param instance: the instance whose disks we should create
9109   @type to_skip: list
9110   @param to_skip: list of indices to skip
9111   @type target_node: string
9112   @param target_node: if passed, overrides the target node for creation
9113   @rtype: boolean
9114   @return: the success of the creation
9115
9116   """
9117   info = _GetInstanceInfoText(instance)
9118   if target_node is None:
9119     pnode = instance.primary_node
9120     all_nodes = instance.all_nodes
9121   else:
9122     pnode = target_node
9123     all_nodes = [pnode]
9124
9125   if instance.disk_template in constants.DTS_FILEBASED:
9126     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
9127     result = lu.rpc.call_file_storage_dir_create(pnode, file_storage_dir)
9128
9129     result.Raise("Failed to create directory '%s' on"
9130                  " node %s" % (file_storage_dir, pnode))
9131
9132   # Note: this needs to be kept in sync with adding of disks in
9133   # LUInstanceSetParams
9134   for idx, device in enumerate(instance.disks):
9135     if to_skip and idx in to_skip:
9136       continue
9137     logging.info("Creating disk %s for instance '%s'", idx, instance.name)
9138     #HARDCODE
9139     for node in all_nodes:
9140       f_create = node == pnode
9141       _CreateBlockDev(lu, node, instance, device, f_create, info, f_create)
9142
9143
9144 def _RemoveDisks(lu, instance, target_node=None, ignore_failures=False):
9145   """Remove all disks for an instance.
9146
9147   This abstracts away some work from `AddInstance()` and
9148   `RemoveInstance()`. Note that in case some of the devices couldn't
9149   be removed, the removal will continue with the other ones (compare
9150   with `_CreateDisks()`).
9151
9152   @type lu: L{LogicalUnit}
9153   @param lu: the logical unit on whose behalf we execute
9154   @type instance: L{objects.Instance}
9155   @param instance: the instance whose disks we should remove
9156   @type target_node: string
9157   @param target_node: used to override the node on which to remove the disks
9158   @rtype: boolean
9159   @return: the success of the removal
9160
9161   """
9162   logging.info("Removing block devices for instance %s", instance.name)
9163
9164   all_result = True
9165   ports_to_release = set()
9166   anno_disks = _AnnotateDiskParams(instance, instance.disks, lu.cfg)
9167   for (idx, device) in enumerate(anno_disks):
9168     if target_node:
9169       edata = [(target_node, device)]
9170     else:
9171       edata = device.ComputeNodeTree(instance.primary_node)
9172     for node, disk in edata:
9173       lu.cfg.SetDiskID(disk, node)
9174       result = lu.rpc.call_blockdev_remove(node, disk)
9175       if result.fail_msg:
9176         lu.LogWarning("Could not remove disk %s on node %s,"
9177                       " continuing anyway: %s", idx, node, result.fail_msg)
9178         if not (result.offline and node != instance.primary_node):
9179           all_result = False
9180
9181     # if this is a DRBD disk, return its port to the pool
9182     if device.dev_type in constants.LDS_DRBD:
9183       ports_to_release.add(device.logical_id[2])
9184
9185   if all_result or ignore_failures:
9186     for port in ports_to_release:
9187       lu.cfg.AddTcpUdpPort(port)
9188
9189   if instance.disk_template == constants.DT_FILE:
9190     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
9191     if target_node:
9192       tgt = target_node
9193     else:
9194       tgt = instance.primary_node
9195     result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
9196     if result.fail_msg:
9197       lu.LogWarning("Could not remove directory '%s' on node %s: %s",
9198                     file_storage_dir, instance.primary_node, result.fail_msg)
9199       all_result = False
9200
9201   return all_result
9202
9203
9204 def _ComputeDiskSizePerVG(disk_template, disks):
9205   """Compute disk size requirements in the volume group
9206
9207   """
9208   def _compute(disks, payload):
9209     """Universal algorithm.
9210
9211     """
9212     vgs = {}
9213     for disk in disks:
9214       vgs[disk[constants.IDISK_VG]] = \
9215         vgs.get(constants.IDISK_VG, 0) + disk[constants.IDISK_SIZE] + payload
9216
9217     return vgs
9218
9219   # Required free disk space as a function of disk and swap space
9220   req_size_dict = {
9221     constants.DT_DISKLESS: {},
9222     constants.DT_PLAIN: _compute(disks, 0),
9223     # 128 MB are added for drbd metadata for each disk
9224     constants.DT_DRBD8: _compute(disks, constants.DRBD_META_SIZE),
9225     constants.DT_FILE: {},
9226     constants.DT_SHARED_FILE: {},
9227   }
9228
9229   if disk_template not in req_size_dict:
9230     raise errors.ProgrammerError("Disk template '%s' size requirement"
9231                                  " is unknown" % disk_template)
9232
9233   return req_size_dict[disk_template]
9234
9235
9236 def _FilterVmNodes(lu, nodenames):
9237   """Filters out non-vm_capable nodes from a list.
9238
9239   @type lu: L{LogicalUnit}
9240   @param lu: the logical unit for which we check
9241   @type nodenames: list
9242   @param nodenames: the list of nodes on which we should check
9243   @rtype: list
9244   @return: the list of vm-capable nodes
9245
9246   """
9247   vm_nodes = frozenset(lu.cfg.GetNonVmCapableNodeList())
9248   return [name for name in nodenames if name not in vm_nodes]
9249
9250
9251 def _CheckHVParams(lu, nodenames, hvname, hvparams):
9252   """Hypervisor parameter validation.
9253
9254   This function abstract the hypervisor parameter validation to be
9255   used in both instance create and instance modify.
9256
9257   @type lu: L{LogicalUnit}
9258   @param lu: the logical unit for which we check
9259   @type nodenames: list
9260   @param nodenames: the list of nodes on which we should check
9261   @type hvname: string
9262   @param hvname: the name of the hypervisor we should use
9263   @type hvparams: dict
9264   @param hvparams: the parameters which we need to check
9265   @raise errors.OpPrereqError: if the parameters are not valid
9266
9267   """
9268   nodenames = _FilterVmNodes(lu, nodenames)
9269
9270   cluster = lu.cfg.GetClusterInfo()
9271   hvfull = objects.FillDict(cluster.hvparams.get(hvname, {}), hvparams)
9272
9273   hvinfo = lu.rpc.call_hypervisor_validate_params(nodenames, hvname, hvfull)
9274   for node in nodenames:
9275     info = hvinfo[node]
9276     if info.offline:
9277       continue
9278     info.Raise("Hypervisor parameter validation failed on node %s" % node)
9279
9280
9281 def _CheckOSParams(lu, required, nodenames, osname, osparams):
9282   """OS parameters validation.
9283
9284   @type lu: L{LogicalUnit}
9285   @param lu: the logical unit for which we check
9286   @type required: boolean
9287   @param required: whether the validation should fail if the OS is not
9288       found
9289   @type nodenames: list
9290   @param nodenames: the list of nodes on which we should check
9291   @type osname: string
9292   @param osname: the name of the hypervisor we should use
9293   @type osparams: dict
9294   @param osparams: the parameters which we need to check
9295   @raise errors.OpPrereqError: if the parameters are not valid
9296
9297   """
9298   nodenames = _FilterVmNodes(lu, nodenames)
9299   result = lu.rpc.call_os_validate(nodenames, required, osname,
9300                                    [constants.OS_VALIDATE_PARAMETERS],
9301                                    osparams)
9302   for node, nres in result.items():
9303     # we don't check for offline cases since this should be run only
9304     # against the master node and/or an instance's nodes
9305     nres.Raise("OS Parameters validation failed on node %s" % node)
9306     if not nres.payload:
9307       lu.LogInfo("OS %s not found on node %s, validation skipped",
9308                  osname, node)
9309
9310
9311 def _CreateInstanceAllocRequest(op, disks, nics, beparams):
9312   """Wrapper around IAReqInstanceAlloc.
9313
9314   @param op: The instance opcode
9315   @param disks: The computed disks
9316   @param nics: The computed nics
9317   @param beparams: The full filled beparams
9318
9319   @returns: A filled L{iallocator.IAReqInstanceAlloc}
9320
9321   """
9322   spindle_use = beparams[constants.BE_SPINDLE_USE]
9323   return iallocator.IAReqInstanceAlloc(name=op.instance_name,
9324                                        disk_template=op.disk_template,
9325                                        tags=op.tags,
9326                                        os=op.os_type,
9327                                        vcpus=beparams[constants.BE_VCPUS],
9328                                        memory=beparams[constants.BE_MAXMEM],
9329                                        spindle_use=spindle_use,
9330                                        disks=disks,
9331                                        nics=[n.ToDict() for n in nics],
9332                                        hypervisor=op.hypervisor)
9333
9334
9335 def _ComputeNics(op, cluster, default_ip, cfg, proc):
9336   """Computes the nics.
9337
9338   @param op: The instance opcode
9339   @param cluster: Cluster configuration object
9340   @param default_ip: The default ip to assign
9341   @param cfg: An instance of the configuration object
9342   @param proc: The executer instance
9343
9344   @returns: The build up nics
9345
9346   """
9347   nics = []
9348   for idx, nic in enumerate(op.nics):
9349     nic_mode_req = nic.get(constants.INIC_MODE, None)
9350     nic_mode = nic_mode_req
9351     if nic_mode is None or nic_mode == constants.VALUE_AUTO:
9352       nic_mode = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_MODE]
9353
9354     # in routed mode, for the first nic, the default ip is 'auto'
9355     if nic_mode == constants.NIC_MODE_ROUTED and idx == 0:
9356       default_ip_mode = constants.VALUE_AUTO
9357     else:
9358       default_ip_mode = constants.VALUE_NONE
9359
9360     # ip validity checks
9361     ip = nic.get(constants.INIC_IP, default_ip_mode)
9362     if ip is None or ip.lower() == constants.VALUE_NONE:
9363       nic_ip = None
9364     elif ip.lower() == constants.VALUE_AUTO:
9365       if not op.name_check:
9366         raise errors.OpPrereqError("IP address set to auto but name checks"
9367                                    " have been skipped",
9368                                    errors.ECODE_INVAL)
9369       nic_ip = default_ip
9370     else:
9371       if not netutils.IPAddress.IsValid(ip):
9372         raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
9373                                    errors.ECODE_INVAL)
9374       nic_ip = ip
9375
9376     # TODO: check the ip address for uniqueness
9377     if nic_mode == constants.NIC_MODE_ROUTED and not nic_ip:
9378       raise errors.OpPrereqError("Routed nic mode requires an ip address",
9379                                  errors.ECODE_INVAL)
9380
9381     # MAC address verification
9382     mac = nic.get(constants.INIC_MAC, constants.VALUE_AUTO)
9383     if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
9384       mac = utils.NormalizeAndValidateMac(mac)
9385
9386       try:
9387         # TODO: We need to factor this out
9388         cfg.ReserveMAC(mac, proc.GetECId())
9389       except errors.ReservationError:
9390         raise errors.OpPrereqError("MAC address %s already in use"
9391                                    " in cluster" % mac,
9392                                    errors.ECODE_NOTUNIQUE)
9393
9394     #  Build nic parameters
9395     link = nic.get(constants.INIC_LINK, None)
9396     if link == constants.VALUE_AUTO:
9397       link = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_LINK]
9398     nicparams = {}
9399     if nic_mode_req:
9400       nicparams[constants.NIC_MODE] = nic_mode
9401     if link:
9402       nicparams[constants.NIC_LINK] = link
9403
9404     check_params = cluster.SimpleFillNIC(nicparams)
9405     objects.NIC.CheckParameterSyntax(check_params)
9406     nics.append(objects.NIC(mac=mac, ip=nic_ip, nicparams=nicparams))
9407
9408   return nics
9409
9410
9411 def _ComputeDisks(op, default_vg):
9412   """Computes the instance disks.
9413
9414   @param op: The instance opcode
9415   @param default_vg: The default_vg to assume
9416
9417   @return: The computer disks
9418
9419   """
9420   disks = []
9421   for disk in op.disks:
9422     mode = disk.get(constants.IDISK_MODE, constants.DISK_RDWR)
9423     if mode not in constants.DISK_ACCESS_SET:
9424       raise errors.OpPrereqError("Invalid disk access mode '%s'" %
9425                                  mode, errors.ECODE_INVAL)
9426     size = disk.get(constants.IDISK_SIZE, None)
9427     if size is None:
9428       raise errors.OpPrereqError("Missing disk size", errors.ECODE_INVAL)
9429     try:
9430       size = int(size)
9431     except (TypeError, ValueError):
9432       raise errors.OpPrereqError("Invalid disk size '%s'" % size,
9433                                  errors.ECODE_INVAL)
9434
9435     data_vg = disk.get(constants.IDISK_VG, default_vg)
9436     new_disk = {
9437       constants.IDISK_SIZE: size,
9438       constants.IDISK_MODE: mode,
9439       constants.IDISK_VG: data_vg,
9440       }
9441     if constants.IDISK_METAVG in disk:
9442       new_disk[constants.IDISK_METAVG] = disk[constants.IDISK_METAVG]
9443     if constants.IDISK_ADOPT in disk:
9444       new_disk[constants.IDISK_ADOPT] = disk[constants.IDISK_ADOPT]
9445     disks.append(new_disk)
9446
9447   return disks
9448
9449
9450 def _ComputeFullBeParams(op, cluster):
9451   """Computes the full beparams.
9452
9453   @param op: The instance opcode
9454   @param cluster: The cluster config object
9455
9456   @return: The fully filled beparams
9457
9458   """
9459   default_beparams = cluster.beparams[constants.PP_DEFAULT]
9460   for param, value in op.beparams.iteritems():
9461     if value == constants.VALUE_AUTO:
9462       op.beparams[param] = default_beparams[param]
9463   objects.UpgradeBeParams(op.beparams)
9464   utils.ForceDictType(op.beparams, constants.BES_PARAMETER_TYPES)
9465   return cluster.SimpleFillBE(op.beparams)
9466
9467
9468 class LUInstanceCreate(LogicalUnit):
9469   """Create an instance.
9470
9471   """
9472   HPATH = "instance-add"
9473   HTYPE = constants.HTYPE_INSTANCE
9474   REQ_BGL = False
9475
9476   def CheckArguments(self):
9477     """Check arguments.
9478
9479     """
9480     # do not require name_check to ease forward/backward compatibility
9481     # for tools
9482     if self.op.no_install and self.op.start:
9483       self.LogInfo("No-installation mode selected, disabling startup")
9484       self.op.start = False
9485     # validate/normalize the instance name
9486     self.op.instance_name = \
9487       netutils.Hostname.GetNormalizedName(self.op.instance_name)
9488
9489     if self.op.ip_check and not self.op.name_check:
9490       # TODO: make the ip check more flexible and not depend on the name check
9491       raise errors.OpPrereqError("Cannot do IP address check without a name"
9492                                  " check", errors.ECODE_INVAL)
9493
9494     # check nics' parameter names
9495     for nic in self.op.nics:
9496       utils.ForceDictType(nic, constants.INIC_PARAMS_TYPES)
9497
9498     # check disks. parameter names and consistent adopt/no-adopt strategy
9499     has_adopt = has_no_adopt = False
9500     for disk in self.op.disks:
9501       utils.ForceDictType(disk, constants.IDISK_PARAMS_TYPES)
9502       if constants.IDISK_ADOPT in disk:
9503         has_adopt = True
9504       else:
9505         has_no_adopt = True
9506     if has_adopt and has_no_adopt:
9507       raise errors.OpPrereqError("Either all disks are adopted or none is",
9508                                  errors.ECODE_INVAL)
9509     if has_adopt:
9510       if self.op.disk_template not in constants.DTS_MAY_ADOPT:
9511         raise errors.OpPrereqError("Disk adoption is not supported for the"
9512                                    " '%s' disk template" %
9513                                    self.op.disk_template,
9514                                    errors.ECODE_INVAL)
9515       if self.op.iallocator is not None:
9516         raise errors.OpPrereqError("Disk adoption not allowed with an"
9517                                    " iallocator script", errors.ECODE_INVAL)
9518       if self.op.mode == constants.INSTANCE_IMPORT:
9519         raise errors.OpPrereqError("Disk adoption not allowed for"
9520                                    " instance import", errors.ECODE_INVAL)
9521     else:
9522       if self.op.disk_template in constants.DTS_MUST_ADOPT:
9523         raise errors.OpPrereqError("Disk template %s requires disk adoption,"
9524                                    " but no 'adopt' parameter given" %
9525                                    self.op.disk_template,
9526                                    errors.ECODE_INVAL)
9527
9528     self.adopt_disks = has_adopt
9529
9530     # instance name verification
9531     if self.op.name_check:
9532       self.hostname1 = netutils.GetHostname(name=self.op.instance_name)
9533       self.op.instance_name = self.hostname1.name
9534       # used in CheckPrereq for ip ping check
9535       self.check_ip = self.hostname1.ip
9536     else:
9537       self.check_ip = None
9538
9539     # file storage checks
9540     if (self.op.file_driver and
9541         not self.op.file_driver in constants.FILE_DRIVER):
9542       raise errors.OpPrereqError("Invalid file driver name '%s'" %
9543                                  self.op.file_driver, errors.ECODE_INVAL)
9544
9545     if self.op.disk_template == constants.DT_FILE:
9546       opcodes.RequireFileStorage()
9547     elif self.op.disk_template == constants.DT_SHARED_FILE:
9548       opcodes.RequireSharedFileStorage()
9549
9550     ### Node/iallocator related checks
9551     _CheckIAllocatorOrNode(self, "iallocator", "pnode")
9552
9553     if self.op.pnode is not None:
9554       if self.op.disk_template in constants.DTS_INT_MIRROR:
9555         if self.op.snode is None:
9556           raise errors.OpPrereqError("The networked disk templates need"
9557                                      " a mirror node", errors.ECODE_INVAL)
9558       elif self.op.snode:
9559         self.LogWarning("Secondary node will be ignored on non-mirrored disk"
9560                         " template")
9561         self.op.snode = None
9562
9563     self._cds = _GetClusterDomainSecret()
9564
9565     if self.op.mode == constants.INSTANCE_IMPORT:
9566       # On import force_variant must be True, because if we forced it at
9567       # initial install, our only chance when importing it back is that it
9568       # works again!
9569       self.op.force_variant = True
9570
9571       if self.op.no_install:
9572         self.LogInfo("No-installation mode has no effect during import")
9573
9574     elif self.op.mode == constants.INSTANCE_CREATE:
9575       if self.op.os_type is None:
9576         raise errors.OpPrereqError("No guest OS specified",
9577                                    errors.ECODE_INVAL)
9578       if self.op.os_type in self.cfg.GetClusterInfo().blacklisted_os:
9579         raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
9580                                    " installation" % self.op.os_type,
9581                                    errors.ECODE_STATE)
9582       if self.op.disk_template is None:
9583         raise errors.OpPrereqError("No disk template specified",
9584                                    errors.ECODE_INVAL)
9585
9586     elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
9587       # Check handshake to ensure both clusters have the same domain secret
9588       src_handshake = self.op.source_handshake
9589       if not src_handshake:
9590         raise errors.OpPrereqError("Missing source handshake",
9591                                    errors.ECODE_INVAL)
9592
9593       errmsg = masterd.instance.CheckRemoteExportHandshake(self._cds,
9594                                                            src_handshake)
9595       if errmsg:
9596         raise errors.OpPrereqError("Invalid handshake: %s" % errmsg,
9597                                    errors.ECODE_INVAL)
9598
9599       # Load and check source CA
9600       self.source_x509_ca_pem = self.op.source_x509_ca
9601       if not self.source_x509_ca_pem:
9602         raise errors.OpPrereqError("Missing source X509 CA",
9603                                    errors.ECODE_INVAL)
9604
9605       try:
9606         (cert, _) = utils.LoadSignedX509Certificate(self.source_x509_ca_pem,
9607                                                     self._cds)
9608       except OpenSSL.crypto.Error, err:
9609         raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
9610                                    (err, ), errors.ECODE_INVAL)
9611
9612       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
9613       if errcode is not None:
9614         raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
9615                                    errors.ECODE_INVAL)
9616
9617       self.source_x509_ca = cert
9618
9619       src_instance_name = self.op.source_instance_name
9620       if not src_instance_name:
9621         raise errors.OpPrereqError("Missing source instance name",
9622                                    errors.ECODE_INVAL)
9623
9624       self.source_instance_name = \
9625           netutils.GetHostname(name=src_instance_name).name
9626
9627     else:
9628       raise errors.OpPrereqError("Invalid instance creation mode %r" %
9629                                  self.op.mode, errors.ECODE_INVAL)
9630
9631   def ExpandNames(self):
9632     """ExpandNames for CreateInstance.
9633
9634     Figure out the right locks for instance creation.
9635
9636     """
9637     self.needed_locks = {}
9638
9639     instance_name = self.op.instance_name
9640     # this is just a preventive check, but someone might still add this
9641     # instance in the meantime, and creation will fail at lock-add time
9642     if instance_name in self.cfg.GetInstanceList():
9643       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
9644                                  instance_name, errors.ECODE_EXISTS)
9645
9646     self.add_locks[locking.LEVEL_INSTANCE] = instance_name
9647
9648     if self.op.iallocator:
9649       # TODO: Find a solution to not lock all nodes in the cluster, e.g. by
9650       # specifying a group on instance creation and then selecting nodes from
9651       # that group
9652       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9653       self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
9654     else:
9655       self.op.pnode = _ExpandNodeName(self.cfg, self.op.pnode)
9656       nodelist = [self.op.pnode]
9657       if self.op.snode is not None:
9658         self.op.snode = _ExpandNodeName(self.cfg, self.op.snode)
9659         nodelist.append(self.op.snode)
9660       self.needed_locks[locking.LEVEL_NODE] = nodelist
9661       # Lock resources of instance's primary and secondary nodes (copy to
9662       # prevent accidential modification)
9663       self.needed_locks[locking.LEVEL_NODE_RES] = list(nodelist)
9664
9665     # in case of import lock the source node too
9666     if self.op.mode == constants.INSTANCE_IMPORT:
9667       src_node = self.op.src_node
9668       src_path = self.op.src_path
9669
9670       if src_path is None:
9671         self.op.src_path = src_path = self.op.instance_name
9672
9673       if src_node is None:
9674         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9675         self.op.src_node = None
9676         if os.path.isabs(src_path):
9677           raise errors.OpPrereqError("Importing an instance from a path"
9678                                      " requires a source node option",
9679                                      errors.ECODE_INVAL)
9680       else:
9681         self.op.src_node = src_node = _ExpandNodeName(self.cfg, src_node)
9682         if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
9683           self.needed_locks[locking.LEVEL_NODE].append(src_node)
9684         if not os.path.isabs(src_path):
9685           self.op.src_path = src_path = \
9686             utils.PathJoin(pathutils.EXPORT_DIR, src_path)
9687
9688   def _RunAllocator(self):
9689     """Run the allocator based on input opcode.
9690
9691     """
9692     req = _CreateInstanceAllocRequest(self.op, self.disks,
9693                                       self.nics, self.be_full)
9694     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
9695
9696     ial.Run(self.op.iallocator)
9697
9698     if not ial.success:
9699       raise errors.OpPrereqError("Can't compute nodes using"
9700                                  " iallocator '%s': %s" %
9701                                  (self.op.iallocator, ial.info),
9702                                  errors.ECODE_NORES)
9703     self.op.pnode = ial.result[0]
9704     self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
9705                  self.op.instance_name, self.op.iallocator,
9706                  utils.CommaJoin(ial.result))
9707
9708     assert req.RequiredNodes() in (1, 2), "Wrong node count from iallocator"
9709
9710     if req.RequiredNodes() == 2:
9711       self.op.snode = ial.result[1]
9712
9713   def BuildHooksEnv(self):
9714     """Build hooks env.
9715
9716     This runs on master, primary and secondary nodes of the instance.
9717
9718     """
9719     env = {
9720       "ADD_MODE": self.op.mode,
9721       }
9722     if self.op.mode == constants.INSTANCE_IMPORT:
9723       env["SRC_NODE"] = self.op.src_node
9724       env["SRC_PATH"] = self.op.src_path
9725       env["SRC_IMAGES"] = self.src_images
9726
9727     env.update(_BuildInstanceHookEnv(
9728       name=self.op.instance_name,
9729       primary_node=self.op.pnode,
9730       secondary_nodes=self.secondaries,
9731       status=self.op.start,
9732       os_type=self.op.os_type,
9733       minmem=self.be_full[constants.BE_MINMEM],
9734       maxmem=self.be_full[constants.BE_MAXMEM],
9735       vcpus=self.be_full[constants.BE_VCPUS],
9736       nics=_NICListToTuple(self, self.nics),
9737       disk_template=self.op.disk_template,
9738       disks=[(d[constants.IDISK_SIZE], d[constants.IDISK_MODE])
9739              for d in self.disks],
9740       bep=self.be_full,
9741       hvp=self.hv_full,
9742       hypervisor_name=self.op.hypervisor,
9743       tags=self.op.tags,
9744     ))
9745
9746     return env
9747
9748   def BuildHooksNodes(self):
9749     """Build hooks nodes.
9750
9751     """
9752     nl = [self.cfg.GetMasterNode(), self.op.pnode] + self.secondaries
9753     return nl, nl
9754
9755   def _ReadExportInfo(self):
9756     """Reads the export information from disk.
9757
9758     It will override the opcode source node and path with the actual
9759     information, if these two were not specified before.
9760
9761     @return: the export information
9762
9763     """
9764     assert self.op.mode == constants.INSTANCE_IMPORT
9765
9766     src_node = self.op.src_node
9767     src_path = self.op.src_path
9768
9769     if src_node is None:
9770       locked_nodes = self.owned_locks(locking.LEVEL_NODE)
9771       exp_list = self.rpc.call_export_list(locked_nodes)
9772       found = False
9773       for node in exp_list:
9774         if exp_list[node].fail_msg:
9775           continue
9776         if src_path in exp_list[node].payload:
9777           found = True
9778           self.op.src_node = src_node = node
9779           self.op.src_path = src_path = utils.PathJoin(pathutils.EXPORT_DIR,
9780                                                        src_path)
9781           break
9782       if not found:
9783         raise errors.OpPrereqError("No export found for relative path %s" %
9784                                     src_path, errors.ECODE_INVAL)
9785
9786     _CheckNodeOnline(self, src_node)
9787     result = self.rpc.call_export_info(src_node, src_path)
9788     result.Raise("No export or invalid export found in dir %s" % src_path)
9789
9790     export_info = objects.SerializableConfigParser.Loads(str(result.payload))
9791     if not export_info.has_section(constants.INISECT_EXP):
9792       raise errors.ProgrammerError("Corrupted export config",
9793                                    errors.ECODE_ENVIRON)
9794
9795     ei_version = export_info.get(constants.INISECT_EXP, "version")
9796     if (int(ei_version) != constants.EXPORT_VERSION):
9797       raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
9798                                  (ei_version, constants.EXPORT_VERSION),
9799                                  errors.ECODE_ENVIRON)
9800     return export_info
9801
9802   def _ReadExportParams(self, einfo):
9803     """Use export parameters as defaults.
9804
9805     In case the opcode doesn't specify (as in override) some instance
9806     parameters, then try to use them from the export information, if
9807     that declares them.
9808
9809     """
9810     self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
9811
9812     if self.op.disk_template is None:
9813       if einfo.has_option(constants.INISECT_INS, "disk_template"):
9814         self.op.disk_template = einfo.get(constants.INISECT_INS,
9815                                           "disk_template")
9816         if self.op.disk_template not in constants.DISK_TEMPLATES:
9817           raise errors.OpPrereqError("Disk template specified in configuration"
9818                                      " file is not one of the allowed values:"
9819                                      " %s" %
9820                                      " ".join(constants.DISK_TEMPLATES),
9821                                      errors.ECODE_INVAL)
9822       else:
9823         raise errors.OpPrereqError("No disk template specified and the export"
9824                                    " is missing the disk_template information",
9825                                    errors.ECODE_INVAL)
9826
9827     if not self.op.disks:
9828       disks = []
9829       # TODO: import the disk iv_name too
9830       for idx in range(constants.MAX_DISKS):
9831         if einfo.has_option(constants.INISECT_INS, "disk%d_size" % idx):
9832           disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
9833           disks.append({constants.IDISK_SIZE: disk_sz})
9834       self.op.disks = disks
9835       if not disks and self.op.disk_template != constants.DT_DISKLESS:
9836         raise errors.OpPrereqError("No disk info specified and the export"
9837                                    " is missing the disk information",
9838                                    errors.ECODE_INVAL)
9839
9840     if not self.op.nics:
9841       nics = []
9842       for idx in range(constants.MAX_NICS):
9843         if einfo.has_option(constants.INISECT_INS, "nic%d_mac" % idx):
9844           ndict = {}
9845           for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
9846             v = einfo.get(constants.INISECT_INS, "nic%d_%s" % (idx, name))
9847             ndict[name] = v
9848           nics.append(ndict)
9849         else:
9850           break
9851       self.op.nics = nics
9852
9853     if not self.op.tags and einfo.has_option(constants.INISECT_INS, "tags"):
9854       self.op.tags = einfo.get(constants.INISECT_INS, "tags").split()
9855
9856     if (self.op.hypervisor is None and
9857         einfo.has_option(constants.INISECT_INS, "hypervisor")):
9858       self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
9859
9860     if einfo.has_section(constants.INISECT_HYP):
9861       # use the export parameters but do not override the ones
9862       # specified by the user
9863       for name, value in einfo.items(constants.INISECT_HYP):
9864         if name not in self.op.hvparams:
9865           self.op.hvparams[name] = value
9866
9867     if einfo.has_section(constants.INISECT_BEP):
9868       # use the parameters, without overriding
9869       for name, value in einfo.items(constants.INISECT_BEP):
9870         if name not in self.op.beparams:
9871           self.op.beparams[name] = value
9872         # Compatibility for the old "memory" be param
9873         if name == constants.BE_MEMORY:
9874           if constants.BE_MAXMEM not in self.op.beparams:
9875             self.op.beparams[constants.BE_MAXMEM] = value
9876           if constants.BE_MINMEM not in self.op.beparams:
9877             self.op.beparams[constants.BE_MINMEM] = value
9878     else:
9879       # try to read the parameters old style, from the main section
9880       for name in constants.BES_PARAMETERS:
9881         if (name not in self.op.beparams and
9882             einfo.has_option(constants.INISECT_INS, name)):
9883           self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
9884
9885     if einfo.has_section(constants.INISECT_OSP):
9886       # use the parameters, without overriding
9887       for name, value in einfo.items(constants.INISECT_OSP):
9888         if name not in self.op.osparams:
9889           self.op.osparams[name] = value
9890
9891   def _RevertToDefaults(self, cluster):
9892     """Revert the instance parameters to the default values.
9893
9894     """
9895     # hvparams
9896     hv_defs = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type, {})
9897     for name in self.op.hvparams.keys():
9898       if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
9899         del self.op.hvparams[name]
9900     # beparams
9901     be_defs = cluster.SimpleFillBE({})
9902     for name in self.op.beparams.keys():
9903       if name in be_defs and be_defs[name] == self.op.beparams[name]:
9904         del self.op.beparams[name]
9905     # nic params
9906     nic_defs = cluster.SimpleFillNIC({})
9907     for nic in self.op.nics:
9908       for name in constants.NICS_PARAMETERS:
9909         if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
9910           del nic[name]
9911     # osparams
9912     os_defs = cluster.SimpleFillOS(self.op.os_type, {})
9913     for name in self.op.osparams.keys():
9914       if name in os_defs and os_defs[name] == self.op.osparams[name]:
9915         del self.op.osparams[name]
9916
9917   def _CalculateFileStorageDir(self):
9918     """Calculate final instance file storage dir.
9919
9920     """
9921     # file storage dir calculation/check
9922     self.instance_file_storage_dir = None
9923     if self.op.disk_template in constants.DTS_FILEBASED:
9924       # build the full file storage dir path
9925       joinargs = []
9926
9927       if self.op.disk_template == constants.DT_SHARED_FILE:
9928         get_fsd_fn = self.cfg.GetSharedFileStorageDir
9929       else:
9930         get_fsd_fn = self.cfg.GetFileStorageDir
9931
9932       cfg_storagedir = get_fsd_fn()
9933       if not cfg_storagedir:
9934         raise errors.OpPrereqError("Cluster file storage dir not defined",
9935                                    errors.ECODE_STATE)
9936       joinargs.append(cfg_storagedir)
9937
9938       if self.op.file_storage_dir is not None:
9939         joinargs.append(self.op.file_storage_dir)
9940
9941       joinargs.append(self.op.instance_name)
9942
9943       # pylint: disable=W0142
9944       self.instance_file_storage_dir = utils.PathJoin(*joinargs)
9945
9946   def CheckPrereq(self): # pylint: disable=R0914
9947     """Check prerequisites.
9948
9949     """
9950     self._CalculateFileStorageDir()
9951
9952     if self.op.mode == constants.INSTANCE_IMPORT:
9953       export_info = self._ReadExportInfo()
9954       self._ReadExportParams(export_info)
9955       self._old_instance_name = export_info.get(constants.INISECT_INS, "name")
9956     else:
9957       self._old_instance_name = None
9958
9959     if (not self.cfg.GetVGName() and
9960         self.op.disk_template not in constants.DTS_NOT_LVM):
9961       raise errors.OpPrereqError("Cluster does not support lvm-based"
9962                                  " instances", errors.ECODE_STATE)
9963
9964     if (self.op.hypervisor is None or
9965         self.op.hypervisor == constants.VALUE_AUTO):
9966       self.op.hypervisor = self.cfg.GetHypervisorType()
9967
9968     cluster = self.cfg.GetClusterInfo()
9969     enabled_hvs = cluster.enabled_hypervisors
9970     if self.op.hypervisor not in enabled_hvs:
9971       raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
9972                                  " cluster (%s)" %
9973                                  (self.op.hypervisor, ",".join(enabled_hvs)),
9974                                  errors.ECODE_STATE)
9975
9976     # Check tag validity
9977     for tag in self.op.tags:
9978       objects.TaggableObject.ValidateTag(tag)
9979
9980     # check hypervisor parameter syntax (locally)
9981     utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
9982     filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type,
9983                                       self.op.hvparams)
9984     hv_type = hypervisor.GetHypervisor(self.op.hypervisor)
9985     hv_type.CheckParameterSyntax(filled_hvp)
9986     self.hv_full = filled_hvp
9987     # check that we don't specify global parameters on an instance
9988     _CheckGlobalHvParams(self.op.hvparams)
9989
9990     # fill and remember the beparams dict
9991     self.be_full = _ComputeFullBeParams(self.op, cluster)
9992
9993     # build os parameters
9994     self.os_full = cluster.SimpleFillOS(self.op.os_type, self.op.osparams)
9995
9996     # now that hvp/bep are in final format, let's reset to defaults,
9997     # if told to do so
9998     if self.op.identify_defaults:
9999       self._RevertToDefaults(cluster)
10000
10001     # NIC buildup
10002     self.nics = _ComputeNics(self.op, cluster, self.hostname1.ip, self.cfg,
10003                              self.proc)
10004
10005     # disk checks/pre-build
10006     default_vg = self.cfg.GetVGName()
10007     self.disks = _ComputeDisks(self.op, default_vg)
10008
10009     if self.op.mode == constants.INSTANCE_IMPORT:
10010       disk_images = []
10011       for idx in range(len(self.disks)):
10012         option = "disk%d_dump" % idx
10013         if export_info.has_option(constants.INISECT_INS, option):
10014           # FIXME: are the old os-es, disk sizes, etc. useful?
10015           export_name = export_info.get(constants.INISECT_INS, option)
10016           image = utils.PathJoin(self.op.src_path, export_name)
10017           disk_images.append(image)
10018         else:
10019           disk_images.append(False)
10020
10021       self.src_images = disk_images
10022
10023       if self.op.instance_name == self._old_instance_name:
10024         for idx, nic in enumerate(self.nics):
10025           if nic.mac == constants.VALUE_AUTO:
10026             nic_mac_ini = "nic%d_mac" % idx
10027             nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
10028
10029     # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
10030
10031     # ip ping checks (we use the same ip that was resolved in ExpandNames)
10032     if self.op.ip_check:
10033       if netutils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
10034         raise errors.OpPrereqError("IP %s of instance %s already in use" %
10035                                    (self.check_ip, self.op.instance_name),
10036                                    errors.ECODE_NOTUNIQUE)
10037
10038     #### mac address generation
10039     # By generating here the mac address both the allocator and the hooks get
10040     # the real final mac address rather than the 'auto' or 'generate' value.
10041     # There is a race condition between the generation and the instance object
10042     # creation, which means that we know the mac is valid now, but we're not
10043     # sure it will be when we actually add the instance. If things go bad
10044     # adding the instance will abort because of a duplicate mac, and the
10045     # creation job will fail.
10046     for nic in self.nics:
10047       if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
10048         nic.mac = self.cfg.GenerateMAC(self.proc.GetECId())
10049
10050     #### allocator run
10051
10052     if self.op.iallocator is not None:
10053       self._RunAllocator()
10054
10055     # Release all unneeded node locks
10056     _ReleaseLocks(self, locking.LEVEL_NODE,
10057                   keep=filter(None, [self.op.pnode, self.op.snode,
10058                                      self.op.src_node]))
10059     _ReleaseLocks(self, locking.LEVEL_NODE_RES,
10060                   keep=filter(None, [self.op.pnode, self.op.snode,
10061                                      self.op.src_node]))
10062
10063     #### node related checks
10064
10065     # check primary node
10066     self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode)
10067     assert self.pnode is not None, \
10068       "Cannot retrieve locked node %s" % self.op.pnode
10069     if pnode.offline:
10070       raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
10071                                  pnode.name, errors.ECODE_STATE)
10072     if pnode.drained:
10073       raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
10074                                  pnode.name, errors.ECODE_STATE)
10075     if not pnode.vm_capable:
10076       raise errors.OpPrereqError("Cannot use non-vm_capable primary node"
10077                                  " '%s'" % pnode.name, errors.ECODE_STATE)
10078
10079     self.secondaries = []
10080
10081     # mirror node verification
10082     if self.op.disk_template in constants.DTS_INT_MIRROR:
10083       if self.op.snode == pnode.name:
10084         raise errors.OpPrereqError("The secondary node cannot be the"
10085                                    " primary node", errors.ECODE_INVAL)
10086       _CheckNodeOnline(self, self.op.snode)
10087       _CheckNodeNotDrained(self, self.op.snode)
10088       _CheckNodeVmCapable(self, self.op.snode)
10089       self.secondaries.append(self.op.snode)
10090
10091       snode = self.cfg.GetNodeInfo(self.op.snode)
10092       if pnode.group != snode.group:
10093         self.LogWarning("The primary and secondary nodes are in two"
10094                         " different node groups; the disk parameters"
10095                         " from the first disk's node group will be"
10096                         " used")
10097
10098     nodenames = [pnode.name] + self.secondaries
10099
10100     # Verify instance specs
10101     spindle_use = self.be_full.get(constants.BE_SPINDLE_USE, None)
10102     ispec = {
10103       constants.ISPEC_MEM_SIZE: self.be_full.get(constants.BE_MAXMEM, None),
10104       constants.ISPEC_CPU_COUNT: self.be_full.get(constants.BE_VCPUS, None),
10105       constants.ISPEC_DISK_COUNT: len(self.disks),
10106       constants.ISPEC_DISK_SIZE: [disk["size"] for disk in self.disks],
10107       constants.ISPEC_NIC_COUNT: len(self.nics),
10108       constants.ISPEC_SPINDLE_USE: spindle_use,
10109       }
10110
10111     group_info = self.cfg.GetNodeGroup(pnode.group)
10112     ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
10113     res = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec)
10114     if not self.op.ignore_ipolicy and res:
10115       msg = ("Instance allocation to group %s (%s) violates policy: %s" %
10116              (pnode.group, group_info.name, utils.CommaJoin(res)))
10117       raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
10118
10119     if not self.adopt_disks:
10120       if self.op.disk_template == constants.DT_RBD:
10121         # _CheckRADOSFreeSpace() is just a placeholder.
10122         # Any function that checks prerequisites can be placed here.
10123         # Check if there is enough space on the RADOS cluster.
10124         _CheckRADOSFreeSpace()
10125       else:
10126         # Check lv size requirements, if not adopting
10127         req_sizes = _ComputeDiskSizePerVG(self.op.disk_template, self.disks)
10128         _CheckNodesFreeDiskPerVG(self, nodenames, req_sizes)
10129
10130     elif self.op.disk_template == constants.DT_PLAIN: # Check the adoption data
10131       all_lvs = set(["%s/%s" % (disk[constants.IDISK_VG],
10132                                 disk[constants.IDISK_ADOPT])
10133                      for disk in self.disks])
10134       if len(all_lvs) != len(self.disks):
10135         raise errors.OpPrereqError("Duplicate volume names given for adoption",
10136                                    errors.ECODE_INVAL)
10137       for lv_name in all_lvs:
10138         try:
10139           # FIXME: lv_name here is "vg/lv" need to ensure that other calls
10140           # to ReserveLV uses the same syntax
10141           self.cfg.ReserveLV(lv_name, self.proc.GetECId())
10142         except errors.ReservationError:
10143           raise errors.OpPrereqError("LV named %s used by another instance" %
10144                                      lv_name, errors.ECODE_NOTUNIQUE)
10145
10146       vg_names = self.rpc.call_vg_list([pnode.name])[pnode.name]
10147       vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
10148
10149       node_lvs = self.rpc.call_lv_list([pnode.name],
10150                                        vg_names.payload.keys())[pnode.name]
10151       node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
10152       node_lvs = node_lvs.payload
10153
10154       delta = all_lvs.difference(node_lvs.keys())
10155       if delta:
10156         raise errors.OpPrereqError("Missing logical volume(s): %s" %
10157                                    utils.CommaJoin(delta),
10158                                    errors.ECODE_INVAL)
10159       online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
10160       if online_lvs:
10161         raise errors.OpPrereqError("Online logical volumes found, cannot"
10162                                    " adopt: %s" % utils.CommaJoin(online_lvs),
10163                                    errors.ECODE_STATE)
10164       # update the size of disk based on what is found
10165       for dsk in self.disks:
10166         dsk[constants.IDISK_SIZE] = \
10167           int(float(node_lvs["%s/%s" % (dsk[constants.IDISK_VG],
10168                                         dsk[constants.IDISK_ADOPT])][0]))
10169
10170     elif self.op.disk_template == constants.DT_BLOCK:
10171       # Normalize and de-duplicate device paths
10172       all_disks = set([os.path.abspath(disk[constants.IDISK_ADOPT])
10173                        for disk in self.disks])
10174       if len(all_disks) != len(self.disks):
10175         raise errors.OpPrereqError("Duplicate disk names given for adoption",
10176                                    errors.ECODE_INVAL)
10177       baddisks = [d for d in all_disks
10178                   if not d.startswith(constants.ADOPTABLE_BLOCKDEV_ROOT)]
10179       if baddisks:
10180         raise errors.OpPrereqError("Device node(s) %s lie outside %s and"
10181                                    " cannot be adopted" %
10182                                    (", ".join(baddisks),
10183                                     constants.ADOPTABLE_BLOCKDEV_ROOT),
10184                                    errors.ECODE_INVAL)
10185
10186       node_disks = self.rpc.call_bdev_sizes([pnode.name],
10187                                             list(all_disks))[pnode.name]
10188       node_disks.Raise("Cannot get block device information from node %s" %
10189                        pnode.name)
10190       node_disks = node_disks.payload
10191       delta = all_disks.difference(node_disks.keys())
10192       if delta:
10193         raise errors.OpPrereqError("Missing block device(s): %s" %
10194                                    utils.CommaJoin(delta),
10195                                    errors.ECODE_INVAL)
10196       for dsk in self.disks:
10197         dsk[constants.IDISK_SIZE] = \
10198           int(float(node_disks[dsk[constants.IDISK_ADOPT]]))
10199
10200     _CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
10201
10202     _CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant)
10203     # check OS parameters (remotely)
10204     _CheckOSParams(self, True, nodenames, self.op.os_type, self.os_full)
10205
10206     _CheckNicsBridgesExist(self, self.nics, self.pnode.name)
10207
10208     # memory check on primary node
10209     #TODO(dynmem): use MINMEM for checking
10210     if self.op.start:
10211       _CheckNodeFreeMemory(self, self.pnode.name,
10212                            "creating instance %s" % self.op.instance_name,
10213                            self.be_full[constants.BE_MAXMEM],
10214                            self.op.hypervisor)
10215
10216     self.dry_run_result = list(nodenames)
10217
10218   def Exec(self, feedback_fn):
10219     """Create and add the instance to the cluster.
10220
10221     """
10222     instance = self.op.instance_name
10223     pnode_name = self.pnode.name
10224
10225     assert not (self.owned_locks(locking.LEVEL_NODE_RES) -
10226                 self.owned_locks(locking.LEVEL_NODE)), \
10227       "Node locks differ from node resource locks"
10228
10229     ht_kind = self.op.hypervisor
10230     if ht_kind in constants.HTS_REQ_PORT:
10231       network_port = self.cfg.AllocatePort()
10232     else:
10233       network_port = None
10234
10235     # This is ugly but we got a chicken-egg problem here
10236     # We can only take the group disk parameters, as the instance
10237     # has no disks yet (we are generating them right here).
10238     node = self.cfg.GetNodeInfo(pnode_name)
10239     nodegroup = self.cfg.GetNodeGroup(node.group)
10240     disks = _GenerateDiskTemplate(self,
10241                                   self.op.disk_template,
10242                                   instance, pnode_name,
10243                                   self.secondaries,
10244                                   self.disks,
10245                                   self.instance_file_storage_dir,
10246                                   self.op.file_driver,
10247                                   0,
10248                                   feedback_fn,
10249                                   self.cfg.GetGroupDiskParams(nodegroup))
10250
10251     iobj = objects.Instance(name=instance, os=self.op.os_type,
10252                             primary_node=pnode_name,
10253                             nics=self.nics, disks=disks,
10254                             disk_template=self.op.disk_template,
10255                             admin_state=constants.ADMINST_DOWN,
10256                             network_port=network_port,
10257                             beparams=self.op.beparams,
10258                             hvparams=self.op.hvparams,
10259                             hypervisor=self.op.hypervisor,
10260                             osparams=self.op.osparams,
10261                             )
10262
10263     if self.op.tags:
10264       for tag in self.op.tags:
10265         iobj.AddTag(tag)
10266
10267     if self.adopt_disks:
10268       if self.op.disk_template == constants.DT_PLAIN:
10269         # rename LVs to the newly-generated names; we need to construct
10270         # 'fake' LV disks with the old data, plus the new unique_id
10271         tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
10272         rename_to = []
10273         for t_dsk, a_dsk in zip(tmp_disks, self.disks):
10274           rename_to.append(t_dsk.logical_id)
10275           t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
10276           self.cfg.SetDiskID(t_dsk, pnode_name)
10277         result = self.rpc.call_blockdev_rename(pnode_name,
10278                                                zip(tmp_disks, rename_to))
10279         result.Raise("Failed to rename adoped LVs")
10280     else:
10281       feedback_fn("* creating instance disks...")
10282       try:
10283         _CreateDisks(self, iobj)
10284       except errors.OpExecError:
10285         self.LogWarning("Device creation failed, reverting...")
10286         try:
10287           _RemoveDisks(self, iobj)
10288         finally:
10289           self.cfg.ReleaseDRBDMinors(instance)
10290           raise
10291
10292     feedback_fn("adding instance %s to cluster config" % instance)
10293
10294     self.cfg.AddInstance(iobj, self.proc.GetECId())
10295
10296     # Declare that we don't want to remove the instance lock anymore, as we've
10297     # added the instance to the config
10298     del self.remove_locks[locking.LEVEL_INSTANCE]
10299
10300     if self.op.mode == constants.INSTANCE_IMPORT:
10301       # Release unused nodes
10302       _ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node])
10303     else:
10304       # Release all nodes
10305       _ReleaseLocks(self, locking.LEVEL_NODE)
10306
10307     disk_abort = False
10308     if not self.adopt_disks and self.cfg.GetClusterInfo().prealloc_wipe_disks:
10309       feedback_fn("* wiping instance disks...")
10310       try:
10311         _WipeDisks(self, iobj)
10312       except errors.OpExecError, err:
10313         logging.exception("Wiping disks failed")
10314         self.LogWarning("Wiping instance disks failed (%s)", err)
10315         disk_abort = True
10316
10317     if disk_abort:
10318       # Something is already wrong with the disks, don't do anything else
10319       pass
10320     elif self.op.wait_for_sync:
10321       disk_abort = not _WaitForSync(self, iobj)
10322     elif iobj.disk_template in constants.DTS_INT_MIRROR:
10323       # make sure the disks are not degraded (still sync-ing is ok)
10324       feedback_fn("* checking mirrors status")
10325       disk_abort = not _WaitForSync(self, iobj, oneshot=True)
10326     else:
10327       disk_abort = False
10328
10329     if disk_abort:
10330       _RemoveDisks(self, iobj)
10331       self.cfg.RemoveInstance(iobj.name)
10332       # Make sure the instance lock gets removed
10333       self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
10334       raise errors.OpExecError("There are some degraded disks for"
10335                                " this instance")
10336
10337     # Release all node resource locks
10338     _ReleaseLocks(self, locking.LEVEL_NODE_RES)
10339
10340     if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
10341       # we need to set the disks ID to the primary node, since the
10342       # preceding code might or might have not done it, depending on
10343       # disk template and other options
10344       for disk in iobj.disks:
10345         self.cfg.SetDiskID(disk, pnode_name)
10346       if self.op.mode == constants.INSTANCE_CREATE:
10347         if not self.op.no_install:
10348           pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
10349                         not self.op.wait_for_sync)
10350           if pause_sync:
10351             feedback_fn("* pausing disk sync to install instance OS")
10352             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
10353                                                               (iobj.disks,
10354                                                                iobj), True)
10355             for idx, success in enumerate(result.payload):
10356               if not success:
10357                 logging.warn("pause-sync of instance %s for disk %d failed",
10358                              instance, idx)
10359
10360           feedback_fn("* running the instance OS create scripts...")
10361           # FIXME: pass debug option from opcode to backend
10362           os_add_result = \
10363             self.rpc.call_instance_os_add(pnode_name, (iobj, None), False,
10364                                           self.op.debug_level)
10365           if pause_sync:
10366             feedback_fn("* resuming disk sync")
10367             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
10368                                                               (iobj.disks,
10369                                                                iobj), False)
10370             for idx, success in enumerate(result.payload):
10371               if not success:
10372                 logging.warn("resume-sync of instance %s for disk %d failed",
10373                              instance, idx)
10374
10375           os_add_result.Raise("Could not add os for instance %s"
10376                               " on node %s" % (instance, pnode_name))
10377
10378       else:
10379         if self.op.mode == constants.INSTANCE_IMPORT:
10380           feedback_fn("* running the instance OS import scripts...")
10381
10382           transfers = []
10383
10384           for idx, image in enumerate(self.src_images):
10385             if not image:
10386               continue
10387
10388             # FIXME: pass debug option from opcode to backend
10389             dt = masterd.instance.DiskTransfer("disk/%s" % idx,
10390                                                constants.IEIO_FILE, (image, ),
10391                                                constants.IEIO_SCRIPT,
10392                                                (iobj.disks[idx], idx),
10393                                                None)
10394             transfers.append(dt)
10395
10396           import_result = \
10397             masterd.instance.TransferInstanceData(self, feedback_fn,
10398                                                   self.op.src_node, pnode_name,
10399                                                   self.pnode.secondary_ip,
10400                                                   iobj, transfers)
10401           if not compat.all(import_result):
10402             self.LogWarning("Some disks for instance %s on node %s were not"
10403                             " imported successfully" % (instance, pnode_name))
10404
10405           rename_from = self._old_instance_name
10406
10407         elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
10408           feedback_fn("* preparing remote import...")
10409           # The source cluster will stop the instance before attempting to make
10410           # a connection. In some cases stopping an instance can take a long
10411           # time, hence the shutdown timeout is added to the connection
10412           # timeout.
10413           connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
10414                              self.op.source_shutdown_timeout)
10415           timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
10416
10417           assert iobj.primary_node == self.pnode.name
10418           disk_results = \
10419             masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
10420                                           self.source_x509_ca,
10421                                           self._cds, timeouts)
10422           if not compat.all(disk_results):
10423             # TODO: Should the instance still be started, even if some disks
10424             # failed to import (valid for local imports, too)?
10425             self.LogWarning("Some disks for instance %s on node %s were not"
10426                             " imported successfully" % (instance, pnode_name))
10427
10428           rename_from = self.source_instance_name
10429
10430         else:
10431           # also checked in the prereq part
10432           raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
10433                                        % self.op.mode)
10434
10435         # Run rename script on newly imported instance
10436         assert iobj.name == instance
10437         feedback_fn("Running rename script for %s" % instance)
10438         result = self.rpc.call_instance_run_rename(pnode_name, iobj,
10439                                                    rename_from,
10440                                                    self.op.debug_level)
10441         if result.fail_msg:
10442           self.LogWarning("Failed to run rename script for %s on node"
10443                           " %s: %s" % (instance, pnode_name, result.fail_msg))
10444
10445     assert not self.owned_locks(locking.LEVEL_NODE_RES)
10446
10447     if self.op.start:
10448       iobj.admin_state = constants.ADMINST_UP
10449       self.cfg.Update(iobj, feedback_fn)
10450       logging.info("Starting instance %s on node %s", instance, pnode_name)
10451       feedback_fn("* starting instance...")
10452       result = self.rpc.call_instance_start(pnode_name, (iobj, None, None),
10453                                             False)
10454       result.Raise("Could not start instance")
10455
10456     return list(iobj.all_nodes)
10457
10458
10459 class LUInstanceMultiAlloc(NoHooksLU):
10460   """Allocates multiple instances at the same time.
10461
10462   """
10463   REQ_BGL = False
10464
10465   def CheckArguments(self):
10466     """Check arguments.
10467
10468     """
10469     nodes = []
10470     for inst in self.op.instances:
10471       if inst.iallocator is not None:
10472         raise errors.OpPrereqError("iallocator are not allowed to be set on"
10473                                    " instance objects", errors.ECODE_INVAL)
10474       nodes.append(bool(inst.pnode))
10475       if inst.disk_template in constants.DTS_INT_MIRROR:
10476         nodes.append(bool(inst.snode))
10477
10478     has_nodes = compat.any(nodes)
10479     if compat.all(nodes) ^ has_nodes:
10480       raise errors.OpPrereqError("There are instance objects providing"
10481                                  " pnode/snode while others do not",
10482                                  errors.ECODE_INVAL)
10483
10484     if self.op.iallocator is None:
10485       default_iallocator = self.cfg.GetDefaultIAllocator()
10486       if default_iallocator and has_nodes:
10487         self.op.iallocator = default_iallocator
10488       else:
10489         raise errors.OpPrereqError("No iallocator or nodes on the instances"
10490                                    " given and no cluster-wide default"
10491                                    " iallocator found; please specify either"
10492                                    " an iallocator or nodes on the instances"
10493                                    " or set a cluster-wide default iallocator",
10494                                    errors.ECODE_INVAL)
10495
10496     dups = utils.FindDuplicates([op.instance_name for op in self.op.instances])
10497     if dups:
10498       raise errors.OpPrereqError("There are duplicate instance names: %s" %
10499                                  utils.CommaJoin(dups), errors.ECODE_INVAL)
10500
10501   def ExpandNames(self):
10502     """Calculate the locks.
10503
10504     """
10505     self.share_locks = _ShareAll()
10506     self.needed_locks = {}
10507
10508     if self.op.iallocator:
10509       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
10510       self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
10511     else:
10512       nodeslist = []
10513       for inst in self.op.instances:
10514         inst.pnode = _ExpandNodeName(self.cfg, inst.pnode)
10515         nodeslist.append(inst.pnode)
10516         if inst.snode is not None:
10517           inst.snode = _ExpandNodeName(self.cfg, inst.snode)
10518           nodeslist.append(inst.snode)
10519
10520       self.needed_locks[locking.LEVEL_NODE] = nodeslist
10521       # Lock resources of instance's primary and secondary nodes (copy to
10522       # prevent accidential modification)
10523       self.needed_locks[locking.LEVEL_NODE_RES] = list(nodeslist)
10524
10525   def CheckPrereq(self):
10526     """Check prerequisite.
10527
10528     """
10529     cluster = self.cfg.GetClusterInfo()
10530     default_vg = self.cfg.GetVGName()
10531     insts = [_CreateInstanceAllocRequest(op, _ComputeDisks(op, default_vg),
10532                                          _ComputeNics(op, cluster, None,
10533                                                       self.cfg, self.proc),
10534                                          _ComputeFullBeParams(op, cluster))
10535              for op in self.op.instances]
10536     req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
10537     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
10538
10539     ial.Run(self.op.iallocator)
10540
10541     if not ial.success:
10542       raise errors.OpPrereqError("Can't compute nodes using"
10543                                  " iallocator '%s': %s" %
10544                                  (self.op.iallocator, ial.info),
10545                                  errors.ECODE_NORES)
10546
10547     self.ia_result = ial.result
10548
10549     if self.op.dry_run:
10550       self.dry_run_rsult = objects.FillDict(self._ConstructPartialResult(), {
10551         constants.JOB_IDS_KEY: [],
10552         })
10553
10554   def _ConstructPartialResult(self):
10555     """Contructs the partial result.
10556
10557     """
10558     (allocatable, failed) = self.ia_result
10559     return {
10560       opcodes.OpInstanceMultiAlloc.ALLOCATABLE_KEY:
10561         map(compat.fst, allocatable),
10562       opcodes.OpInstanceMultiAlloc.FAILED_KEY: failed,
10563       }
10564
10565   def Exec(self, feedback_fn):
10566     """Executes the opcode.
10567
10568     """
10569     op2inst = dict((op.instance_name, op) for op in self.op.instances)
10570     (allocatable, failed) = self.ia_result
10571
10572     jobs = []
10573     for (name, nodes) in allocatable:
10574       op = op2inst.pop(name)
10575
10576       if len(nodes) > 1:
10577         (op.pnode, op.snode) = nodes
10578       else:
10579         (op.pnode,) = nodes
10580
10581       jobs.append([op])
10582
10583     missing = set(op2inst.keys()) - set(failed)
10584     assert not missing, \
10585       "Iallocator did return incomplete result: %s" % utils.CommaJoin(missing)
10586
10587     return ResultWithJobs(jobs, **self._ConstructPartialResult())
10588
10589
10590 def _CheckRADOSFreeSpace():
10591   """Compute disk size requirements inside the RADOS cluster.
10592
10593   """
10594   # For the RADOS cluster we assume there is always enough space.
10595   pass
10596
10597
10598 class LUInstanceConsole(NoHooksLU):
10599   """Connect to an instance's console.
10600
10601   This is somewhat special in that it returns the command line that
10602   you need to run on the master node in order to connect to the
10603   console.
10604
10605   """
10606   REQ_BGL = False
10607
10608   def ExpandNames(self):
10609     self.share_locks = _ShareAll()
10610     self._ExpandAndLockInstance()
10611
10612   def CheckPrereq(self):
10613     """Check prerequisites.
10614
10615     This checks that the instance is in the cluster.
10616
10617     """
10618     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
10619     assert self.instance is not None, \
10620       "Cannot retrieve locked instance %s" % self.op.instance_name
10621     _CheckNodeOnline(self, self.instance.primary_node)
10622
10623   def Exec(self, feedback_fn):
10624     """Connect to the console of an instance
10625
10626     """
10627     instance = self.instance
10628     node = instance.primary_node
10629
10630     node_insts = self.rpc.call_instance_list([node],
10631                                              [instance.hypervisor])[node]
10632     node_insts.Raise("Can't get node information from %s" % node)
10633
10634     if instance.name not in node_insts.payload:
10635       if instance.admin_state == constants.ADMINST_UP:
10636         state = constants.INSTST_ERRORDOWN
10637       elif instance.admin_state == constants.ADMINST_DOWN:
10638         state = constants.INSTST_ADMINDOWN
10639       else:
10640         state = constants.INSTST_ADMINOFFLINE
10641       raise errors.OpExecError("Instance %s is not running (state %s)" %
10642                                (instance.name, state))
10643
10644     logging.debug("Connecting to console of %s on %s", instance.name, node)
10645
10646     return _GetInstanceConsole(self.cfg.GetClusterInfo(), instance)
10647
10648
10649 def _GetInstanceConsole(cluster, instance):
10650   """Returns console information for an instance.
10651
10652   @type cluster: L{objects.Cluster}
10653   @type instance: L{objects.Instance}
10654   @rtype: dict
10655
10656   """
10657   hyper = hypervisor.GetHypervisor(instance.hypervisor)
10658   # beparams and hvparams are passed separately, to avoid editing the
10659   # instance and then saving the defaults in the instance itself.
10660   hvparams = cluster.FillHV(instance)
10661   beparams = cluster.FillBE(instance)
10662   console = hyper.GetInstanceConsole(instance, hvparams, beparams)
10663
10664   assert console.instance == instance.name
10665   assert console.Validate()
10666
10667   return console.ToDict()
10668
10669
10670 class LUInstanceReplaceDisks(LogicalUnit):
10671   """Replace the disks of an instance.
10672
10673   """
10674   HPATH = "mirrors-replace"
10675   HTYPE = constants.HTYPE_INSTANCE
10676   REQ_BGL = False
10677
10678   def CheckArguments(self):
10679     """Check arguments.
10680
10681     """
10682     remote_node = self.op.remote_node
10683     ialloc = self.op.iallocator
10684     if self.op.mode == constants.REPLACE_DISK_CHG:
10685       if remote_node is None and ialloc is None:
10686         raise errors.OpPrereqError("When changing the secondary either an"
10687                                    " iallocator script must be used or the"
10688                                    " new node given", errors.ECODE_INVAL)
10689       else:
10690         _CheckIAllocatorOrNode(self, "iallocator", "remote_node")
10691
10692     elif remote_node is not None or ialloc is not None:
10693       # Not replacing the secondary
10694       raise errors.OpPrereqError("The iallocator and new node options can"
10695                                  " only be used when changing the"
10696                                  " secondary node", errors.ECODE_INVAL)
10697
10698   def ExpandNames(self):
10699     self._ExpandAndLockInstance()
10700
10701     assert locking.LEVEL_NODE not in self.needed_locks
10702     assert locking.LEVEL_NODE_RES not in self.needed_locks
10703     assert locking.LEVEL_NODEGROUP not in self.needed_locks
10704
10705     assert self.op.iallocator is None or self.op.remote_node is None, \
10706       "Conflicting options"
10707
10708     if self.op.remote_node is not None:
10709       self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
10710
10711       # Warning: do not remove the locking of the new secondary here
10712       # unless DRBD8.AddChildren is changed to work in parallel;
10713       # currently it doesn't since parallel invocations of
10714       # FindUnusedMinor will conflict
10715       self.needed_locks[locking.LEVEL_NODE] = [self.op.remote_node]
10716       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
10717     else:
10718       self.needed_locks[locking.LEVEL_NODE] = []
10719       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
10720
10721       if self.op.iallocator is not None:
10722         # iallocator will select a new node in the same group
10723         self.needed_locks[locking.LEVEL_NODEGROUP] = []
10724
10725     self.needed_locks[locking.LEVEL_NODE_RES] = []
10726
10727     self.replacer = TLReplaceDisks(self, self.op.instance_name, self.op.mode,
10728                                    self.op.iallocator, self.op.remote_node,
10729                                    self.op.disks, False, self.op.early_release,
10730                                    self.op.ignore_ipolicy)
10731
10732     self.tasklets = [self.replacer]
10733
10734   def DeclareLocks(self, level):
10735     if level == locking.LEVEL_NODEGROUP:
10736       assert self.op.remote_node is None
10737       assert self.op.iallocator is not None
10738       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
10739
10740       self.share_locks[locking.LEVEL_NODEGROUP] = 1
10741       # Lock all groups used by instance optimistically; this requires going
10742       # via the node before it's locked, requiring verification later on
10743       self.needed_locks[locking.LEVEL_NODEGROUP] = \
10744         self.cfg.GetInstanceNodeGroups(self.op.instance_name)
10745
10746     elif level == locking.LEVEL_NODE:
10747       if self.op.iallocator is not None:
10748         assert self.op.remote_node is None
10749         assert not self.needed_locks[locking.LEVEL_NODE]
10750
10751         # Lock member nodes of all locked groups
10752         self.needed_locks[locking.LEVEL_NODE] = \
10753             [node_name
10754              for group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
10755              for node_name in self.cfg.GetNodeGroup(group_uuid).members]
10756       else:
10757         self._LockInstancesNodes()
10758     elif level == locking.LEVEL_NODE_RES:
10759       # Reuse node locks
10760       self.needed_locks[locking.LEVEL_NODE_RES] = \
10761         self.needed_locks[locking.LEVEL_NODE]
10762
10763   def BuildHooksEnv(self):
10764     """Build hooks env.
10765
10766     This runs on the master, the primary and all the secondaries.
10767
10768     """
10769     instance = self.replacer.instance
10770     env = {
10771       "MODE": self.op.mode,
10772       "NEW_SECONDARY": self.op.remote_node,
10773       "OLD_SECONDARY": instance.secondary_nodes[0],
10774       }
10775     env.update(_BuildInstanceHookEnvByObject(self, instance))
10776     return env
10777
10778   def BuildHooksNodes(self):
10779     """Build hooks nodes.
10780
10781     """
10782     instance = self.replacer.instance
10783     nl = [
10784       self.cfg.GetMasterNode(),
10785       instance.primary_node,
10786       ]
10787     if self.op.remote_node is not None:
10788       nl.append(self.op.remote_node)
10789     return nl, nl
10790
10791   def CheckPrereq(self):
10792     """Check prerequisites.
10793
10794     """
10795     assert (self.glm.is_owned(locking.LEVEL_NODEGROUP) or
10796             self.op.iallocator is None)
10797
10798     # Verify if node group locks are still correct
10799     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
10800     if owned_groups:
10801       _CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
10802
10803     return LogicalUnit.CheckPrereq(self)
10804
10805
10806 class TLReplaceDisks(Tasklet):
10807   """Replaces disks for an instance.
10808
10809   Note: Locking is not within the scope of this class.
10810
10811   """
10812   def __init__(self, lu, instance_name, mode, iallocator_name, remote_node,
10813                disks, delay_iallocator, early_release, ignore_ipolicy):
10814     """Initializes this class.
10815
10816     """
10817     Tasklet.__init__(self, lu)
10818
10819     # Parameters
10820     self.instance_name = instance_name
10821     self.mode = mode
10822     self.iallocator_name = iallocator_name
10823     self.remote_node = remote_node
10824     self.disks = disks
10825     self.delay_iallocator = delay_iallocator
10826     self.early_release = early_release
10827     self.ignore_ipolicy = ignore_ipolicy
10828
10829     # Runtime data
10830     self.instance = None
10831     self.new_node = None
10832     self.target_node = None
10833     self.other_node = None
10834     self.remote_node_info = None
10835     self.node_secondary_ip = None
10836
10837   @staticmethod
10838   def _RunAllocator(lu, iallocator_name, instance_name, relocate_from):
10839     """Compute a new secondary node using an IAllocator.
10840
10841     """
10842     req = iallocator.IAReqRelocate(name=instance_name,
10843                                    relocate_from=list(relocate_from))
10844     ial = iallocator.IAllocator(lu.cfg, lu.rpc, req)
10845
10846     ial.Run(iallocator_name)
10847
10848     if not ial.success:
10849       raise errors.OpPrereqError("Can't compute nodes using iallocator '%s':"
10850                                  " %s" % (iallocator_name, ial.info),
10851                                  errors.ECODE_NORES)
10852
10853     remote_node_name = ial.result[0]
10854
10855     lu.LogInfo("Selected new secondary for instance '%s': %s",
10856                instance_name, remote_node_name)
10857
10858     return remote_node_name
10859
10860   def _FindFaultyDisks(self, node_name):
10861     """Wrapper for L{_FindFaultyInstanceDisks}.
10862
10863     """
10864     return _FindFaultyInstanceDisks(self.cfg, self.rpc, self.instance,
10865                                     node_name, True)
10866
10867   def _CheckDisksActivated(self, instance):
10868     """Checks if the instance disks are activated.
10869
10870     @param instance: The instance to check disks
10871     @return: True if they are activated, False otherwise
10872
10873     """
10874     nodes = instance.all_nodes
10875
10876     for idx, dev in enumerate(instance.disks):
10877       for node in nodes:
10878         self.lu.LogInfo("Checking disk/%d on %s", idx, node)
10879         self.cfg.SetDiskID(dev, node)
10880
10881         result = _BlockdevFind(self, node, dev, instance)
10882
10883         if result.offline:
10884           continue
10885         elif result.fail_msg or not result.payload:
10886           return False
10887
10888     return True
10889
10890   def CheckPrereq(self):
10891     """Check prerequisites.
10892
10893     This checks that the instance is in the cluster.
10894
10895     """
10896     self.instance = instance = self.cfg.GetInstanceInfo(self.instance_name)
10897     assert instance is not None, \
10898       "Cannot retrieve locked instance %s" % self.instance_name
10899
10900     if instance.disk_template != constants.DT_DRBD8:
10901       raise errors.OpPrereqError("Can only run replace disks for DRBD8-based"
10902                                  " instances", errors.ECODE_INVAL)
10903
10904     if len(instance.secondary_nodes) != 1:
10905       raise errors.OpPrereqError("The instance has a strange layout,"
10906                                  " expected one secondary but found %d" %
10907                                  len(instance.secondary_nodes),
10908                                  errors.ECODE_FAULT)
10909
10910     if not self.delay_iallocator:
10911       self._CheckPrereq2()
10912
10913   def _CheckPrereq2(self):
10914     """Check prerequisites, second part.
10915
10916     This function should always be part of CheckPrereq. It was separated and is
10917     now called from Exec because during node evacuation iallocator was only
10918     called with an unmodified cluster model, not taking planned changes into
10919     account.
10920
10921     """
10922     instance = self.instance
10923     secondary_node = instance.secondary_nodes[0]
10924
10925     if self.iallocator_name is None:
10926       remote_node = self.remote_node
10927     else:
10928       remote_node = self._RunAllocator(self.lu, self.iallocator_name,
10929                                        instance.name, instance.secondary_nodes)
10930
10931     if remote_node is None:
10932       self.remote_node_info = None
10933     else:
10934       assert remote_node in self.lu.owned_locks(locking.LEVEL_NODE), \
10935              "Remote node '%s' is not locked" % remote_node
10936
10937       self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
10938       assert self.remote_node_info is not None, \
10939         "Cannot retrieve locked node %s" % remote_node
10940
10941     if remote_node == self.instance.primary_node:
10942       raise errors.OpPrereqError("The specified node is the primary node of"
10943                                  " the instance", errors.ECODE_INVAL)
10944
10945     if remote_node == secondary_node:
10946       raise errors.OpPrereqError("The specified node is already the"
10947                                  " secondary node of the instance",
10948                                  errors.ECODE_INVAL)
10949
10950     if self.disks and self.mode in (constants.REPLACE_DISK_AUTO,
10951                                     constants.REPLACE_DISK_CHG):
10952       raise errors.OpPrereqError("Cannot specify disks to be replaced",
10953                                  errors.ECODE_INVAL)
10954
10955     if self.mode == constants.REPLACE_DISK_AUTO:
10956       if not self._CheckDisksActivated(instance):
10957         raise errors.OpPrereqError("Please run activate-disks on instance %s"
10958                                    " first" % self.instance_name,
10959                                    errors.ECODE_STATE)
10960       faulty_primary = self._FindFaultyDisks(instance.primary_node)
10961       faulty_secondary = self._FindFaultyDisks(secondary_node)
10962
10963       if faulty_primary and faulty_secondary:
10964         raise errors.OpPrereqError("Instance %s has faulty disks on more than"
10965                                    " one node and can not be repaired"
10966                                    " automatically" % self.instance_name,
10967                                    errors.ECODE_STATE)
10968
10969       if faulty_primary:
10970         self.disks = faulty_primary
10971         self.target_node = instance.primary_node
10972         self.other_node = secondary_node
10973         check_nodes = [self.target_node, self.other_node]
10974       elif faulty_secondary:
10975         self.disks = faulty_secondary
10976         self.target_node = secondary_node
10977         self.other_node = instance.primary_node
10978         check_nodes = [self.target_node, self.other_node]
10979       else:
10980         self.disks = []
10981         check_nodes = []
10982
10983     else:
10984       # Non-automatic modes
10985       if self.mode == constants.REPLACE_DISK_PRI:
10986         self.target_node = instance.primary_node
10987         self.other_node = secondary_node
10988         check_nodes = [self.target_node, self.other_node]
10989
10990       elif self.mode == constants.REPLACE_DISK_SEC:
10991         self.target_node = secondary_node
10992         self.other_node = instance.primary_node
10993         check_nodes = [self.target_node, self.other_node]
10994
10995       elif self.mode == constants.REPLACE_DISK_CHG:
10996         self.new_node = remote_node
10997         self.other_node = instance.primary_node
10998         self.target_node = secondary_node
10999         check_nodes = [self.new_node, self.other_node]
11000
11001         _CheckNodeNotDrained(self.lu, remote_node)
11002         _CheckNodeVmCapable(self.lu, remote_node)
11003
11004         old_node_info = self.cfg.GetNodeInfo(secondary_node)
11005         assert old_node_info is not None
11006         if old_node_info.offline and not self.early_release:
11007           # doesn't make sense to delay the release
11008           self.early_release = True
11009           self.lu.LogInfo("Old secondary %s is offline, automatically enabling"
11010                           " early-release mode", secondary_node)
11011
11012       else:
11013         raise errors.ProgrammerError("Unhandled disk replace mode (%s)" %
11014                                      self.mode)
11015
11016       # If not specified all disks should be replaced
11017       if not self.disks:
11018         self.disks = range(len(self.instance.disks))
11019
11020     # TODO: This is ugly, but right now we can't distinguish between internal
11021     # submitted opcode and external one. We should fix that.
11022     if self.remote_node_info:
11023       # We change the node, lets verify it still meets instance policy
11024       new_group_info = self.cfg.GetNodeGroup(self.remote_node_info.group)
11025       cluster = self.cfg.GetClusterInfo()
11026       ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
11027                                                               new_group_info)
11028       _CheckTargetNodeIPolicy(self, ipolicy, instance, self.remote_node_info,
11029                               ignore=self.ignore_ipolicy)
11030
11031     for node in check_nodes:
11032       _CheckNodeOnline(self.lu, node)
11033
11034     touched_nodes = frozenset(node_name for node_name in [self.new_node,
11035                                                           self.other_node,
11036                                                           self.target_node]
11037                               if node_name is not None)
11038
11039     # Release unneeded node and node resource locks
11040     _ReleaseLocks(self.lu, locking.LEVEL_NODE, keep=touched_nodes)
11041     _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES, keep=touched_nodes)
11042
11043     # Release any owned node group
11044     if self.lu.glm.is_owned(locking.LEVEL_NODEGROUP):
11045       _ReleaseLocks(self.lu, locking.LEVEL_NODEGROUP)
11046
11047     # Check whether disks are valid
11048     for disk_idx in self.disks:
11049       instance.FindDisk(disk_idx)
11050
11051     # Get secondary node IP addresses
11052     self.node_secondary_ip = dict((name, node.secondary_ip) for (name, node)
11053                                   in self.cfg.GetMultiNodeInfo(touched_nodes))
11054
11055   def Exec(self, feedback_fn):
11056     """Execute disk replacement.
11057
11058     This dispatches the disk replacement to the appropriate handler.
11059
11060     """
11061     if self.delay_iallocator:
11062       self._CheckPrereq2()
11063
11064     if __debug__:
11065       # Verify owned locks before starting operation
11066       owned_nodes = self.lu.owned_locks(locking.LEVEL_NODE)
11067       assert set(owned_nodes) == set(self.node_secondary_ip), \
11068           ("Incorrect node locks, owning %s, expected %s" %
11069            (owned_nodes, self.node_secondary_ip.keys()))
11070       assert (self.lu.owned_locks(locking.LEVEL_NODE) ==
11071               self.lu.owned_locks(locking.LEVEL_NODE_RES))
11072
11073       owned_instances = self.lu.owned_locks(locking.LEVEL_INSTANCE)
11074       assert list(owned_instances) == [self.instance_name], \
11075           "Instance '%s' not locked" % self.instance_name
11076
11077       assert not self.lu.glm.is_owned(locking.LEVEL_NODEGROUP), \
11078           "Should not own any node group lock at this point"
11079
11080     if not self.disks:
11081       feedback_fn("No disks need replacement for instance '%s'" %
11082                   self.instance.name)
11083       return
11084
11085     feedback_fn("Replacing disk(s) %s for instance '%s'" %
11086                 (utils.CommaJoin(self.disks), self.instance.name))
11087     feedback_fn("Current primary node: %s", self.instance.primary_node)
11088     feedback_fn("Current seconary node: %s",
11089                 utils.CommaJoin(self.instance.secondary_nodes))
11090
11091     activate_disks = (self.instance.admin_state != constants.ADMINST_UP)
11092
11093     # Activate the instance disks if we're replacing them on a down instance
11094     if activate_disks:
11095       _StartInstanceDisks(self.lu, self.instance, True)
11096
11097     try:
11098       # Should we replace the secondary node?
11099       if self.new_node is not None:
11100         fn = self._ExecDrbd8Secondary
11101       else:
11102         fn = self._ExecDrbd8DiskOnly
11103
11104       result = fn(feedback_fn)
11105     finally:
11106       # Deactivate the instance disks if we're replacing them on a
11107       # down instance
11108       if activate_disks:
11109         _SafeShutdownInstanceDisks(self.lu, self.instance)
11110
11111     assert not self.lu.owned_locks(locking.LEVEL_NODE)
11112
11113     if __debug__:
11114       # Verify owned locks
11115       owned_nodes = self.lu.owned_locks(locking.LEVEL_NODE_RES)
11116       nodes = frozenset(self.node_secondary_ip)
11117       assert ((self.early_release and not owned_nodes) or
11118               (not self.early_release and not (set(owned_nodes) - nodes))), \
11119         ("Not owning the correct locks, early_release=%s, owned=%r,"
11120          " nodes=%r" % (self.early_release, owned_nodes, nodes))
11121
11122     return result
11123
11124   def _CheckVolumeGroup(self, nodes):
11125     self.lu.LogInfo("Checking volume groups")
11126
11127     vgname = self.cfg.GetVGName()
11128
11129     # Make sure volume group exists on all involved nodes
11130     results = self.rpc.call_vg_list(nodes)
11131     if not results:
11132       raise errors.OpExecError("Can't list volume groups on the nodes")
11133
11134     for node in nodes:
11135       res = results[node]
11136       res.Raise("Error checking node %s" % node)
11137       if vgname not in res.payload:
11138         raise errors.OpExecError("Volume group '%s' not found on node %s" %
11139                                  (vgname, node))
11140
11141   def _CheckDisksExistence(self, nodes):
11142     # Check disk existence
11143     for idx, dev in enumerate(self.instance.disks):
11144       if idx not in self.disks:
11145         continue
11146
11147       for node in nodes:
11148         self.lu.LogInfo("Checking disk/%d on %s" % (idx, node))
11149         self.cfg.SetDiskID(dev, node)
11150
11151         result = _BlockdevFind(self, node, dev, self.instance)
11152
11153         msg = result.fail_msg
11154         if msg or not result.payload:
11155           if not msg:
11156             msg = "disk not found"
11157           raise errors.OpExecError("Can't find disk/%d on node %s: %s" %
11158                                    (idx, node, msg))
11159
11160   def _CheckDisksConsistency(self, node_name, on_primary, ldisk):
11161     for idx, dev in enumerate(self.instance.disks):
11162       if idx not in self.disks:
11163         continue
11164
11165       self.lu.LogInfo("Checking disk/%d consistency on node %s" %
11166                       (idx, node_name))
11167
11168       if not _CheckDiskConsistency(self.lu, self.instance, dev, node_name,
11169                                    on_primary, ldisk=ldisk):
11170         raise errors.OpExecError("Node %s has degraded storage, unsafe to"
11171                                  " replace disks for instance %s" %
11172                                  (node_name, self.instance.name))
11173
11174   def _CreateNewStorage(self, node_name):
11175     """Create new storage on the primary or secondary node.
11176
11177     This is only used for same-node replaces, not for changing the
11178     secondary node, hence we don't want to modify the existing disk.
11179
11180     """
11181     iv_names = {}
11182
11183     disks = _AnnotateDiskParams(self.instance, self.instance.disks, self.cfg)
11184     for idx, dev in enumerate(disks):
11185       if idx not in self.disks:
11186         continue
11187
11188       self.lu.LogInfo("Adding storage on %s for disk/%d" % (node_name, idx))
11189
11190       self.cfg.SetDiskID(dev, node_name)
11191
11192       lv_names = [".disk%d_%s" % (idx, suffix) for suffix in ["data", "meta"]]
11193       names = _GenerateUniqueNames(self.lu, lv_names)
11194
11195       (data_disk, meta_disk) = dev.children
11196       vg_data = data_disk.logical_id[0]
11197       lv_data = objects.Disk(dev_type=constants.LD_LV, size=dev.size,
11198                              logical_id=(vg_data, names[0]),
11199                              params=data_disk.params)
11200       vg_meta = meta_disk.logical_id[0]
11201       lv_meta = objects.Disk(dev_type=constants.LD_LV,
11202                              size=constants.DRBD_META_SIZE,
11203                              logical_id=(vg_meta, names[1]),
11204                              params=meta_disk.params)
11205
11206       new_lvs = [lv_data, lv_meta]
11207       old_lvs = [child.Copy() for child in dev.children]
11208       iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
11209
11210       # we pass force_create=True to force the LVM creation
11211       for new_lv in new_lvs:
11212         _CreateBlockDevInner(self.lu, node_name, self.instance, new_lv, True,
11213                              _GetInstanceInfoText(self.instance), False)
11214
11215     return iv_names
11216
11217   def _CheckDevices(self, node_name, iv_names):
11218     for name, (dev, _, _) in iv_names.iteritems():
11219       self.cfg.SetDiskID(dev, node_name)
11220
11221       result = _BlockdevFind(self, node_name, dev, self.instance)
11222
11223       msg = result.fail_msg
11224       if msg or not result.payload:
11225         if not msg:
11226           msg = "disk not found"
11227         raise errors.OpExecError("Can't find DRBD device %s: %s" %
11228                                  (name, msg))
11229
11230       if result.payload.is_degraded:
11231         raise errors.OpExecError("DRBD device %s is degraded!" % name)
11232
11233   def _RemoveOldStorage(self, node_name, iv_names):
11234     for name, (_, old_lvs, _) in iv_names.iteritems():
11235       self.lu.LogInfo("Remove logical volumes for %s" % name)
11236
11237       for lv in old_lvs:
11238         self.cfg.SetDiskID(lv, node_name)
11239
11240         msg = self.rpc.call_blockdev_remove(node_name, lv).fail_msg
11241         if msg:
11242           self.lu.LogWarning("Can't remove old LV: %s" % msg,
11243                              hint="remove unused LVs manually")
11244
11245   def _ExecDrbd8DiskOnly(self, feedback_fn): # pylint: disable=W0613
11246     """Replace a disk on the primary or secondary for DRBD 8.
11247
11248     The algorithm for replace is quite complicated:
11249
11250       1. for each disk to be replaced:
11251
11252         1. create new LVs on the target node with unique names
11253         1. detach old LVs from the drbd device
11254         1. rename old LVs to name_replaced.<time_t>
11255         1. rename new LVs to old LVs
11256         1. attach the new LVs (with the old names now) to the drbd device
11257
11258       1. wait for sync across all devices
11259
11260       1. for each modified disk:
11261
11262         1. remove old LVs (which have the name name_replaces.<time_t>)
11263
11264     Failures are not very well handled.
11265
11266     """
11267     steps_total = 6
11268
11269     # Step: check device activation
11270     self.lu.LogStep(1, steps_total, "Check device existence")
11271     self._CheckDisksExistence([self.other_node, self.target_node])
11272     self._CheckVolumeGroup([self.target_node, self.other_node])
11273
11274     # Step: check other node consistency
11275     self.lu.LogStep(2, steps_total, "Check peer consistency")
11276     self._CheckDisksConsistency(self.other_node,
11277                                 self.other_node == self.instance.primary_node,
11278                                 False)
11279
11280     # Step: create new storage
11281     self.lu.LogStep(3, steps_total, "Allocate new storage")
11282     iv_names = self._CreateNewStorage(self.target_node)
11283
11284     # Step: for each lv, detach+rename*2+attach
11285     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
11286     for dev, old_lvs, new_lvs in iv_names.itervalues():
11287       self.lu.LogInfo("Detaching %s drbd from local storage" % dev.iv_name)
11288
11289       result = self.rpc.call_blockdev_removechildren(self.target_node, dev,
11290                                                      old_lvs)
11291       result.Raise("Can't detach drbd from local storage on node"
11292                    " %s for device %s" % (self.target_node, dev.iv_name))
11293       #dev.children = []
11294       #cfg.Update(instance)
11295
11296       # ok, we created the new LVs, so now we know we have the needed
11297       # storage; as such, we proceed on the target node to rename
11298       # old_lv to _old, and new_lv to old_lv; note that we rename LVs
11299       # using the assumption that logical_id == physical_id (which in
11300       # turn is the unique_id on that node)
11301
11302       # FIXME(iustin): use a better name for the replaced LVs
11303       temp_suffix = int(time.time())
11304       ren_fn = lambda d, suff: (d.physical_id[0],
11305                                 d.physical_id[1] + "_replaced-%s" % suff)
11306
11307       # Build the rename list based on what LVs exist on the node
11308       rename_old_to_new = []
11309       for to_ren in old_lvs:
11310         result = self.rpc.call_blockdev_find(self.target_node, to_ren)
11311         if not result.fail_msg and result.payload:
11312           # device exists
11313           rename_old_to_new.append((to_ren, ren_fn(to_ren, temp_suffix)))
11314
11315       self.lu.LogInfo("Renaming the old LVs on the target node")
11316       result = self.rpc.call_blockdev_rename(self.target_node,
11317                                              rename_old_to_new)
11318       result.Raise("Can't rename old LVs on node %s" % self.target_node)
11319
11320       # Now we rename the new LVs to the old LVs
11321       self.lu.LogInfo("Renaming the new LVs on the target node")
11322       rename_new_to_old = [(new, old.physical_id)
11323                            for old, new in zip(old_lvs, new_lvs)]
11324       result = self.rpc.call_blockdev_rename(self.target_node,
11325                                              rename_new_to_old)
11326       result.Raise("Can't rename new LVs on node %s" % self.target_node)
11327
11328       # Intermediate steps of in memory modifications
11329       for old, new in zip(old_lvs, new_lvs):
11330         new.logical_id = old.logical_id
11331         self.cfg.SetDiskID(new, self.target_node)
11332
11333       # We need to modify old_lvs so that removal later removes the
11334       # right LVs, not the newly added ones; note that old_lvs is a
11335       # copy here
11336       for disk in old_lvs:
11337         disk.logical_id = ren_fn(disk, temp_suffix)
11338         self.cfg.SetDiskID(disk, self.target_node)
11339
11340       # Now that the new lvs have the old name, we can add them to the device
11341       self.lu.LogInfo("Adding new mirror component on %s" % self.target_node)
11342       result = self.rpc.call_blockdev_addchildren(self.target_node,
11343                                                   (dev, self.instance), new_lvs)
11344       msg = result.fail_msg
11345       if msg:
11346         for new_lv in new_lvs:
11347           msg2 = self.rpc.call_blockdev_remove(self.target_node,
11348                                                new_lv).fail_msg
11349           if msg2:
11350             self.lu.LogWarning("Can't rollback device %s: %s", dev, msg2,
11351                                hint=("cleanup manually the unused logical"
11352                                      "volumes"))
11353         raise errors.OpExecError("Can't add local storage to drbd: %s" % msg)
11354
11355     cstep = itertools.count(5)
11356
11357     if self.early_release:
11358       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11359       self._RemoveOldStorage(self.target_node, iv_names)
11360       # TODO: Check if releasing locks early still makes sense
11361       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
11362     else:
11363       # Release all resource locks except those used by the instance
11364       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES,
11365                     keep=self.node_secondary_ip.keys())
11366
11367     # Release all node locks while waiting for sync
11368     _ReleaseLocks(self.lu, locking.LEVEL_NODE)
11369
11370     # TODO: Can the instance lock be downgraded here? Take the optional disk
11371     # shutdown in the caller into consideration.
11372
11373     # Wait for sync
11374     # This can fail as the old devices are degraded and _WaitForSync
11375     # does a combined result over all disks, so we don't check its return value
11376     self.lu.LogStep(cstep.next(), steps_total, "Sync devices")
11377     _WaitForSync(self.lu, self.instance)
11378
11379     # Check all devices manually
11380     self._CheckDevices(self.instance.primary_node, iv_names)
11381
11382     # Step: remove old storage
11383     if not self.early_release:
11384       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11385       self._RemoveOldStorage(self.target_node, iv_names)
11386
11387   def _ExecDrbd8Secondary(self, feedback_fn):
11388     """Replace the secondary node for DRBD 8.
11389
11390     The algorithm for replace is quite complicated:
11391       - for all disks of the instance:
11392         - create new LVs on the new node with same names
11393         - shutdown the drbd device on the old secondary
11394         - disconnect the drbd network on the primary
11395         - create the drbd device on the new secondary
11396         - network attach the drbd on the primary, using an artifice:
11397           the drbd code for Attach() will connect to the network if it
11398           finds a device which is connected to the good local disks but
11399           not network enabled
11400       - wait for sync across all devices
11401       - remove all disks from the old secondary
11402
11403     Failures are not very well handled.
11404
11405     """
11406     steps_total = 6
11407
11408     pnode = self.instance.primary_node
11409
11410     # Step: check device activation
11411     self.lu.LogStep(1, steps_total, "Check device existence")
11412     self._CheckDisksExistence([self.instance.primary_node])
11413     self._CheckVolumeGroup([self.instance.primary_node])
11414
11415     # Step: check other node consistency
11416     self.lu.LogStep(2, steps_total, "Check peer consistency")
11417     self._CheckDisksConsistency(self.instance.primary_node, True, True)
11418
11419     # Step: create new storage
11420     self.lu.LogStep(3, steps_total, "Allocate new storage")
11421     disks = _AnnotateDiskParams(self.instance, self.instance.disks, self.cfg)
11422     for idx, dev in enumerate(disks):
11423       self.lu.LogInfo("Adding new local storage on %s for disk/%d" %
11424                       (self.new_node, idx))
11425       # we pass force_create=True to force LVM creation
11426       for new_lv in dev.children:
11427         _CreateBlockDevInner(self.lu, self.new_node, self.instance, new_lv,
11428                              True, _GetInstanceInfoText(self.instance), False)
11429
11430     # Step 4: dbrd minors and drbd setups changes
11431     # after this, we must manually remove the drbd minors on both the
11432     # error and the success paths
11433     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
11434     minors = self.cfg.AllocateDRBDMinor([self.new_node
11435                                          for dev in self.instance.disks],
11436                                         self.instance.name)
11437     logging.debug("Allocated minors %r", minors)
11438
11439     iv_names = {}
11440     for idx, (dev, new_minor) in enumerate(zip(self.instance.disks, minors)):
11441       self.lu.LogInfo("activating a new drbd on %s for disk/%d" %
11442                       (self.new_node, idx))
11443       # create new devices on new_node; note that we create two IDs:
11444       # one without port, so the drbd will be activated without
11445       # networking information on the new node at this stage, and one
11446       # with network, for the latter activation in step 4
11447       (o_node1, o_node2, o_port, o_minor1, o_minor2, o_secret) = dev.logical_id
11448       if self.instance.primary_node == o_node1:
11449         p_minor = o_minor1
11450       else:
11451         assert self.instance.primary_node == o_node2, "Three-node instance?"
11452         p_minor = o_minor2
11453
11454       new_alone_id = (self.instance.primary_node, self.new_node, None,
11455                       p_minor, new_minor, o_secret)
11456       new_net_id = (self.instance.primary_node, self.new_node, o_port,
11457                     p_minor, new_minor, o_secret)
11458
11459       iv_names[idx] = (dev, dev.children, new_net_id)
11460       logging.debug("Allocated new_minor: %s, new_logical_id: %s", new_minor,
11461                     new_net_id)
11462       new_drbd = objects.Disk(dev_type=constants.LD_DRBD8,
11463                               logical_id=new_alone_id,
11464                               children=dev.children,
11465                               size=dev.size,
11466                               params={})
11467       (anno_new_drbd,) = _AnnotateDiskParams(self.instance, [new_drbd],
11468                                              self.cfg)
11469       try:
11470         _CreateSingleBlockDev(self.lu, self.new_node, self.instance,
11471                               anno_new_drbd,
11472                               _GetInstanceInfoText(self.instance), False)
11473       except errors.GenericError:
11474         self.cfg.ReleaseDRBDMinors(self.instance.name)
11475         raise
11476
11477     # We have new devices, shutdown the drbd on the old secondary
11478     for idx, dev in enumerate(self.instance.disks):
11479       self.lu.LogInfo("Shutting down drbd for disk/%d on old node" % idx)
11480       self.cfg.SetDiskID(dev, self.target_node)
11481       msg = self.rpc.call_blockdev_shutdown(self.target_node,
11482                                             (dev, self.instance)).fail_msg
11483       if msg:
11484         self.lu.LogWarning("Failed to shutdown drbd for disk/%d on old"
11485                            "node: %s" % (idx, msg),
11486                            hint=("Please cleanup this device manually as"
11487                                  " soon as possible"))
11488
11489     self.lu.LogInfo("Detaching primary drbds from the network (=> standalone)")
11490     result = self.rpc.call_drbd_disconnect_net([pnode], self.node_secondary_ip,
11491                                                self.instance.disks)[pnode]
11492
11493     msg = result.fail_msg
11494     if msg:
11495       # detaches didn't succeed (unlikely)
11496       self.cfg.ReleaseDRBDMinors(self.instance.name)
11497       raise errors.OpExecError("Can't detach the disks from the network on"
11498                                " old node: %s" % (msg,))
11499
11500     # if we managed to detach at least one, we update all the disks of
11501     # the instance to point to the new secondary
11502     self.lu.LogInfo("Updating instance configuration")
11503     for dev, _, new_logical_id in iv_names.itervalues():
11504       dev.logical_id = new_logical_id
11505       self.cfg.SetDiskID(dev, self.instance.primary_node)
11506
11507     self.cfg.Update(self.instance, feedback_fn)
11508
11509     # Release all node locks (the configuration has been updated)
11510     _ReleaseLocks(self.lu, locking.LEVEL_NODE)
11511
11512     # and now perform the drbd attach
11513     self.lu.LogInfo("Attaching primary drbds to new secondary"
11514                     " (standalone => connected)")
11515     result = self.rpc.call_drbd_attach_net([self.instance.primary_node,
11516                                             self.new_node],
11517                                            self.node_secondary_ip,
11518                                            (self.instance.disks, self.instance),
11519                                            self.instance.name,
11520                                            False)
11521     for to_node, to_result in result.items():
11522       msg = to_result.fail_msg
11523       if msg:
11524         self.lu.LogWarning("Can't attach drbd disks on node %s: %s",
11525                            to_node, msg,
11526                            hint=("please do a gnt-instance info to see the"
11527                                  " status of disks"))
11528
11529     cstep = itertools.count(5)
11530
11531     if self.early_release:
11532       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11533       self._RemoveOldStorage(self.target_node, iv_names)
11534       # TODO: Check if releasing locks early still makes sense
11535       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
11536     else:
11537       # Release all resource locks except those used by the instance
11538       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES,
11539                     keep=self.node_secondary_ip.keys())
11540
11541     # TODO: Can the instance lock be downgraded here? Take the optional disk
11542     # shutdown in the caller into consideration.
11543
11544     # Wait for sync
11545     # This can fail as the old devices are degraded and _WaitForSync
11546     # does a combined result over all disks, so we don't check its return value
11547     self.lu.LogStep(cstep.next(), steps_total, "Sync devices")
11548     _WaitForSync(self.lu, self.instance)
11549
11550     # Check all devices manually
11551     self._CheckDevices(self.instance.primary_node, iv_names)
11552
11553     # Step: remove old storage
11554     if not self.early_release:
11555       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11556       self._RemoveOldStorage(self.target_node, iv_names)
11557
11558
11559 class LURepairNodeStorage(NoHooksLU):
11560   """Repairs the volume group on a node.
11561
11562   """
11563   REQ_BGL = False
11564
11565   def CheckArguments(self):
11566     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
11567
11568     storage_type = self.op.storage_type
11569
11570     if (constants.SO_FIX_CONSISTENCY not in
11571         constants.VALID_STORAGE_OPERATIONS.get(storage_type, [])):
11572       raise errors.OpPrereqError("Storage units of type '%s' can not be"
11573                                  " repaired" % storage_type,
11574                                  errors.ECODE_INVAL)
11575
11576   def ExpandNames(self):
11577     self.needed_locks = {
11578       locking.LEVEL_NODE: [self.op.node_name],
11579       }
11580
11581   def _CheckFaultyDisks(self, instance, node_name):
11582     """Ensure faulty disks abort the opcode or at least warn."""
11583     try:
11584       if _FindFaultyInstanceDisks(self.cfg, self.rpc, instance,
11585                                   node_name, True):
11586         raise errors.OpPrereqError("Instance '%s' has faulty disks on"
11587                                    " node '%s'" % (instance.name, node_name),
11588                                    errors.ECODE_STATE)
11589     except errors.OpPrereqError, err:
11590       if self.op.ignore_consistency:
11591         self.proc.LogWarning(str(err.args[0]))
11592       else:
11593         raise
11594
11595   def CheckPrereq(self):
11596     """Check prerequisites.
11597
11598     """
11599     # Check whether any instance on this node has faulty disks
11600     for inst in _GetNodeInstances(self.cfg, self.op.node_name):
11601       if inst.admin_state != constants.ADMINST_UP:
11602         continue
11603       check_nodes = set(inst.all_nodes)
11604       check_nodes.discard(self.op.node_name)
11605       for inst_node_name in check_nodes:
11606         self._CheckFaultyDisks(inst, inst_node_name)
11607
11608   def Exec(self, feedback_fn):
11609     feedback_fn("Repairing storage unit '%s' on %s ..." %
11610                 (self.op.name, self.op.node_name))
11611
11612     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
11613     result = self.rpc.call_storage_execute(self.op.node_name,
11614                                            self.op.storage_type, st_args,
11615                                            self.op.name,
11616                                            constants.SO_FIX_CONSISTENCY)
11617     result.Raise("Failed to repair storage unit '%s' on %s" %
11618                  (self.op.name, self.op.node_name))
11619
11620
11621 class LUNodeEvacuate(NoHooksLU):
11622   """Evacuates instances off a list of nodes.
11623
11624   """
11625   REQ_BGL = False
11626
11627   _MODE2IALLOCATOR = {
11628     constants.NODE_EVAC_PRI: constants.IALLOCATOR_NEVAC_PRI,
11629     constants.NODE_EVAC_SEC: constants.IALLOCATOR_NEVAC_SEC,
11630     constants.NODE_EVAC_ALL: constants.IALLOCATOR_NEVAC_ALL,
11631     }
11632   assert frozenset(_MODE2IALLOCATOR.keys()) == constants.NODE_EVAC_MODES
11633   assert (frozenset(_MODE2IALLOCATOR.values()) ==
11634           constants.IALLOCATOR_NEVAC_MODES)
11635
11636   def CheckArguments(self):
11637     _CheckIAllocatorOrNode(self, "iallocator", "remote_node")
11638
11639   def ExpandNames(self):
11640     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
11641
11642     if self.op.remote_node is not None:
11643       self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
11644       assert self.op.remote_node
11645
11646       if self.op.remote_node == self.op.node_name:
11647         raise errors.OpPrereqError("Can not use evacuated node as a new"
11648                                    " secondary node", errors.ECODE_INVAL)
11649
11650       if self.op.mode != constants.NODE_EVAC_SEC:
11651         raise errors.OpPrereqError("Without the use of an iallocator only"
11652                                    " secondary instances can be evacuated",
11653                                    errors.ECODE_INVAL)
11654
11655     # Declare locks
11656     self.share_locks = _ShareAll()
11657     self.needed_locks = {
11658       locking.LEVEL_INSTANCE: [],
11659       locking.LEVEL_NODEGROUP: [],
11660       locking.LEVEL_NODE: [],
11661       }
11662
11663     # Determine nodes (via group) optimistically, needs verification once locks
11664     # have been acquired
11665     self.lock_nodes = self._DetermineNodes()
11666
11667   def _DetermineNodes(self):
11668     """Gets the list of nodes to operate on.
11669
11670     """
11671     if self.op.remote_node is None:
11672       # Iallocator will choose any node(s) in the same group
11673       group_nodes = self.cfg.GetNodeGroupMembersByNodes([self.op.node_name])
11674     else:
11675       group_nodes = frozenset([self.op.remote_node])
11676
11677     # Determine nodes to be locked
11678     return set([self.op.node_name]) | group_nodes
11679
11680   def _DetermineInstances(self):
11681     """Builds list of instances to operate on.
11682
11683     """
11684     assert self.op.mode in constants.NODE_EVAC_MODES
11685
11686     if self.op.mode == constants.NODE_EVAC_PRI:
11687       # Primary instances only
11688       inst_fn = _GetNodePrimaryInstances
11689       assert self.op.remote_node is None, \
11690         "Evacuating primary instances requires iallocator"
11691     elif self.op.mode == constants.NODE_EVAC_SEC:
11692       # Secondary instances only
11693       inst_fn = _GetNodeSecondaryInstances
11694     else:
11695       # All instances
11696       assert self.op.mode == constants.NODE_EVAC_ALL
11697       inst_fn = _GetNodeInstances
11698       # TODO: In 2.6, change the iallocator interface to take an evacuation mode
11699       # per instance
11700       raise errors.OpPrereqError("Due to an issue with the iallocator"
11701                                  " interface it is not possible to evacuate"
11702                                  " all instances at once; specify explicitly"
11703                                  " whether to evacuate primary or secondary"
11704                                  " instances",
11705                                  errors.ECODE_INVAL)
11706
11707     return inst_fn(self.cfg, self.op.node_name)
11708
11709   def DeclareLocks(self, level):
11710     if level == locking.LEVEL_INSTANCE:
11711       # Lock instances optimistically, needs verification once node and group
11712       # locks have been acquired
11713       self.needed_locks[locking.LEVEL_INSTANCE] = \
11714         set(i.name for i in self._DetermineInstances())
11715
11716     elif level == locking.LEVEL_NODEGROUP:
11717       # Lock node groups for all potential target nodes optimistically, needs
11718       # verification once nodes have been acquired
11719       self.needed_locks[locking.LEVEL_NODEGROUP] = \
11720         self.cfg.GetNodeGroupsFromNodes(self.lock_nodes)
11721
11722     elif level == locking.LEVEL_NODE:
11723       self.needed_locks[locking.LEVEL_NODE] = self.lock_nodes
11724
11725   def CheckPrereq(self):
11726     # Verify locks
11727     owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
11728     owned_nodes = self.owned_locks(locking.LEVEL_NODE)
11729     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
11730
11731     need_nodes = self._DetermineNodes()
11732
11733     if not owned_nodes.issuperset(need_nodes):
11734       raise errors.OpPrereqError("Nodes in same group as '%s' changed since"
11735                                  " locks were acquired, current nodes are"
11736                                  " are '%s', used to be '%s'; retry the"
11737                                  " operation" %
11738                                  (self.op.node_name,
11739                                   utils.CommaJoin(need_nodes),
11740                                   utils.CommaJoin(owned_nodes)),
11741                                  errors.ECODE_STATE)
11742
11743     wanted_groups = self.cfg.GetNodeGroupsFromNodes(owned_nodes)
11744     if owned_groups != wanted_groups:
11745       raise errors.OpExecError("Node groups changed since locks were acquired,"
11746                                " current groups are '%s', used to be '%s';"
11747                                " retry the operation" %
11748                                (utils.CommaJoin(wanted_groups),
11749                                 utils.CommaJoin(owned_groups)))
11750
11751     # Determine affected instances
11752     self.instances = self._DetermineInstances()
11753     self.instance_names = [i.name for i in self.instances]
11754
11755     if set(self.instance_names) != owned_instances:
11756       raise errors.OpExecError("Instances on node '%s' changed since locks"
11757                                " were acquired, current instances are '%s',"
11758                                " used to be '%s'; retry the operation" %
11759                                (self.op.node_name,
11760                                 utils.CommaJoin(self.instance_names),
11761                                 utils.CommaJoin(owned_instances)))
11762
11763     if self.instance_names:
11764       self.LogInfo("Evacuating instances from node '%s': %s",
11765                    self.op.node_name,
11766                    utils.CommaJoin(utils.NiceSort(self.instance_names)))
11767     else:
11768       self.LogInfo("No instances to evacuate from node '%s'",
11769                    self.op.node_name)
11770
11771     if self.op.remote_node is not None:
11772       for i in self.instances:
11773         if i.primary_node == self.op.remote_node:
11774           raise errors.OpPrereqError("Node %s is the primary node of"
11775                                      " instance %s, cannot use it as"
11776                                      " secondary" %
11777                                      (self.op.remote_node, i.name),
11778                                      errors.ECODE_INVAL)
11779
11780   def Exec(self, feedback_fn):
11781     assert (self.op.iallocator is not None) ^ (self.op.remote_node is not None)
11782
11783     if not self.instance_names:
11784       # No instances to evacuate
11785       jobs = []
11786
11787     elif self.op.iallocator is not None:
11788       # TODO: Implement relocation to other group
11789       evac_mode = self._MODE2IALLOCATOR[self.op.mode]
11790       req = iallocator.IAReqNodeEvac(evac_mode=evac_mode,
11791                                      instances=list(self.instance_names))
11792       ial = iallocator.IAllocator(self.cfg, self.rpc, req)
11793
11794       ial.Run(self.op.iallocator)
11795
11796       if not ial.success:
11797         raise errors.OpPrereqError("Can't compute node evacuation using"
11798                                    " iallocator '%s': %s" %
11799                                    (self.op.iallocator, ial.info),
11800                                    errors.ECODE_NORES)
11801
11802       jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, True)
11803
11804     elif self.op.remote_node is not None:
11805       assert self.op.mode == constants.NODE_EVAC_SEC
11806       jobs = [
11807         [opcodes.OpInstanceReplaceDisks(instance_name=instance_name,
11808                                         remote_node=self.op.remote_node,
11809                                         disks=[],
11810                                         mode=constants.REPLACE_DISK_CHG,
11811                                         early_release=self.op.early_release)]
11812         for instance_name in self.instance_names
11813         ]
11814
11815     else:
11816       raise errors.ProgrammerError("No iallocator or remote node")
11817
11818     return ResultWithJobs(jobs)
11819
11820
11821 def _SetOpEarlyRelease(early_release, op):
11822   """Sets C{early_release} flag on opcodes if available.
11823
11824   """
11825   try:
11826     op.early_release = early_release
11827   except AttributeError:
11828     assert not isinstance(op, opcodes.OpInstanceReplaceDisks)
11829
11830   return op
11831
11832
11833 def _NodeEvacDest(use_nodes, group, nodes):
11834   """Returns group or nodes depending on caller's choice.
11835
11836   """
11837   if use_nodes:
11838     return utils.CommaJoin(nodes)
11839   else:
11840     return group
11841
11842
11843 def _LoadNodeEvacResult(lu, alloc_result, early_release, use_nodes):
11844   """Unpacks the result of change-group and node-evacuate iallocator requests.
11845
11846   Iallocator modes L{constants.IALLOCATOR_MODE_NODE_EVAC} and
11847   L{constants.IALLOCATOR_MODE_CHG_GROUP}.
11848
11849   @type lu: L{LogicalUnit}
11850   @param lu: Logical unit instance
11851   @type alloc_result: tuple/list
11852   @param alloc_result: Result from iallocator
11853   @type early_release: bool
11854   @param early_release: Whether to release locks early if possible
11855   @type use_nodes: bool
11856   @param use_nodes: Whether to display node names instead of groups
11857
11858   """
11859   (moved, failed, jobs) = alloc_result
11860
11861   if failed:
11862     failreason = utils.CommaJoin("%s (%s)" % (name, reason)
11863                                  for (name, reason) in failed)
11864     lu.LogWarning("Unable to evacuate instances %s", failreason)
11865     raise errors.OpExecError("Unable to evacuate instances %s" % failreason)
11866
11867   if moved:
11868     lu.LogInfo("Instances to be moved: %s",
11869                utils.CommaJoin("%s (to %s)" %
11870                                (name, _NodeEvacDest(use_nodes, group, nodes))
11871                                for (name, group, nodes) in moved))
11872
11873   return [map(compat.partial(_SetOpEarlyRelease, early_release),
11874               map(opcodes.OpCode.LoadOpCode, ops))
11875           for ops in jobs]
11876
11877
11878 def _DiskSizeInBytesToMebibytes(lu, size):
11879   """Converts a disk size in bytes to mebibytes.
11880
11881   Warns and rounds up if the size isn't an even multiple of 1 MiB.
11882
11883   """
11884   (mib, remainder) = divmod(size, 1024 * 1024)
11885
11886   if remainder != 0:
11887     lu.LogWarning("Disk size is not an even multiple of 1 MiB; rounding up"
11888                   " to not overwrite existing data (%s bytes will not be"
11889                   " wiped)", (1024 * 1024) - remainder)
11890     mib += 1
11891
11892   return mib
11893
11894
11895 class LUInstanceGrowDisk(LogicalUnit):
11896   """Grow a disk of an instance.
11897
11898   """
11899   HPATH = "disk-grow"
11900   HTYPE = constants.HTYPE_INSTANCE
11901   REQ_BGL = False
11902
11903   def ExpandNames(self):
11904     self._ExpandAndLockInstance()
11905     self.needed_locks[locking.LEVEL_NODE] = []
11906     self.needed_locks[locking.LEVEL_NODE_RES] = []
11907     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
11908     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
11909
11910   def DeclareLocks(self, level):
11911     if level == locking.LEVEL_NODE:
11912       self._LockInstancesNodes()
11913     elif level == locking.LEVEL_NODE_RES:
11914       # Copy node locks
11915       self.needed_locks[locking.LEVEL_NODE_RES] = \
11916         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
11917
11918   def BuildHooksEnv(self):
11919     """Build hooks env.
11920
11921     This runs on the master, the primary and all the secondaries.
11922
11923     """
11924     env = {
11925       "DISK": self.op.disk,
11926       "AMOUNT": self.op.amount,
11927       "ABSOLUTE": self.op.absolute,
11928       }
11929     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
11930     return env
11931
11932   def BuildHooksNodes(self):
11933     """Build hooks nodes.
11934
11935     """
11936     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
11937     return (nl, nl)
11938
11939   def CheckPrereq(self):
11940     """Check prerequisites.
11941
11942     This checks that the instance is in the cluster.
11943
11944     """
11945     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
11946     assert instance is not None, \
11947       "Cannot retrieve locked instance %s" % self.op.instance_name
11948     nodenames = list(instance.all_nodes)
11949     for node in nodenames:
11950       _CheckNodeOnline(self, node)
11951
11952     self.instance = instance
11953
11954     if instance.disk_template not in constants.DTS_GROWABLE:
11955       raise errors.OpPrereqError("Instance's disk layout does not support"
11956                                  " growing", errors.ECODE_INVAL)
11957
11958     self.disk = instance.FindDisk(self.op.disk)
11959
11960     if self.op.absolute:
11961       self.target = self.op.amount
11962       self.delta = self.target - self.disk.size
11963       if self.delta < 0:
11964         raise errors.OpPrereqError("Requested size (%s) is smaller than "
11965                                    "current disk size (%s)" %
11966                                    (utils.FormatUnit(self.target, "h"),
11967                                     utils.FormatUnit(self.disk.size, "h")),
11968                                    errors.ECODE_STATE)
11969     else:
11970       self.delta = self.op.amount
11971       self.target = self.disk.size + self.delta
11972       if self.delta < 0:
11973         raise errors.OpPrereqError("Requested increment (%s) is negative" %
11974                                    utils.FormatUnit(self.delta, "h"),
11975                                    errors.ECODE_INVAL)
11976
11977     if instance.disk_template not in (constants.DT_FILE,
11978                                       constants.DT_SHARED_FILE,
11979                                       constants.DT_RBD):
11980       # TODO: check the free disk space for file, when that feature will be
11981       # supported
11982       _CheckNodesFreeDiskPerVG(self, nodenames,
11983                                self.disk.ComputeGrowth(self.delta))
11984
11985   def Exec(self, feedback_fn):
11986     """Execute disk grow.
11987
11988     """
11989     instance = self.instance
11990     disk = self.disk
11991
11992     assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
11993     assert (self.owned_locks(locking.LEVEL_NODE) ==
11994             self.owned_locks(locking.LEVEL_NODE_RES))
11995
11996     wipe_disks = self.cfg.GetClusterInfo().prealloc_wipe_disks
11997
11998     disks_ok, _ = _AssembleInstanceDisks(self, self.instance, disks=[disk])
11999     if not disks_ok:
12000       raise errors.OpExecError("Cannot activate block device to grow")
12001
12002     feedback_fn("Growing disk %s of instance '%s' by %s to %s" %
12003                 (self.op.disk, instance.name,
12004                  utils.FormatUnit(self.delta, "h"),
12005                  utils.FormatUnit(self.target, "h")))
12006
12007     # First run all grow ops in dry-run mode
12008     for node in instance.all_nodes:
12009       self.cfg.SetDiskID(disk, node)
12010       result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
12011                                            True, True)
12012       result.Raise("Dry-run grow request failed to node %s" % node)
12013
12014     if wipe_disks:
12015       # Get disk size from primary node for wiping
12016       result = self.rpc.call_blockdev_getsize(instance.primary_node, [disk])
12017       result.Raise("Failed to retrieve disk size from node '%s'" %
12018                    instance.primary_node)
12019
12020       (disk_size_in_bytes, ) = result.payload
12021
12022       if disk_size_in_bytes is None:
12023         raise errors.OpExecError("Failed to retrieve disk size from primary"
12024                                  " node '%s'" % instance.primary_node)
12025
12026       old_disk_size = _DiskSizeInBytesToMebibytes(self, disk_size_in_bytes)
12027
12028       assert old_disk_size >= disk.size, \
12029         ("Retrieved disk size too small (got %s, should be at least %s)" %
12030          (old_disk_size, disk.size))
12031     else:
12032       old_disk_size = None
12033
12034     # We know that (as far as we can test) operations across different
12035     # nodes will succeed, time to run it for real on the backing storage
12036     for node in instance.all_nodes:
12037       self.cfg.SetDiskID(disk, node)
12038       result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
12039                                            False, True)
12040       result.Raise("Grow request failed to node %s" % node)
12041
12042     # And now execute it for logical storage, on the primary node
12043     node = instance.primary_node
12044     self.cfg.SetDiskID(disk, node)
12045     result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
12046                                          False, False)
12047     result.Raise("Grow request failed to node %s" % node)
12048
12049     disk.RecordGrow(self.delta)
12050     self.cfg.Update(instance, feedback_fn)
12051
12052     # Changes have been recorded, release node lock
12053     _ReleaseLocks(self, locking.LEVEL_NODE)
12054
12055     # Downgrade lock while waiting for sync
12056     self.glm.downgrade(locking.LEVEL_INSTANCE)
12057
12058     assert wipe_disks ^ (old_disk_size is None)
12059
12060     if wipe_disks:
12061       assert instance.disks[self.op.disk] == disk
12062
12063       # Wipe newly added disk space
12064       _WipeDisks(self, instance,
12065                  disks=[(self.op.disk, disk, old_disk_size)])
12066
12067     if self.op.wait_for_sync:
12068       disk_abort = not _WaitForSync(self, instance, disks=[disk])
12069       if disk_abort:
12070         self.proc.LogWarning("Disk sync-ing has not returned a good"
12071                              " status; please check the instance")
12072       if instance.admin_state != constants.ADMINST_UP:
12073         _SafeShutdownInstanceDisks(self, instance, disks=[disk])
12074     elif instance.admin_state != constants.ADMINST_UP:
12075       self.proc.LogWarning("Not shutting down the disk even if the instance is"
12076                            " not supposed to be running because no wait for"
12077                            " sync mode was requested")
12078
12079     assert self.owned_locks(locking.LEVEL_NODE_RES)
12080     assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
12081
12082
12083 class LUInstanceQueryData(NoHooksLU):
12084   """Query runtime instance data.
12085
12086   """
12087   REQ_BGL = False
12088
12089   def ExpandNames(self):
12090     self.needed_locks = {}
12091
12092     # Use locking if requested or when non-static information is wanted
12093     if not (self.op.static or self.op.use_locking):
12094       self.LogWarning("Non-static data requested, locks need to be acquired")
12095       self.op.use_locking = True
12096
12097     if self.op.instances or not self.op.use_locking:
12098       # Expand instance names right here
12099       self.wanted_names = _GetWantedInstances(self, self.op.instances)
12100     else:
12101       # Will use acquired locks
12102       self.wanted_names = None
12103
12104     if self.op.use_locking:
12105       self.share_locks = _ShareAll()
12106
12107       if self.wanted_names is None:
12108         self.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET
12109       else:
12110         self.needed_locks[locking.LEVEL_INSTANCE] = self.wanted_names
12111
12112       self.needed_locks[locking.LEVEL_NODEGROUP] = []
12113       self.needed_locks[locking.LEVEL_NODE] = []
12114       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
12115
12116   def DeclareLocks(self, level):
12117     if self.op.use_locking:
12118       if level == locking.LEVEL_NODEGROUP:
12119         owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
12120
12121         # Lock all groups used by instances optimistically; this requires going
12122         # via the node before it's locked, requiring verification later on
12123         self.needed_locks[locking.LEVEL_NODEGROUP] = \
12124           frozenset(group_uuid
12125                     for instance_name in owned_instances
12126                     for group_uuid in
12127                       self.cfg.GetInstanceNodeGroups(instance_name))
12128
12129       elif level == locking.LEVEL_NODE:
12130         self._LockInstancesNodes()
12131
12132   def CheckPrereq(self):
12133     """Check prerequisites.
12134
12135     This only checks the optional instance list against the existing names.
12136
12137     """
12138     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
12139     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
12140     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
12141
12142     if self.wanted_names is None:
12143       assert self.op.use_locking, "Locking was not used"
12144       self.wanted_names = owned_instances
12145
12146     instances = dict(self.cfg.GetMultiInstanceInfo(self.wanted_names))
12147
12148     if self.op.use_locking:
12149       _CheckInstancesNodeGroups(self.cfg, instances, owned_groups, owned_nodes,
12150                                 None)
12151     else:
12152       assert not (owned_instances or owned_groups or owned_nodes)
12153
12154     self.wanted_instances = instances.values()
12155
12156   def _ComputeBlockdevStatus(self, node, instance, dev):
12157     """Returns the status of a block device
12158
12159     """
12160     if self.op.static or not node:
12161       return None
12162
12163     self.cfg.SetDiskID(dev, node)
12164
12165     result = self.rpc.call_blockdev_find(node, dev)
12166     if result.offline:
12167       return None
12168
12169     result.Raise("Can't compute disk status for %s" % instance.name)
12170
12171     status = result.payload
12172     if status is None:
12173       return None
12174
12175     return (status.dev_path, status.major, status.minor,
12176             status.sync_percent, status.estimated_time,
12177             status.is_degraded, status.ldisk_status)
12178
12179   def _ComputeDiskStatus(self, instance, snode, dev):
12180     """Compute block device status.
12181
12182     """
12183     (anno_dev,) = _AnnotateDiskParams(instance, [dev], self.cfg)
12184
12185     return self._ComputeDiskStatusInner(instance, snode, anno_dev)
12186
12187   def _ComputeDiskStatusInner(self, instance, snode, dev):
12188     """Compute block device status.
12189
12190     @attention: The device has to be annotated already.
12191
12192     """
12193     if dev.dev_type in constants.LDS_DRBD:
12194       # we change the snode then (otherwise we use the one passed in)
12195       if dev.logical_id[0] == instance.primary_node:
12196         snode = dev.logical_id[1]
12197       else:
12198         snode = dev.logical_id[0]
12199
12200     dev_pstatus = self._ComputeBlockdevStatus(instance.primary_node,
12201                                               instance, dev)
12202     dev_sstatus = self._ComputeBlockdevStatus(snode, instance, dev)
12203
12204     if dev.children:
12205       dev_children = map(compat.partial(self._ComputeDiskStatusInner,
12206                                         instance, snode),
12207                          dev.children)
12208     else:
12209       dev_children = []
12210
12211     return {
12212       "iv_name": dev.iv_name,
12213       "dev_type": dev.dev_type,
12214       "logical_id": dev.logical_id,
12215       "physical_id": dev.physical_id,
12216       "pstatus": dev_pstatus,
12217       "sstatus": dev_sstatus,
12218       "children": dev_children,
12219       "mode": dev.mode,
12220       "size": dev.size,
12221       }
12222
12223   def Exec(self, feedback_fn):
12224     """Gather and return data"""
12225     result = {}
12226
12227     cluster = self.cfg.GetClusterInfo()
12228
12229     node_names = itertools.chain(*(i.all_nodes for i in self.wanted_instances))
12230     nodes = dict(self.cfg.GetMultiNodeInfo(node_names))
12231
12232     groups = dict(self.cfg.GetMultiNodeGroupInfo(node.group
12233                                                  for node in nodes.values()))
12234
12235     group2name_fn = lambda uuid: groups[uuid].name
12236
12237     for instance in self.wanted_instances:
12238       pnode = nodes[instance.primary_node]
12239
12240       if self.op.static or pnode.offline:
12241         remote_state = None
12242         if pnode.offline:
12243           self.LogWarning("Primary node %s is marked offline, returning static"
12244                           " information only for instance %s" %
12245                           (pnode.name, instance.name))
12246       else:
12247         remote_info = self.rpc.call_instance_info(instance.primary_node,
12248                                                   instance.name,
12249                                                   instance.hypervisor)
12250         remote_info.Raise("Error checking node %s" % instance.primary_node)
12251         remote_info = remote_info.payload
12252         if remote_info and "state" in remote_info:
12253           remote_state = "up"
12254         else:
12255           if instance.admin_state == constants.ADMINST_UP:
12256             remote_state = "down"
12257           else:
12258             remote_state = instance.admin_state
12259
12260       disks = map(compat.partial(self._ComputeDiskStatus, instance, None),
12261                   instance.disks)
12262
12263       snodes_group_uuids = [nodes[snode_name].group
12264                             for snode_name in instance.secondary_nodes]
12265
12266       result[instance.name] = {
12267         "name": instance.name,
12268         "config_state": instance.admin_state,
12269         "run_state": remote_state,
12270         "pnode": instance.primary_node,
12271         "pnode_group_uuid": pnode.group,
12272         "pnode_group_name": group2name_fn(pnode.group),
12273         "snodes": instance.secondary_nodes,
12274         "snodes_group_uuids": snodes_group_uuids,
12275         "snodes_group_names": map(group2name_fn, snodes_group_uuids),
12276         "os": instance.os,
12277         # this happens to be the same format used for hooks
12278         "nics": _NICListToTuple(self, instance.nics),
12279         "disk_template": instance.disk_template,
12280         "disks": disks,
12281         "hypervisor": instance.hypervisor,
12282         "network_port": instance.network_port,
12283         "hv_instance": instance.hvparams,
12284         "hv_actual": cluster.FillHV(instance, skip_globals=True),
12285         "be_instance": instance.beparams,
12286         "be_actual": cluster.FillBE(instance),
12287         "os_instance": instance.osparams,
12288         "os_actual": cluster.SimpleFillOS(instance.os, instance.osparams),
12289         "serial_no": instance.serial_no,
12290         "mtime": instance.mtime,
12291         "ctime": instance.ctime,
12292         "uuid": instance.uuid,
12293         }
12294
12295     return result
12296
12297
12298 def PrepareContainerMods(mods, private_fn):
12299   """Prepares a list of container modifications by adding a private data field.
12300
12301   @type mods: list of tuples; (operation, index, parameters)
12302   @param mods: List of modifications
12303   @type private_fn: callable or None
12304   @param private_fn: Callable for constructing a private data field for a
12305     modification
12306   @rtype: list
12307
12308   """
12309   if private_fn is None:
12310     fn = lambda: None
12311   else:
12312     fn = private_fn
12313
12314   return [(op, idx, params, fn()) for (op, idx, params) in mods]
12315
12316
12317 #: Type description for changes as returned by L{ApplyContainerMods}'s
12318 #: callbacks
12319 _TApplyContModsCbChanges = \
12320   ht.TMaybeListOf(ht.TAnd(ht.TIsLength(2), ht.TItems([
12321     ht.TNonEmptyString,
12322     ht.TAny,
12323     ])))
12324
12325
12326 def ApplyContainerMods(kind, container, chgdesc, mods,
12327                        create_fn, modify_fn, remove_fn):
12328   """Applies descriptions in C{mods} to C{container}.
12329
12330   @type kind: string
12331   @param kind: One-word item description
12332   @type container: list
12333   @param container: Container to modify
12334   @type chgdesc: None or list
12335   @param chgdesc: List of applied changes
12336   @type mods: list
12337   @param mods: Modifications as returned by L{PrepareContainerMods}
12338   @type create_fn: callable
12339   @param create_fn: Callback for creating a new item (L{constants.DDM_ADD});
12340     receives absolute item index, parameters and private data object as added
12341     by L{PrepareContainerMods}, returns tuple containing new item and changes
12342     as list
12343   @type modify_fn: callable
12344   @param modify_fn: Callback for modifying an existing item
12345     (L{constants.DDM_MODIFY}); receives absolute item index, item, parameters
12346     and private data object as added by L{PrepareContainerMods}, returns
12347     changes as list
12348   @type remove_fn: callable
12349   @param remove_fn: Callback on removing item; receives absolute item index,
12350     item and private data object as added by L{PrepareContainerMods}
12351
12352   """
12353   for (op, idx, params, private) in mods:
12354     if idx == -1:
12355       # Append
12356       absidx = len(container) - 1
12357     elif idx < 0:
12358       raise IndexError("Not accepting negative indices other than -1")
12359     elif idx > len(container):
12360       raise IndexError("Got %s index %s, but there are only %s" %
12361                        (kind, idx, len(container)))
12362     else:
12363       absidx = idx
12364
12365     changes = None
12366
12367     if op == constants.DDM_ADD:
12368       # Calculate where item will be added
12369       if idx == -1:
12370         addidx = len(container)
12371       else:
12372         addidx = idx
12373
12374       if create_fn is None:
12375         item = params
12376       else:
12377         (item, changes) = create_fn(addidx, params, private)
12378
12379       if idx == -1:
12380         container.append(item)
12381       else:
12382         assert idx >= 0
12383         assert idx <= len(container)
12384         # list.insert does so before the specified index
12385         container.insert(idx, item)
12386     else:
12387       # Retrieve existing item
12388       try:
12389         item = container[absidx]
12390       except IndexError:
12391         raise IndexError("Invalid %s index %s" % (kind, idx))
12392
12393       if op == constants.DDM_REMOVE:
12394         assert not params
12395
12396         if remove_fn is not None:
12397           remove_fn(absidx, item, private)
12398
12399         changes = [("%s/%s" % (kind, absidx), "remove")]
12400
12401         assert container[absidx] == item
12402         del container[absidx]
12403       elif op == constants.DDM_MODIFY:
12404         if modify_fn is not None:
12405           changes = modify_fn(absidx, item, params, private)
12406       else:
12407         raise errors.ProgrammerError("Unhandled operation '%s'" % op)
12408
12409     assert _TApplyContModsCbChanges(changes)
12410
12411     if not (chgdesc is None or changes is None):
12412       chgdesc.extend(changes)
12413
12414
12415 def _UpdateIvNames(base_index, disks):
12416   """Updates the C{iv_name} attribute of disks.
12417
12418   @type disks: list of L{objects.Disk}
12419
12420   """
12421   for (idx, disk) in enumerate(disks):
12422     disk.iv_name = "disk/%s" % (base_index + idx, )
12423
12424
12425 class _InstNicModPrivate:
12426   """Data structure for network interface modifications.
12427
12428   Used by L{LUInstanceSetParams}.
12429
12430   """
12431   def __init__(self):
12432     self.params = None
12433     self.filled = None
12434
12435
12436 class LUInstanceSetParams(LogicalUnit):
12437   """Modifies an instances's parameters.
12438
12439   """
12440   HPATH = "instance-modify"
12441   HTYPE = constants.HTYPE_INSTANCE
12442   REQ_BGL = False
12443
12444   @staticmethod
12445   def _UpgradeDiskNicMods(kind, mods, verify_fn):
12446     assert ht.TList(mods)
12447     assert not mods or len(mods[0]) in (2, 3)
12448
12449     if mods and len(mods[0]) == 2:
12450       result = []
12451
12452       addremove = 0
12453       for op, params in mods:
12454         if op in (constants.DDM_ADD, constants.DDM_REMOVE):
12455           result.append((op, -1, params))
12456           addremove += 1
12457
12458           if addremove > 1:
12459             raise errors.OpPrereqError("Only one %s add or remove operation is"
12460                                        " supported at a time" % kind,
12461                                        errors.ECODE_INVAL)
12462         else:
12463           result.append((constants.DDM_MODIFY, op, params))
12464
12465       assert verify_fn(result)
12466     else:
12467       result = mods
12468
12469     return result
12470
12471   @staticmethod
12472   def _CheckMods(kind, mods, key_types, item_fn):
12473     """Ensures requested disk/NIC modifications are valid.
12474
12475     """
12476     for (op, _, params) in mods:
12477       assert ht.TDict(params)
12478
12479       utils.ForceDictType(params, key_types)
12480
12481       if op == constants.DDM_REMOVE:
12482         if params:
12483           raise errors.OpPrereqError("No settings should be passed when"
12484                                      " removing a %s" % kind,
12485                                      errors.ECODE_INVAL)
12486       elif op in (constants.DDM_ADD, constants.DDM_MODIFY):
12487         item_fn(op, params)
12488       else:
12489         raise errors.ProgrammerError("Unhandled operation '%s'" % op)
12490
12491   @staticmethod
12492   def _VerifyDiskModification(op, params):
12493     """Verifies a disk modification.
12494
12495     """
12496     if op == constants.DDM_ADD:
12497       mode = params.setdefault(constants.IDISK_MODE, constants.DISK_RDWR)
12498       if mode not in constants.DISK_ACCESS_SET:
12499         raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
12500                                    errors.ECODE_INVAL)
12501
12502       size = params.get(constants.IDISK_SIZE, None)
12503       if size is None:
12504         raise errors.OpPrereqError("Required disk parameter '%s' missing" %
12505                                    constants.IDISK_SIZE, errors.ECODE_INVAL)
12506
12507       try:
12508         size = int(size)
12509       except (TypeError, ValueError), err:
12510         raise errors.OpPrereqError("Invalid disk size parameter: %s" % err,
12511                                    errors.ECODE_INVAL)
12512
12513       params[constants.IDISK_SIZE] = size
12514
12515     elif op == constants.DDM_MODIFY and constants.IDISK_SIZE in params:
12516       raise errors.OpPrereqError("Disk size change not possible, use"
12517                                  " grow-disk", errors.ECODE_INVAL)
12518
12519   @staticmethod
12520   def _VerifyNicModification(op, params):
12521     """Verifies a network interface modification.
12522
12523     """
12524     if op in (constants.DDM_ADD, constants.DDM_MODIFY):
12525       ip = params.get(constants.INIC_IP, None)
12526       if ip is None:
12527         pass
12528       elif ip.lower() == constants.VALUE_NONE:
12529         params[constants.INIC_IP] = None
12530       elif not netutils.IPAddress.IsValid(ip):
12531         raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
12532                                    errors.ECODE_INVAL)
12533
12534       bridge = params.get("bridge", None)
12535       link = params.get(constants.INIC_LINK, None)
12536       if bridge and link:
12537         raise errors.OpPrereqError("Cannot pass 'bridge' and 'link'"
12538                                    " at the same time", errors.ECODE_INVAL)
12539       elif bridge and bridge.lower() == constants.VALUE_NONE:
12540         params["bridge"] = None
12541       elif link and link.lower() == constants.VALUE_NONE:
12542         params[constants.INIC_LINK] = None
12543
12544       if op == constants.DDM_ADD:
12545         macaddr = params.get(constants.INIC_MAC, None)
12546         if macaddr is None:
12547           params[constants.INIC_MAC] = constants.VALUE_AUTO
12548
12549       if constants.INIC_MAC in params:
12550         macaddr = params[constants.INIC_MAC]
12551         if macaddr not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
12552           macaddr = utils.NormalizeAndValidateMac(macaddr)
12553
12554         if op == constants.DDM_MODIFY and macaddr == constants.VALUE_AUTO:
12555           raise errors.OpPrereqError("'auto' is not a valid MAC address when"
12556                                      " modifying an existing NIC",
12557                                      errors.ECODE_INVAL)
12558
12559   def CheckArguments(self):
12560     if not (self.op.nics or self.op.disks or self.op.disk_template or
12561             self.op.hvparams or self.op.beparams or self.op.os_name or
12562             self.op.offline is not None or self.op.runtime_mem):
12563       raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
12564
12565     if self.op.hvparams:
12566       _CheckGlobalHvParams(self.op.hvparams)
12567
12568     self.op.disks = self._UpgradeDiskNicMods(
12569       "disk", self.op.disks, opcodes.OpInstanceSetParams.TestDiskModifications)
12570     self.op.nics = self._UpgradeDiskNicMods(
12571       "NIC", self.op.nics, opcodes.OpInstanceSetParams.TestNicModifications)
12572
12573     # Check disk modifications
12574     self._CheckMods("disk", self.op.disks, constants.IDISK_PARAMS_TYPES,
12575                     self._VerifyDiskModification)
12576
12577     if self.op.disks and self.op.disk_template is not None:
12578       raise errors.OpPrereqError("Disk template conversion and other disk"
12579                                  " changes not supported at the same time",
12580                                  errors.ECODE_INVAL)
12581
12582     if (self.op.disk_template and
12583         self.op.disk_template in constants.DTS_INT_MIRROR and
12584         self.op.remote_node is None):
12585       raise errors.OpPrereqError("Changing the disk template to a mirrored"
12586                                  " one requires specifying a secondary node",
12587                                  errors.ECODE_INVAL)
12588
12589     # Check NIC modifications
12590     self._CheckMods("NIC", self.op.nics, constants.INIC_PARAMS_TYPES,
12591                     self._VerifyNicModification)
12592
12593   def ExpandNames(self):
12594     self._ExpandAndLockInstance()
12595     # Can't even acquire node locks in shared mode as upcoming changes in
12596     # Ganeti 2.6 will start to modify the node object on disk conversion
12597     self.needed_locks[locking.LEVEL_NODE] = []
12598     self.needed_locks[locking.LEVEL_NODE_RES] = []
12599     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
12600
12601   def DeclareLocks(self, level):
12602     # TODO: Acquire group lock in shared mode (disk parameters)
12603     if level == locking.LEVEL_NODE:
12604       self._LockInstancesNodes()
12605       if self.op.disk_template and self.op.remote_node:
12606         self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
12607         self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
12608     elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
12609       # Copy node locks
12610       self.needed_locks[locking.LEVEL_NODE_RES] = \
12611         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
12612
12613   def BuildHooksEnv(self):
12614     """Build hooks env.
12615
12616     This runs on the master, primary and secondaries.
12617
12618     """
12619     args = dict()
12620     if constants.BE_MINMEM in self.be_new:
12621       args["minmem"] = self.be_new[constants.BE_MINMEM]
12622     if constants.BE_MAXMEM in self.be_new:
12623       args["maxmem"] = self.be_new[constants.BE_MAXMEM]
12624     if constants.BE_VCPUS in self.be_new:
12625       args["vcpus"] = self.be_new[constants.BE_VCPUS]
12626     # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
12627     # information at all.
12628
12629     if self._new_nics is not None:
12630       nics = []
12631
12632       for nic in self._new_nics:
12633         nicparams = self.cluster.SimpleFillNIC(nic.nicparams)
12634         mode = nicparams[constants.NIC_MODE]
12635         link = nicparams[constants.NIC_LINK]
12636         nics.append((nic.ip, nic.mac, mode, link))
12637
12638       args["nics"] = nics
12639
12640     env = _BuildInstanceHookEnvByObject(self, self.instance, override=args)
12641     if self.op.disk_template:
12642       env["NEW_DISK_TEMPLATE"] = self.op.disk_template
12643     if self.op.runtime_mem:
12644       env["RUNTIME_MEMORY"] = self.op.runtime_mem
12645
12646     return env
12647
12648   def BuildHooksNodes(self):
12649     """Build hooks nodes.
12650
12651     """
12652     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
12653     return (nl, nl)
12654
12655   def _PrepareNicModification(self, params, private, old_ip, old_params,
12656                               cluster, pnode):
12657     update_params_dict = dict([(key, params[key])
12658                                for key in constants.NICS_PARAMETERS
12659                                if key in params])
12660
12661     if "bridge" in params:
12662       update_params_dict[constants.NIC_LINK] = params["bridge"]
12663
12664     new_params = _GetUpdatedParams(old_params, update_params_dict)
12665     utils.ForceDictType(new_params, constants.NICS_PARAMETER_TYPES)
12666
12667     new_filled_params = cluster.SimpleFillNIC(new_params)
12668     objects.NIC.CheckParameterSyntax(new_filled_params)
12669
12670     new_mode = new_filled_params[constants.NIC_MODE]
12671     if new_mode == constants.NIC_MODE_BRIDGED:
12672       bridge = new_filled_params[constants.NIC_LINK]
12673       msg = self.rpc.call_bridges_exist(pnode, [bridge]).fail_msg
12674       if msg:
12675         msg = "Error checking bridges on node '%s': %s" % (pnode, msg)
12676         if self.op.force:
12677           self.warn.append(msg)
12678         else:
12679           raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
12680
12681     elif new_mode == constants.NIC_MODE_ROUTED:
12682       ip = params.get(constants.INIC_IP, old_ip)
12683       if ip is None:
12684         raise errors.OpPrereqError("Cannot set the NIC IP address to None"
12685                                    " on a routed NIC", errors.ECODE_INVAL)
12686
12687     if constants.INIC_MAC in params:
12688       mac = params[constants.INIC_MAC]
12689       if mac is None:
12690         raise errors.OpPrereqError("Cannot unset the NIC MAC address",
12691                                    errors.ECODE_INVAL)
12692       elif mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
12693         # otherwise generate the MAC address
12694         params[constants.INIC_MAC] = \
12695           self.cfg.GenerateMAC(self.proc.GetECId())
12696       else:
12697         # or validate/reserve the current one
12698         try:
12699           self.cfg.ReserveMAC(mac, self.proc.GetECId())
12700         except errors.ReservationError:
12701           raise errors.OpPrereqError("MAC address '%s' already in use"
12702                                      " in cluster" % mac,
12703                                      errors.ECODE_NOTUNIQUE)
12704
12705     private.params = new_params
12706     private.filled = new_filled_params
12707
12708   def CheckPrereq(self):
12709     """Check prerequisites.
12710
12711     This only checks the instance list against the existing names.
12712
12713     """
12714     # checking the new params on the primary/secondary nodes
12715
12716     instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
12717     cluster = self.cluster = self.cfg.GetClusterInfo()
12718     assert self.instance is not None, \
12719       "Cannot retrieve locked instance %s" % self.op.instance_name
12720     pnode = instance.primary_node
12721     nodelist = list(instance.all_nodes)
12722     pnode_info = self.cfg.GetNodeInfo(pnode)
12723     self.diskparams = self.cfg.GetInstanceDiskParams(instance)
12724
12725     # Prepare disk/NIC modifications
12726     self.diskmod = PrepareContainerMods(self.op.disks, None)
12727     self.nicmod = PrepareContainerMods(self.op.nics, _InstNicModPrivate)
12728
12729     # OS change
12730     if self.op.os_name and not self.op.force:
12731       _CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
12732                       self.op.force_variant)
12733       instance_os = self.op.os_name
12734     else:
12735       instance_os = instance.os
12736
12737     assert not (self.op.disk_template and self.op.disks), \
12738       "Can't modify disk template and apply disk changes at the same time"
12739
12740     if self.op.disk_template:
12741       if instance.disk_template == self.op.disk_template:
12742         raise errors.OpPrereqError("Instance already has disk template %s" %
12743                                    instance.disk_template, errors.ECODE_INVAL)
12744
12745       if (instance.disk_template,
12746           self.op.disk_template) not in self._DISK_CONVERSIONS:
12747         raise errors.OpPrereqError("Unsupported disk template conversion from"
12748                                    " %s to %s" % (instance.disk_template,
12749                                                   self.op.disk_template),
12750                                    errors.ECODE_INVAL)
12751       _CheckInstanceState(self, instance, INSTANCE_DOWN,
12752                           msg="cannot change disk template")
12753       if self.op.disk_template in constants.DTS_INT_MIRROR:
12754         if self.op.remote_node == pnode:
12755           raise errors.OpPrereqError("Given new secondary node %s is the same"
12756                                      " as the primary node of the instance" %
12757                                      self.op.remote_node, errors.ECODE_STATE)
12758         _CheckNodeOnline(self, self.op.remote_node)
12759         _CheckNodeNotDrained(self, self.op.remote_node)
12760         # FIXME: here we assume that the old instance type is DT_PLAIN
12761         assert instance.disk_template == constants.DT_PLAIN
12762         disks = [{constants.IDISK_SIZE: d.size,
12763                   constants.IDISK_VG: d.logical_id[0]}
12764                  for d in instance.disks]
12765         required = _ComputeDiskSizePerVG(self.op.disk_template, disks)
12766         _CheckNodesFreeDiskPerVG(self, [self.op.remote_node], required)
12767
12768         snode_info = self.cfg.GetNodeInfo(self.op.remote_node)
12769         snode_group = self.cfg.GetNodeGroup(snode_info.group)
12770         ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster,
12771                                                                 snode_group)
12772         _CheckTargetNodeIPolicy(self, ipolicy, instance, snode_info,
12773                                 ignore=self.op.ignore_ipolicy)
12774         if pnode_info.group != snode_info.group:
12775           self.LogWarning("The primary and secondary nodes are in two"
12776                           " different node groups; the disk parameters"
12777                           " from the first disk's node group will be"
12778                           " used")
12779
12780     # hvparams processing
12781     if self.op.hvparams:
12782       hv_type = instance.hypervisor
12783       i_hvdict = _GetUpdatedParams(instance.hvparams, self.op.hvparams)
12784       utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
12785       hv_new = cluster.SimpleFillHV(hv_type, instance.os, i_hvdict)
12786
12787       # local check
12788       hypervisor.GetHypervisor(hv_type).CheckParameterSyntax(hv_new)
12789       _CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
12790       self.hv_proposed = self.hv_new = hv_new # the new actual values
12791       self.hv_inst = i_hvdict # the new dict (without defaults)
12792     else:
12793       self.hv_proposed = cluster.SimpleFillHV(instance.hypervisor, instance.os,
12794                                               instance.hvparams)
12795       self.hv_new = self.hv_inst = {}
12796
12797     # beparams processing
12798     if self.op.beparams:
12799       i_bedict = _GetUpdatedParams(instance.beparams, self.op.beparams,
12800                                    use_none=True)
12801       objects.UpgradeBeParams(i_bedict)
12802       utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
12803       be_new = cluster.SimpleFillBE(i_bedict)
12804       self.be_proposed = self.be_new = be_new # the new actual values
12805       self.be_inst = i_bedict # the new dict (without defaults)
12806     else:
12807       self.be_new = self.be_inst = {}
12808       self.be_proposed = cluster.SimpleFillBE(instance.beparams)
12809     be_old = cluster.FillBE(instance)
12810
12811     # CPU param validation -- checking every time a parameter is
12812     # changed to cover all cases where either CPU mask or vcpus have
12813     # changed
12814     if (constants.BE_VCPUS in self.be_proposed and
12815         constants.HV_CPU_MASK in self.hv_proposed):
12816       cpu_list = \
12817         utils.ParseMultiCpuMask(self.hv_proposed[constants.HV_CPU_MASK])
12818       # Verify mask is consistent with number of vCPUs. Can skip this
12819       # test if only 1 entry in the CPU mask, which means same mask
12820       # is applied to all vCPUs.
12821       if (len(cpu_list) > 1 and
12822           len(cpu_list) != self.be_proposed[constants.BE_VCPUS]):
12823         raise errors.OpPrereqError("Number of vCPUs [%d] does not match the"
12824                                    " CPU mask [%s]" %
12825                                    (self.be_proposed[constants.BE_VCPUS],
12826                                     self.hv_proposed[constants.HV_CPU_MASK]),
12827                                    errors.ECODE_INVAL)
12828
12829       # Only perform this test if a new CPU mask is given
12830       if constants.HV_CPU_MASK in self.hv_new:
12831         # Calculate the largest CPU number requested
12832         max_requested_cpu = max(map(max, cpu_list))
12833         # Check that all of the instance's nodes have enough physical CPUs to
12834         # satisfy the requested CPU mask
12835         _CheckNodesPhysicalCPUs(self, instance.all_nodes,
12836                                 max_requested_cpu + 1, instance.hypervisor)
12837
12838     # osparams processing
12839     if self.op.osparams:
12840       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
12841       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
12842       self.os_inst = i_osdict # the new dict (without defaults)
12843     else:
12844       self.os_inst = {}
12845
12846     self.warn = []
12847
12848     #TODO(dynmem): do the appropriate check involving MINMEM
12849     if (constants.BE_MAXMEM in self.op.beparams and not self.op.force and
12850         be_new[constants.BE_MAXMEM] > be_old[constants.BE_MAXMEM]):
12851       mem_check_list = [pnode]
12852       if be_new[constants.BE_AUTO_BALANCE]:
12853         # either we changed auto_balance to yes or it was from before
12854         mem_check_list.extend(instance.secondary_nodes)
12855       instance_info = self.rpc.call_instance_info(pnode, instance.name,
12856                                                   instance.hypervisor)
12857       nodeinfo = self.rpc.call_node_info(mem_check_list, None,
12858                                          [instance.hypervisor])
12859       pninfo = nodeinfo[pnode]
12860       msg = pninfo.fail_msg
12861       if msg:
12862         # Assume the primary node is unreachable and go ahead
12863         self.warn.append("Can't get info from primary node %s: %s" %
12864                          (pnode, msg))
12865       else:
12866         (_, _, (pnhvinfo, )) = pninfo.payload
12867         if not isinstance(pnhvinfo.get("memory_free", None), int):
12868           self.warn.append("Node data from primary node %s doesn't contain"
12869                            " free memory information" % pnode)
12870         elif instance_info.fail_msg:
12871           self.warn.append("Can't get instance runtime information: %s" %
12872                            instance_info.fail_msg)
12873         else:
12874           if instance_info.payload:
12875             current_mem = int(instance_info.payload["memory"])
12876           else:
12877             # Assume instance not running
12878             # (there is a slight race condition here, but it's not very
12879             # probable, and we have no other way to check)
12880             # TODO: Describe race condition
12881             current_mem = 0
12882           #TODO(dynmem): do the appropriate check involving MINMEM
12883           miss_mem = (be_new[constants.BE_MAXMEM] - current_mem -
12884                       pnhvinfo["memory_free"])
12885           if miss_mem > 0:
12886             raise errors.OpPrereqError("This change will prevent the instance"
12887                                        " from starting, due to %d MB of memory"
12888                                        " missing on its primary node" %
12889                                        miss_mem, errors.ECODE_NORES)
12890
12891       if be_new[constants.BE_AUTO_BALANCE]:
12892         for node, nres in nodeinfo.items():
12893           if node not in instance.secondary_nodes:
12894             continue
12895           nres.Raise("Can't get info from secondary node %s" % node,
12896                      prereq=True, ecode=errors.ECODE_STATE)
12897           (_, _, (nhvinfo, )) = nres.payload
12898           if not isinstance(nhvinfo.get("memory_free", None), int):
12899             raise errors.OpPrereqError("Secondary node %s didn't return free"
12900                                        " memory information" % node,
12901                                        errors.ECODE_STATE)
12902           #TODO(dynmem): do the appropriate check involving MINMEM
12903           elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
12904             raise errors.OpPrereqError("This change will prevent the instance"
12905                                        " from failover to its secondary node"
12906                                        " %s, due to not enough memory" % node,
12907                                        errors.ECODE_STATE)
12908
12909     if self.op.runtime_mem:
12910       remote_info = self.rpc.call_instance_info(instance.primary_node,
12911                                                 instance.name,
12912                                                 instance.hypervisor)
12913       remote_info.Raise("Error checking node %s" % instance.primary_node)
12914       if not remote_info.payload: # not running already
12915         raise errors.OpPrereqError("Instance %s is not running" %
12916                                    instance.name, errors.ECODE_STATE)
12917
12918       current_memory = remote_info.payload["memory"]
12919       if (not self.op.force and
12920            (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or
12921             self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])):
12922         raise errors.OpPrereqError("Instance %s must have memory between %d"
12923                                    " and %d MB of memory unless --force is"
12924                                    " given" %
12925                                    (instance.name,
12926                                     self.be_proposed[constants.BE_MINMEM],
12927                                     self.be_proposed[constants.BE_MAXMEM]),
12928                                    errors.ECODE_INVAL)
12929
12930       if self.op.runtime_mem > current_memory:
12931         _CheckNodeFreeMemory(self, instance.primary_node,
12932                              "ballooning memory for instance %s" %
12933                              instance.name,
12934                              self.op.memory - current_memory,
12935                              instance.hypervisor)
12936
12937     if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
12938       raise errors.OpPrereqError("Disk operations not supported for"
12939                                  " diskless instances", errors.ECODE_INVAL)
12940
12941     def _PrepareNicCreate(_, params, private):
12942       self._PrepareNicModification(params, private, None, {}, cluster, pnode)
12943       return (None, None)
12944
12945     def _PrepareNicMod(_, nic, params, private):
12946       self._PrepareNicModification(params, private, nic.ip,
12947                                    nic.nicparams, cluster, pnode)
12948       return None
12949
12950     # Verify NIC changes (operating on copy)
12951     nics = instance.nics[:]
12952     ApplyContainerMods("NIC", nics, None, self.nicmod,
12953                        _PrepareNicCreate, _PrepareNicMod, None)
12954     if len(nics) > constants.MAX_NICS:
12955       raise errors.OpPrereqError("Instance has too many network interfaces"
12956                                  " (%d), cannot add more" % constants.MAX_NICS,
12957                                  errors.ECODE_STATE)
12958
12959     # Verify disk changes (operating on a copy)
12960     disks = instance.disks[:]
12961     ApplyContainerMods("disk", disks, None, self.diskmod, None, None, None)
12962     if len(disks) > constants.MAX_DISKS:
12963       raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
12964                                  " more" % constants.MAX_DISKS,
12965                                  errors.ECODE_STATE)
12966
12967     if self.op.offline is not None:
12968       if self.op.offline:
12969         msg = "can't change to offline"
12970       else:
12971         msg = "can't change to online"
12972       _CheckInstanceState(self, instance, CAN_CHANGE_INSTANCE_OFFLINE, msg=msg)
12973
12974     # Pre-compute NIC changes (necessary to use result in hooks)
12975     self._nic_chgdesc = []
12976     if self.nicmod:
12977       # Operate on copies as this is still in prereq
12978       nics = [nic.Copy() for nic in instance.nics]
12979       ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
12980                          self._CreateNewNic, self._ApplyNicMods, None)
12981       self._new_nics = nics
12982     else:
12983       self._new_nics = None
12984
12985   def _ConvertPlainToDrbd(self, feedback_fn):
12986     """Converts an instance from plain to drbd.
12987
12988     """
12989     feedback_fn("Converting template to drbd")
12990     instance = self.instance
12991     pnode = instance.primary_node
12992     snode = self.op.remote_node
12993
12994     assert instance.disk_template == constants.DT_PLAIN
12995
12996     # create a fake disk info for _GenerateDiskTemplate
12997     disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
12998                   constants.IDISK_VG: d.logical_id[0]}
12999                  for d in instance.disks]
13000     new_disks = _GenerateDiskTemplate(self, self.op.disk_template,
13001                                       instance.name, pnode, [snode],
13002                                       disk_info, None, None, 0, feedback_fn,
13003                                       self.diskparams)
13004     anno_disks = rpc.AnnotateDiskParams(constants.DT_DRBD8, new_disks,
13005                                         self.diskparams)
13006     info = _GetInstanceInfoText(instance)
13007     feedback_fn("Creating additional volumes...")
13008     # first, create the missing data and meta devices
13009     for disk in anno_disks:
13010       # unfortunately this is... not too nice
13011       _CreateSingleBlockDev(self, pnode, instance, disk.children[1],
13012                             info, True)
13013       for child in disk.children:
13014         _CreateSingleBlockDev(self, snode, instance, child, info, True)
13015     # at this stage, all new LVs have been created, we can rename the
13016     # old ones
13017     feedback_fn("Renaming original volumes...")
13018     rename_list = [(o, n.children[0].logical_id)
13019                    for (o, n) in zip(instance.disks, new_disks)]
13020     result = self.rpc.call_blockdev_rename(pnode, rename_list)
13021     result.Raise("Failed to rename original LVs")
13022
13023     feedback_fn("Initializing DRBD devices...")
13024     # all child devices are in place, we can now create the DRBD devices
13025     for disk in anno_disks:
13026       for node in [pnode, snode]:
13027         f_create = node == pnode
13028         _CreateSingleBlockDev(self, node, instance, disk, info, f_create)
13029
13030     # at this point, the instance has been modified
13031     instance.disk_template = constants.DT_DRBD8
13032     instance.disks = new_disks
13033     self.cfg.Update(instance, feedback_fn)
13034
13035     # Release node locks while waiting for sync
13036     _ReleaseLocks(self, locking.LEVEL_NODE)
13037
13038     # disks are created, waiting for sync
13039     disk_abort = not _WaitForSync(self, instance,
13040                                   oneshot=not self.op.wait_for_sync)
13041     if disk_abort:
13042       raise errors.OpExecError("There are some degraded disks for"
13043                                " this instance, please cleanup manually")
13044
13045     # Node resource locks will be released by caller
13046
13047   def _ConvertDrbdToPlain(self, feedback_fn):
13048     """Converts an instance from drbd to plain.
13049
13050     """
13051     instance = self.instance
13052
13053     assert len(instance.secondary_nodes) == 1
13054     assert instance.disk_template == constants.DT_DRBD8
13055
13056     pnode = instance.primary_node
13057     snode = instance.secondary_nodes[0]
13058     feedback_fn("Converting template to plain")
13059
13060     old_disks = _AnnotateDiskParams(instance, instance.disks, self.cfg)
13061     new_disks = [d.children[0] for d in instance.disks]
13062
13063     # copy over size and mode
13064     for parent, child in zip(old_disks, new_disks):
13065       child.size = parent.size
13066       child.mode = parent.mode
13067
13068     # this is a DRBD disk, return its port to the pool
13069     # NOTE: this must be done right before the call to cfg.Update!
13070     for disk in old_disks:
13071       tcp_port = disk.logical_id[2]
13072       self.cfg.AddTcpUdpPort(tcp_port)
13073
13074     # update instance structure
13075     instance.disks = new_disks
13076     instance.disk_template = constants.DT_PLAIN
13077     self.cfg.Update(instance, feedback_fn)
13078
13079     # Release locks in case removing disks takes a while
13080     _ReleaseLocks(self, locking.LEVEL_NODE)
13081
13082     feedback_fn("Removing volumes on the secondary node...")
13083     for disk in old_disks:
13084       self.cfg.SetDiskID(disk, snode)
13085       msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
13086       if msg:
13087         self.LogWarning("Could not remove block device %s on node %s,"
13088                         " continuing anyway: %s", disk.iv_name, snode, msg)
13089
13090     feedback_fn("Removing unneeded volumes on the primary node...")
13091     for idx, disk in enumerate(old_disks):
13092       meta = disk.children[1]
13093       self.cfg.SetDiskID(meta, pnode)
13094       msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
13095       if msg:
13096         self.LogWarning("Could not remove metadata for disk %d on node %s,"
13097                         " continuing anyway: %s", idx, pnode, msg)
13098
13099   def _CreateNewDisk(self, idx, params, _):
13100     """Creates a new disk.
13101
13102     """
13103     instance = self.instance
13104
13105     # add a new disk
13106     if instance.disk_template in constants.DTS_FILEBASED:
13107       (file_driver, file_path) = instance.disks[0].logical_id
13108       file_path = os.path.dirname(file_path)
13109     else:
13110       file_driver = file_path = None
13111
13112     disk = \
13113       _GenerateDiskTemplate(self, instance.disk_template, instance.name,
13114                             instance.primary_node, instance.secondary_nodes,
13115                             [params], file_path, file_driver, idx,
13116                             self.Log, self.diskparams)[0]
13117
13118     info = _GetInstanceInfoText(instance)
13119
13120     logging.info("Creating volume %s for instance %s",
13121                  disk.iv_name, instance.name)
13122     # Note: this needs to be kept in sync with _CreateDisks
13123     #HARDCODE
13124     for node in instance.all_nodes:
13125       f_create = (node == instance.primary_node)
13126       try:
13127         _CreateBlockDev(self, node, instance, disk, f_create, info, f_create)
13128       except errors.OpExecError, err:
13129         self.LogWarning("Failed to create volume %s (%s) on node '%s': %s",
13130                         disk.iv_name, disk, node, err)
13131
13132     return (disk, [
13133       ("disk/%d" % idx, "add:size=%s,mode=%s" % (disk.size, disk.mode)),
13134       ])
13135
13136   @staticmethod
13137   def _ModifyDisk(idx, disk, params, _):
13138     """Modifies a disk.
13139
13140     """
13141     disk.mode = params[constants.IDISK_MODE]
13142
13143     return [
13144       ("disk.mode/%d" % idx, disk.mode),
13145       ]
13146
13147   def _RemoveDisk(self, idx, root, _):
13148     """Removes a disk.
13149
13150     """
13151     (anno_disk,) = _AnnotateDiskParams(self.instance, [root], self.cfg)
13152     for node, disk in anno_disk.ComputeNodeTree(self.instance.primary_node):
13153       self.cfg.SetDiskID(disk, node)
13154       msg = self.rpc.call_blockdev_remove(node, disk).fail_msg
13155       if msg:
13156         self.LogWarning("Could not remove disk/%d on node '%s': %s,"
13157                         " continuing anyway", idx, node, msg)
13158
13159     # if this is a DRBD disk, return its port to the pool
13160     if root.dev_type in constants.LDS_DRBD:
13161       self.cfg.AddTcpUdpPort(root.logical_id[2])
13162
13163   @staticmethod
13164   def _CreateNewNic(idx, params, private):
13165     """Creates data structure for a new network interface.
13166
13167     """
13168     mac = params[constants.INIC_MAC]
13169     ip = params.get(constants.INIC_IP, None)
13170     nicparams = private.params
13171
13172     return (objects.NIC(mac=mac, ip=ip, nicparams=nicparams), [
13173       ("nic.%d" % idx,
13174        "add:mac=%s,ip=%s,mode=%s,link=%s" %
13175        (mac, ip, private.filled[constants.NIC_MODE],
13176        private.filled[constants.NIC_LINK])),
13177       ])
13178
13179   @staticmethod
13180   def _ApplyNicMods(idx, nic, params, private):
13181     """Modifies a network interface.
13182
13183     """
13184     changes = []
13185
13186     for key in [constants.INIC_MAC, constants.INIC_IP]:
13187       if key in params:
13188         changes.append(("nic.%s/%d" % (key, idx), params[key]))
13189         setattr(nic, key, params[key])
13190
13191     if private.params:
13192       nic.nicparams = private.params
13193
13194       for (key, val) in params.items():
13195         changes.append(("nic.%s/%d" % (key, idx), val))
13196
13197     return changes
13198
13199   def Exec(self, feedback_fn):
13200     """Modifies an instance.
13201
13202     All parameters take effect only at the next restart of the instance.
13203
13204     """
13205     # Process here the warnings from CheckPrereq, as we don't have a
13206     # feedback_fn there.
13207     # TODO: Replace with self.LogWarning
13208     for warn in self.warn:
13209       feedback_fn("WARNING: %s" % warn)
13210
13211     assert ((self.op.disk_template is None) ^
13212             bool(self.owned_locks(locking.LEVEL_NODE_RES))), \
13213       "Not owning any node resource locks"
13214
13215     result = []
13216     instance = self.instance
13217
13218     # runtime memory
13219     if self.op.runtime_mem:
13220       rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
13221                                                      instance,
13222                                                      self.op.runtime_mem)
13223       rpcres.Raise("Cannot modify instance runtime memory")
13224       result.append(("runtime_memory", self.op.runtime_mem))
13225
13226     # Apply disk changes
13227     ApplyContainerMods("disk", instance.disks, result, self.diskmod,
13228                        self._CreateNewDisk, self._ModifyDisk, self._RemoveDisk)
13229     _UpdateIvNames(0, instance.disks)
13230
13231     if self.op.disk_template:
13232       if __debug__:
13233         check_nodes = set(instance.all_nodes)
13234         if self.op.remote_node:
13235           check_nodes.add(self.op.remote_node)
13236         for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
13237           owned = self.owned_locks(level)
13238           assert not (check_nodes - owned), \
13239             ("Not owning the correct locks, owning %r, expected at least %r" %
13240              (owned, check_nodes))
13241
13242       r_shut = _ShutdownInstanceDisks(self, instance)
13243       if not r_shut:
13244         raise errors.OpExecError("Cannot shutdown instance disks, unable to"
13245                                  " proceed with disk template conversion")
13246       mode = (instance.disk_template, self.op.disk_template)
13247       try:
13248         self._DISK_CONVERSIONS[mode](self, feedback_fn)
13249       except:
13250         self.cfg.ReleaseDRBDMinors(instance.name)
13251         raise
13252       result.append(("disk_template", self.op.disk_template))
13253
13254       assert instance.disk_template == self.op.disk_template, \
13255         ("Expected disk template '%s', found '%s'" %
13256          (self.op.disk_template, instance.disk_template))
13257
13258     # Release node and resource locks if there are any (they might already have
13259     # been released during disk conversion)
13260     _ReleaseLocks(self, locking.LEVEL_NODE)
13261     _ReleaseLocks(self, locking.LEVEL_NODE_RES)
13262
13263     # Apply NIC changes
13264     if self._new_nics is not None:
13265       instance.nics = self._new_nics
13266       result.extend(self._nic_chgdesc)
13267
13268     # hvparams changes
13269     if self.op.hvparams:
13270       instance.hvparams = self.hv_inst
13271       for key, val in self.op.hvparams.iteritems():
13272         result.append(("hv/%s" % key, val))
13273
13274     # beparams changes
13275     if self.op.beparams:
13276       instance.beparams = self.be_inst
13277       for key, val in self.op.beparams.iteritems():
13278         result.append(("be/%s" % key, val))
13279
13280     # OS change
13281     if self.op.os_name:
13282       instance.os = self.op.os_name
13283
13284     # osparams changes
13285     if self.op.osparams:
13286       instance.osparams = self.os_inst
13287       for key, val in self.op.osparams.iteritems():
13288         result.append(("os/%s" % key, val))
13289
13290     if self.op.offline is None:
13291       # Ignore
13292       pass
13293     elif self.op.offline:
13294       # Mark instance as offline
13295       self.cfg.MarkInstanceOffline(instance.name)
13296       result.append(("admin_state", constants.ADMINST_OFFLINE))
13297     else:
13298       # Mark instance as online, but stopped
13299       self.cfg.MarkInstanceDown(instance.name)
13300       result.append(("admin_state", constants.ADMINST_DOWN))
13301
13302     self.cfg.Update(instance, feedback_fn)
13303
13304     assert not (self.owned_locks(locking.LEVEL_NODE_RES) or
13305                 self.owned_locks(locking.LEVEL_NODE)), \
13306       "All node locks should have been released by now"
13307
13308     return result
13309
13310   _DISK_CONVERSIONS = {
13311     (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
13312     (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
13313     }
13314
13315
13316 class LUInstanceChangeGroup(LogicalUnit):
13317   HPATH = "instance-change-group"
13318   HTYPE = constants.HTYPE_INSTANCE
13319   REQ_BGL = False
13320
13321   def ExpandNames(self):
13322     self.share_locks = _ShareAll()
13323     self.needed_locks = {
13324       locking.LEVEL_NODEGROUP: [],
13325       locking.LEVEL_NODE: [],
13326       }
13327
13328     self._ExpandAndLockInstance()
13329
13330     if self.op.target_groups:
13331       self.req_target_uuids = map(self.cfg.LookupNodeGroup,
13332                                   self.op.target_groups)
13333     else:
13334       self.req_target_uuids = None
13335
13336     self.op.iallocator = _GetDefaultIAllocator(self.cfg, self.op.iallocator)
13337
13338   def DeclareLocks(self, level):
13339     if level == locking.LEVEL_NODEGROUP:
13340       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
13341
13342       if self.req_target_uuids:
13343         lock_groups = set(self.req_target_uuids)
13344
13345         # Lock all groups used by instance optimistically; this requires going
13346         # via the node before it's locked, requiring verification later on
13347         instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_name)
13348         lock_groups.update(instance_groups)
13349       else:
13350         # No target groups, need to lock all of them
13351         lock_groups = locking.ALL_SET
13352
13353       self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
13354
13355     elif level == locking.LEVEL_NODE:
13356       if self.req_target_uuids:
13357         # Lock all nodes used by instances
13358         self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
13359         self._LockInstancesNodes()
13360
13361         # Lock all nodes in all potential target groups
13362         lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) -
13363                        self.cfg.GetInstanceNodeGroups(self.op.instance_name))
13364         member_nodes = [node_name
13365                         for group in lock_groups
13366                         for node_name in self.cfg.GetNodeGroup(group).members]
13367         self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
13368       else:
13369         # Lock all nodes as all groups are potential targets
13370         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13371
13372   def CheckPrereq(self):
13373     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
13374     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
13375     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
13376
13377     assert (self.req_target_uuids is None or
13378             owned_groups.issuperset(self.req_target_uuids))
13379     assert owned_instances == set([self.op.instance_name])
13380
13381     # Get instance information
13382     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
13383
13384     # Check if node groups for locked instance are still correct
13385     assert owned_nodes.issuperset(self.instance.all_nodes), \
13386       ("Instance %s's nodes changed while we kept the lock" %
13387        self.op.instance_name)
13388
13389     inst_groups = _CheckInstanceNodeGroups(self.cfg, self.op.instance_name,
13390                                            owned_groups)
13391
13392     if self.req_target_uuids:
13393       # User requested specific target groups
13394       self.target_uuids = frozenset(self.req_target_uuids)
13395     else:
13396       # All groups except those used by the instance are potential targets
13397       self.target_uuids = owned_groups - inst_groups
13398
13399     conflicting_groups = self.target_uuids & inst_groups
13400     if conflicting_groups:
13401       raise errors.OpPrereqError("Can't use group(s) '%s' as targets, they are"
13402                                  " used by the instance '%s'" %
13403                                  (utils.CommaJoin(conflicting_groups),
13404                                   self.op.instance_name),
13405                                  errors.ECODE_INVAL)
13406
13407     if not self.target_uuids:
13408       raise errors.OpPrereqError("There are no possible target groups",
13409                                  errors.ECODE_INVAL)
13410
13411   def BuildHooksEnv(self):
13412     """Build hooks env.
13413
13414     """
13415     assert self.target_uuids
13416
13417     env = {
13418       "TARGET_GROUPS": " ".join(self.target_uuids),
13419       }
13420
13421     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
13422
13423     return env
13424
13425   def BuildHooksNodes(self):
13426     """Build hooks nodes.
13427
13428     """
13429     mn = self.cfg.GetMasterNode()
13430     return ([mn], [mn])
13431
13432   def Exec(self, feedback_fn):
13433     instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
13434
13435     assert instances == [self.op.instance_name], "Instance not locked"
13436
13437     req = iallocator.IAReqGroupChange(instances=instances,
13438                                       target_groups=list(self.target_uuids))
13439     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
13440
13441     ial.Run(self.op.iallocator)
13442
13443     if not ial.success:
13444       raise errors.OpPrereqError("Can't compute solution for changing group of"
13445                                  " instance '%s' using iallocator '%s': %s" %
13446                                  (self.op.instance_name, self.op.iallocator,
13447                                   ial.info), errors.ECODE_NORES)
13448
13449     jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
13450
13451     self.LogInfo("Iallocator returned %s job(s) for changing group of"
13452                  " instance '%s'", len(jobs), self.op.instance_name)
13453
13454     return ResultWithJobs(jobs)
13455
13456
13457 class LUBackupQuery(NoHooksLU):
13458   """Query the exports list
13459
13460   """
13461   REQ_BGL = False
13462
13463   def CheckArguments(self):
13464     self.expq = _ExportQuery(qlang.MakeSimpleFilter("node", self.op.nodes),
13465                              ["node", "export"], self.op.use_locking)
13466
13467   def ExpandNames(self):
13468     self.expq.ExpandNames(self)
13469
13470   def DeclareLocks(self, level):
13471     self.expq.DeclareLocks(self, level)
13472
13473   def Exec(self, feedback_fn):
13474     result = {}
13475
13476     for (node, expname) in self.expq.OldStyleQuery(self):
13477       if expname is None:
13478         result[node] = False
13479       else:
13480         result.setdefault(node, []).append(expname)
13481
13482     return result
13483
13484
13485 class _ExportQuery(_QueryBase):
13486   FIELDS = query.EXPORT_FIELDS
13487
13488   #: The node name is not a unique key for this query
13489   SORT_FIELD = "node"
13490
13491   def ExpandNames(self, lu):
13492     lu.needed_locks = {}
13493
13494     # The following variables interact with _QueryBase._GetNames
13495     if self.names:
13496       self.wanted = _GetWantedNodes(lu, self.names)
13497     else:
13498       self.wanted = locking.ALL_SET
13499
13500     self.do_locking = self.use_locking
13501
13502     if self.do_locking:
13503       lu.share_locks = _ShareAll()
13504       lu.needed_locks = {
13505         locking.LEVEL_NODE: self.wanted,
13506         }
13507
13508   def DeclareLocks(self, lu, level):
13509     pass
13510
13511   def _GetQueryData(self, lu):
13512     """Computes the list of nodes and their attributes.
13513
13514     """
13515     # Locking is not used
13516     # TODO
13517     assert not (compat.any(lu.glm.is_owned(level)
13518                            for level in locking.LEVELS
13519                            if level != locking.LEVEL_CLUSTER) or
13520                 self.do_locking or self.use_locking)
13521
13522     nodes = self._GetNames(lu, lu.cfg.GetNodeList(), locking.LEVEL_NODE)
13523
13524     result = []
13525
13526     for (node, nres) in lu.rpc.call_export_list(nodes).items():
13527       if nres.fail_msg:
13528         result.append((node, None))
13529       else:
13530         result.extend((node, expname) for expname in nres.payload)
13531
13532     return result
13533
13534
13535 class LUBackupPrepare(NoHooksLU):
13536   """Prepares an instance for an export and returns useful information.
13537
13538   """
13539   REQ_BGL = False
13540
13541   def ExpandNames(self):
13542     self._ExpandAndLockInstance()
13543
13544   def CheckPrereq(self):
13545     """Check prerequisites.
13546
13547     """
13548     instance_name = self.op.instance_name
13549
13550     self.instance = self.cfg.GetInstanceInfo(instance_name)
13551     assert self.instance is not None, \
13552           "Cannot retrieve locked instance %s" % self.op.instance_name
13553     _CheckNodeOnline(self, self.instance.primary_node)
13554
13555     self._cds = _GetClusterDomainSecret()
13556
13557   def Exec(self, feedback_fn):
13558     """Prepares an instance for an export.
13559
13560     """
13561     instance = self.instance
13562
13563     if self.op.mode == constants.EXPORT_MODE_REMOTE:
13564       salt = utils.GenerateSecret(8)
13565
13566       feedback_fn("Generating X509 certificate on %s" % instance.primary_node)
13567       result = self.rpc.call_x509_cert_create(instance.primary_node,
13568                                               constants.RIE_CERT_VALIDITY)
13569       result.Raise("Can't create X509 key and certificate on %s" % result.node)
13570
13571       (name, cert_pem) = result.payload
13572
13573       cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
13574                                              cert_pem)
13575
13576       return {
13577         "handshake": masterd.instance.ComputeRemoteExportHandshake(self._cds),
13578         "x509_key_name": (name, utils.Sha1Hmac(self._cds, name, salt=salt),
13579                           salt),
13580         "x509_ca": utils.SignX509Certificate(cert, self._cds, salt),
13581         }
13582
13583     return None
13584
13585
13586 class LUBackupExport(LogicalUnit):
13587   """Export an instance to an image in the cluster.
13588
13589   """
13590   HPATH = "instance-export"
13591   HTYPE = constants.HTYPE_INSTANCE
13592   REQ_BGL = False
13593
13594   def CheckArguments(self):
13595     """Check the arguments.
13596
13597     """
13598     self.x509_key_name = self.op.x509_key_name
13599     self.dest_x509_ca_pem = self.op.destination_x509_ca
13600
13601     if self.op.mode == constants.EXPORT_MODE_REMOTE:
13602       if not self.x509_key_name:
13603         raise errors.OpPrereqError("Missing X509 key name for encryption",
13604                                    errors.ECODE_INVAL)
13605
13606       if not self.dest_x509_ca_pem:
13607         raise errors.OpPrereqError("Missing destination X509 CA",
13608                                    errors.ECODE_INVAL)
13609
13610   def ExpandNames(self):
13611     self._ExpandAndLockInstance()
13612
13613     # Lock all nodes for local exports
13614     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13615       # FIXME: lock only instance primary and destination node
13616       #
13617       # Sad but true, for now we have do lock all nodes, as we don't know where
13618       # the previous export might be, and in this LU we search for it and
13619       # remove it from its current node. In the future we could fix this by:
13620       #  - making a tasklet to search (share-lock all), then create the
13621       #    new one, then one to remove, after
13622       #  - removing the removal operation altogether
13623       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13624
13625   def DeclareLocks(self, level):
13626     """Last minute lock declaration."""
13627     # All nodes are locked anyway, so nothing to do here.
13628
13629   def BuildHooksEnv(self):
13630     """Build hooks env.
13631
13632     This will run on the master, primary node and target node.
13633
13634     """
13635     env = {
13636       "EXPORT_MODE": self.op.mode,
13637       "EXPORT_NODE": self.op.target_node,
13638       "EXPORT_DO_SHUTDOWN": self.op.shutdown,
13639       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
13640       # TODO: Generic function for boolean env variables
13641       "REMOVE_INSTANCE": str(bool(self.op.remove_instance)),
13642       }
13643
13644     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
13645
13646     return env
13647
13648   def BuildHooksNodes(self):
13649     """Build hooks nodes.
13650
13651     """
13652     nl = [self.cfg.GetMasterNode(), self.instance.primary_node]
13653
13654     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13655       nl.append(self.op.target_node)
13656
13657     return (nl, nl)
13658
13659   def CheckPrereq(self):
13660     """Check prerequisites.
13661
13662     This checks that the instance and node names are valid.
13663
13664     """
13665     instance_name = self.op.instance_name
13666
13667     self.instance = self.cfg.GetInstanceInfo(instance_name)
13668     assert self.instance is not None, \
13669           "Cannot retrieve locked instance %s" % self.op.instance_name
13670     _CheckNodeOnline(self, self.instance.primary_node)
13671
13672     if (self.op.remove_instance and
13673         self.instance.admin_state == constants.ADMINST_UP and
13674         not self.op.shutdown):
13675       raise errors.OpPrereqError("Can not remove instance without shutting it"
13676                                  " down before", errors.ECODE_STATE)
13677
13678     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13679       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
13680       self.dst_node = self.cfg.GetNodeInfo(self.op.target_node)
13681       assert self.dst_node is not None
13682
13683       _CheckNodeOnline(self, self.dst_node.name)
13684       _CheckNodeNotDrained(self, self.dst_node.name)
13685
13686       self._cds = None
13687       self.dest_disk_info = None
13688       self.dest_x509_ca = None
13689
13690     elif self.op.mode == constants.EXPORT_MODE_REMOTE:
13691       self.dst_node = None
13692
13693       if len(self.op.target_node) != len(self.instance.disks):
13694         raise errors.OpPrereqError(("Received destination information for %s"
13695                                     " disks, but instance %s has %s disks") %
13696                                    (len(self.op.target_node), instance_name,
13697                                     len(self.instance.disks)),
13698                                    errors.ECODE_INVAL)
13699
13700       cds = _GetClusterDomainSecret()
13701
13702       # Check X509 key name
13703       try:
13704         (key_name, hmac_digest, hmac_salt) = self.x509_key_name
13705       except (TypeError, ValueError), err:
13706         raise errors.OpPrereqError("Invalid data for X509 key name: %s" % err,
13707                                    errors.ECODE_INVAL)
13708
13709       if not utils.VerifySha1Hmac(cds, key_name, hmac_digest, salt=hmac_salt):
13710         raise errors.OpPrereqError("HMAC for X509 key name is wrong",
13711                                    errors.ECODE_INVAL)
13712
13713       # Load and verify CA
13714       try:
13715         (cert, _) = utils.LoadSignedX509Certificate(self.dest_x509_ca_pem, cds)
13716       except OpenSSL.crypto.Error, err:
13717         raise errors.OpPrereqError("Unable to load destination X509 CA (%s)" %
13718                                    (err, ), errors.ECODE_INVAL)
13719
13720       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
13721       if errcode is not None:
13722         raise errors.OpPrereqError("Invalid destination X509 CA (%s)" %
13723                                    (msg, ), errors.ECODE_INVAL)
13724
13725       self.dest_x509_ca = cert
13726
13727       # Verify target information
13728       disk_info = []
13729       for idx, disk_data in enumerate(self.op.target_node):
13730         try:
13731           (host, port, magic) = \
13732             masterd.instance.CheckRemoteExportDiskInfo(cds, idx, disk_data)
13733         except errors.GenericError, err:
13734           raise errors.OpPrereqError("Target info for disk %s: %s" %
13735                                      (idx, err), errors.ECODE_INVAL)
13736
13737         disk_info.append((host, port, magic))
13738
13739       assert len(disk_info) == len(self.op.target_node)
13740       self.dest_disk_info = disk_info
13741
13742     else:
13743       raise errors.ProgrammerError("Unhandled export mode %r" %
13744                                    self.op.mode)
13745
13746     # instance disk type verification
13747     # TODO: Implement export support for file-based disks
13748     for disk in self.instance.disks:
13749       if disk.dev_type == constants.LD_FILE:
13750         raise errors.OpPrereqError("Export not supported for instances with"
13751                                    " file-based disks", errors.ECODE_INVAL)
13752
13753   def _CleanupExports(self, feedback_fn):
13754     """Removes exports of current instance from all other nodes.
13755
13756     If an instance in a cluster with nodes A..D was exported to node C, its
13757     exports will be removed from the nodes A, B and D.
13758
13759     """
13760     assert self.op.mode != constants.EXPORT_MODE_REMOTE
13761
13762     nodelist = self.cfg.GetNodeList()
13763     nodelist.remove(self.dst_node.name)
13764
13765     # on one-node clusters nodelist will be empty after the removal
13766     # if we proceed the backup would be removed because OpBackupQuery
13767     # substitutes an empty list with the full cluster node list.
13768     iname = self.instance.name
13769     if nodelist:
13770       feedback_fn("Removing old exports for instance %s" % iname)
13771       exportlist = self.rpc.call_export_list(nodelist)
13772       for node in exportlist:
13773         if exportlist[node].fail_msg:
13774           continue
13775         if iname in exportlist[node].payload:
13776           msg = self.rpc.call_export_remove(node, iname).fail_msg
13777           if msg:
13778             self.LogWarning("Could not remove older export for instance %s"
13779                             " on node %s: %s", iname, node, msg)
13780
13781   def Exec(self, feedback_fn):
13782     """Export an instance to an image in the cluster.
13783
13784     """
13785     assert self.op.mode in constants.EXPORT_MODES
13786
13787     instance = self.instance
13788     src_node = instance.primary_node
13789
13790     if self.op.shutdown:
13791       # shutdown the instance, but not the disks
13792       feedback_fn("Shutting down instance %s" % instance.name)
13793       result = self.rpc.call_instance_shutdown(src_node, instance,
13794                                                self.op.shutdown_timeout)
13795       # TODO: Maybe ignore failures if ignore_remove_failures is set
13796       result.Raise("Could not shutdown instance %s on"
13797                    " node %s" % (instance.name, src_node))
13798
13799     # set the disks ID correctly since call_instance_start needs the
13800     # correct drbd minor to create the symlinks
13801     for disk in instance.disks:
13802       self.cfg.SetDiskID(disk, src_node)
13803
13804     activate_disks = (instance.admin_state != constants.ADMINST_UP)
13805
13806     if activate_disks:
13807       # Activate the instance disks if we'exporting a stopped instance
13808       feedback_fn("Activating disks for %s" % instance.name)
13809       _StartInstanceDisks(self, instance, None)
13810
13811     try:
13812       helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
13813                                                      instance)
13814
13815       helper.CreateSnapshots()
13816       try:
13817         if (self.op.shutdown and
13818             instance.admin_state == constants.ADMINST_UP and
13819             not self.op.remove_instance):
13820           assert not activate_disks
13821           feedback_fn("Starting instance %s" % instance.name)
13822           result = self.rpc.call_instance_start(src_node,
13823                                                 (instance, None, None), False)
13824           msg = result.fail_msg
13825           if msg:
13826             feedback_fn("Failed to start instance: %s" % msg)
13827             _ShutdownInstanceDisks(self, instance)
13828             raise errors.OpExecError("Could not start instance: %s" % msg)
13829
13830         if self.op.mode == constants.EXPORT_MODE_LOCAL:
13831           (fin_resu, dresults) = helper.LocalExport(self.dst_node)
13832         elif self.op.mode == constants.EXPORT_MODE_REMOTE:
13833           connect_timeout = constants.RIE_CONNECT_TIMEOUT
13834           timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
13835
13836           (key_name, _, _) = self.x509_key_name
13837
13838           dest_ca_pem = \
13839             OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
13840                                             self.dest_x509_ca)
13841
13842           (fin_resu, dresults) = helper.RemoteExport(self.dest_disk_info,
13843                                                      key_name, dest_ca_pem,
13844                                                      timeouts)
13845       finally:
13846         helper.Cleanup()
13847
13848       # Check for backwards compatibility
13849       assert len(dresults) == len(instance.disks)
13850       assert compat.all(isinstance(i, bool) for i in dresults), \
13851              "Not all results are boolean: %r" % dresults
13852
13853     finally:
13854       if activate_disks:
13855         feedback_fn("Deactivating disks for %s" % instance.name)
13856         _ShutdownInstanceDisks(self, instance)
13857
13858     if not (compat.all(dresults) and fin_resu):
13859       failures = []
13860       if not fin_resu:
13861         failures.append("export finalization")
13862       if not compat.all(dresults):
13863         fdsk = utils.CommaJoin(idx for (idx, dsk) in enumerate(dresults)
13864                                if not dsk)
13865         failures.append("disk export: disk(s) %s" % fdsk)
13866
13867       raise errors.OpExecError("Export failed, errors in %s" %
13868                                utils.CommaJoin(failures))
13869
13870     # At this point, the export was successful, we can cleanup/finish
13871
13872     # Remove instance if requested
13873     if self.op.remove_instance:
13874       feedback_fn("Removing instance %s" % instance.name)
13875       _RemoveInstance(self, feedback_fn, instance,
13876                       self.op.ignore_remove_failures)
13877
13878     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13879       self._CleanupExports(feedback_fn)
13880
13881     return fin_resu, dresults
13882
13883
13884 class LUBackupRemove(NoHooksLU):
13885   """Remove exports related to the named instance.
13886
13887   """
13888   REQ_BGL = False
13889
13890   def ExpandNames(self):
13891     self.needed_locks = {}
13892     # We need all nodes to be locked in order for RemoveExport to work, but we
13893     # don't need to lock the instance itself, as nothing will happen to it (and
13894     # we can remove exports also for a removed instance)
13895     self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13896
13897   def Exec(self, feedback_fn):
13898     """Remove any export.
13899
13900     """
13901     instance_name = self.cfg.ExpandInstanceName(self.op.instance_name)
13902     # If the instance was not found we'll try with the name that was passed in.
13903     # This will only work if it was an FQDN, though.
13904     fqdn_warn = False
13905     if not instance_name:
13906       fqdn_warn = True
13907       instance_name = self.op.instance_name
13908
13909     locked_nodes = self.owned_locks(locking.LEVEL_NODE)
13910     exportlist = self.rpc.call_export_list(locked_nodes)
13911     found = False
13912     for node in exportlist:
13913       msg = exportlist[node].fail_msg
13914       if msg:
13915         self.LogWarning("Failed to query node %s (continuing): %s", node, msg)
13916         continue
13917       if instance_name in exportlist[node].payload:
13918         found = True
13919         result = self.rpc.call_export_remove(node, instance_name)
13920         msg = result.fail_msg
13921         if msg:
13922           logging.error("Could not remove export for instance %s"
13923                         " on node %s: %s", instance_name, node, msg)
13924
13925     if fqdn_warn and not found:
13926       feedback_fn("Export not found. If trying to remove an export belonging"
13927                   " to a deleted instance please use its Fully Qualified"
13928                   " Domain Name.")
13929
13930
13931 class LUGroupAdd(LogicalUnit):
13932   """Logical unit for creating node groups.
13933
13934   """
13935   HPATH = "group-add"
13936   HTYPE = constants.HTYPE_GROUP
13937   REQ_BGL = False
13938
13939   def ExpandNames(self):
13940     # We need the new group's UUID here so that we can create and acquire the
13941     # corresponding lock. Later, in Exec(), we'll indicate to cfg.AddNodeGroup
13942     # that it should not check whether the UUID exists in the configuration.
13943     self.group_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
13944     self.needed_locks = {}
13945     self.add_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
13946
13947   def CheckPrereq(self):
13948     """Check prerequisites.
13949
13950     This checks that the given group name is not an existing node group
13951     already.
13952
13953     """
13954     try:
13955       existing_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13956     except errors.OpPrereqError:
13957       pass
13958     else:
13959       raise errors.OpPrereqError("Desired group name '%s' already exists as a"
13960                                  " node group (UUID: %s)" %
13961                                  (self.op.group_name, existing_uuid),
13962                                  errors.ECODE_EXISTS)
13963
13964     if self.op.ndparams:
13965       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
13966
13967     if self.op.hv_state:
13968       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None)
13969     else:
13970       self.new_hv_state = None
13971
13972     if self.op.disk_state:
13973       self.new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state, None)
13974     else:
13975       self.new_disk_state = None
13976
13977     if self.op.diskparams:
13978       for templ in constants.DISK_TEMPLATES:
13979         if templ in self.op.diskparams:
13980           utils.ForceDictType(self.op.diskparams[templ],
13981                               constants.DISK_DT_TYPES)
13982       self.new_diskparams = self.op.diskparams
13983       try:
13984         utils.VerifyDictOptions(self.new_diskparams, constants.DISK_DT_DEFAULTS)
13985       except errors.OpPrereqError, err:
13986         raise errors.OpPrereqError("While verify diskparams options: %s" % err,
13987                                    errors.ECODE_INVAL)
13988     else:
13989       self.new_diskparams = {}
13990
13991     if self.op.ipolicy:
13992       cluster = self.cfg.GetClusterInfo()
13993       full_ipolicy = cluster.SimpleFillIPolicy(self.op.ipolicy)
13994       try:
13995         objects.InstancePolicy.CheckParameterSyntax(full_ipolicy, False)
13996       except errors.ConfigurationError, err:
13997         raise errors.OpPrereqError("Invalid instance policy: %s" % err,
13998                                    errors.ECODE_INVAL)
13999
14000   def BuildHooksEnv(self):
14001     """Build hooks env.
14002
14003     """
14004     return {
14005       "GROUP_NAME": self.op.group_name,
14006       }
14007
14008   def BuildHooksNodes(self):
14009     """Build hooks nodes.
14010
14011     """
14012     mn = self.cfg.GetMasterNode()
14013     return ([mn], [mn])
14014
14015   def Exec(self, feedback_fn):
14016     """Add the node group to the cluster.
14017
14018     """
14019     group_obj = objects.NodeGroup(name=self.op.group_name, members=[],
14020                                   uuid=self.group_uuid,
14021                                   alloc_policy=self.op.alloc_policy,
14022                                   ndparams=self.op.ndparams,
14023                                   diskparams=self.new_diskparams,
14024                                   ipolicy=self.op.ipolicy,
14025                                   hv_state_static=self.new_hv_state,
14026                                   disk_state_static=self.new_disk_state)
14027
14028     self.cfg.AddNodeGroup(group_obj, self.proc.GetECId(), check_uuid=False)
14029     del self.remove_locks[locking.LEVEL_NODEGROUP]
14030
14031
14032 class LUGroupAssignNodes(NoHooksLU):
14033   """Logical unit for assigning nodes to groups.
14034
14035   """
14036   REQ_BGL = False
14037
14038   def ExpandNames(self):
14039     # These raise errors.OpPrereqError on their own:
14040     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14041     self.op.nodes = _GetWantedNodes(self, self.op.nodes)
14042
14043     # We want to lock all the affected nodes and groups. We have readily
14044     # available the list of nodes, and the *destination* group. To gather the
14045     # list of "source" groups, we need to fetch node information later on.
14046     self.needed_locks = {
14047       locking.LEVEL_NODEGROUP: set([self.group_uuid]),
14048       locking.LEVEL_NODE: self.op.nodes,
14049       }
14050
14051   def DeclareLocks(self, level):
14052     if level == locking.LEVEL_NODEGROUP:
14053       assert len(self.needed_locks[locking.LEVEL_NODEGROUP]) == 1
14054
14055       # Try to get all affected nodes' groups without having the group or node
14056       # lock yet. Needs verification later in the code flow.
14057       groups = self.cfg.GetNodeGroupsFromNodes(self.op.nodes)
14058
14059       self.needed_locks[locking.LEVEL_NODEGROUP].update(groups)
14060
14061   def CheckPrereq(self):
14062     """Check prerequisites.
14063
14064     """
14065     assert self.needed_locks[locking.LEVEL_NODEGROUP]
14066     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
14067             frozenset(self.op.nodes))
14068
14069     expected_locks = (set([self.group_uuid]) |
14070                       self.cfg.GetNodeGroupsFromNodes(self.op.nodes))
14071     actual_locks = self.owned_locks(locking.LEVEL_NODEGROUP)
14072     if actual_locks != expected_locks:
14073       raise errors.OpExecError("Nodes changed groups since locks were acquired,"
14074                                " current groups are '%s', used to be '%s'" %
14075                                (utils.CommaJoin(expected_locks),
14076                                 utils.CommaJoin(actual_locks)))
14077
14078     self.node_data = self.cfg.GetAllNodesInfo()
14079     self.group = self.cfg.GetNodeGroup(self.group_uuid)
14080     instance_data = self.cfg.GetAllInstancesInfo()
14081
14082     if self.group is None:
14083       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
14084                                (self.op.group_name, self.group_uuid))
14085
14086     (new_splits, previous_splits) = \
14087       self.CheckAssignmentForSplitInstances([(node, self.group_uuid)
14088                                              for node in self.op.nodes],
14089                                             self.node_data, instance_data)
14090
14091     if new_splits:
14092       fmt_new_splits = utils.CommaJoin(utils.NiceSort(new_splits))
14093
14094       if not self.op.force:
14095         raise errors.OpExecError("The following instances get split by this"
14096                                  " change and --force was not given: %s" %
14097                                  fmt_new_splits)
14098       else:
14099         self.LogWarning("This operation will split the following instances: %s",
14100                         fmt_new_splits)
14101
14102         if previous_splits:
14103           self.LogWarning("In addition, these already-split instances continue"
14104                           " to be split across groups: %s",
14105                           utils.CommaJoin(utils.NiceSort(previous_splits)))
14106
14107   def Exec(self, feedback_fn):
14108     """Assign nodes to a new group.
14109
14110     """
14111     mods = [(node_name, self.group_uuid) for node_name in self.op.nodes]
14112
14113     self.cfg.AssignGroupNodes(mods)
14114
14115   @staticmethod
14116   def CheckAssignmentForSplitInstances(changes, node_data, instance_data):
14117     """Check for split instances after a node assignment.
14118
14119     This method considers a series of node assignments as an atomic operation,
14120     and returns information about split instances after applying the set of
14121     changes.
14122
14123     In particular, it returns information about newly split instances, and
14124     instances that were already split, and remain so after the change.
14125
14126     Only instances whose disk template is listed in constants.DTS_INT_MIRROR are
14127     considered.
14128
14129     @type changes: list of (node_name, new_group_uuid) pairs.
14130     @param changes: list of node assignments to consider.
14131     @param node_data: a dict with data for all nodes
14132     @param instance_data: a dict with all instances to consider
14133     @rtype: a two-tuple
14134     @return: a list of instances that were previously okay and result split as a
14135       consequence of this change, and a list of instances that were previously
14136       split and this change does not fix.
14137
14138     """
14139     changed_nodes = dict((node, group) for node, group in changes
14140                          if node_data[node].group != group)
14141
14142     all_split_instances = set()
14143     previously_split_instances = set()
14144
14145     def InstanceNodes(instance):
14146       return [instance.primary_node] + list(instance.secondary_nodes)
14147
14148     for inst in instance_data.values():
14149       if inst.disk_template not in constants.DTS_INT_MIRROR:
14150         continue
14151
14152       instance_nodes = InstanceNodes(inst)
14153
14154       if len(set(node_data[node].group for node in instance_nodes)) > 1:
14155         previously_split_instances.add(inst.name)
14156
14157       if len(set(changed_nodes.get(node, node_data[node].group)
14158                  for node in instance_nodes)) > 1:
14159         all_split_instances.add(inst.name)
14160
14161     return (list(all_split_instances - previously_split_instances),
14162             list(previously_split_instances & all_split_instances))
14163
14164
14165 class _GroupQuery(_QueryBase):
14166   FIELDS = query.GROUP_FIELDS
14167
14168   def ExpandNames(self, lu):
14169     lu.needed_locks = {}
14170
14171     self._all_groups = lu.cfg.GetAllNodeGroupsInfo()
14172     self._cluster = lu.cfg.GetClusterInfo()
14173     name_to_uuid = dict((g.name, g.uuid) for g in self._all_groups.values())
14174
14175     if not self.names:
14176       self.wanted = [name_to_uuid[name]
14177                      for name in utils.NiceSort(name_to_uuid.keys())]
14178     else:
14179       # Accept names to be either names or UUIDs.
14180       missing = []
14181       self.wanted = []
14182       all_uuid = frozenset(self._all_groups.keys())
14183
14184       for name in self.names:
14185         if name in all_uuid:
14186           self.wanted.append(name)
14187         elif name in name_to_uuid:
14188           self.wanted.append(name_to_uuid[name])
14189         else:
14190           missing.append(name)
14191
14192       if missing:
14193         raise errors.OpPrereqError("Some groups do not exist: %s" %
14194                                    utils.CommaJoin(missing),
14195                                    errors.ECODE_NOENT)
14196
14197   def DeclareLocks(self, lu, level):
14198     pass
14199
14200   def _GetQueryData(self, lu):
14201     """Computes the list of node groups and their attributes.
14202
14203     """
14204     do_nodes = query.GQ_NODE in self.requested_data
14205     do_instances = query.GQ_INST in self.requested_data
14206
14207     group_to_nodes = None
14208     group_to_instances = None
14209
14210     # For GQ_NODE, we need to map group->[nodes], and group->[instances] for
14211     # GQ_INST. The former is attainable with just GetAllNodesInfo(), but for the
14212     # latter GetAllInstancesInfo() is not enough, for we have to go through
14213     # instance->node. Hence, we will need to process nodes even if we only need
14214     # instance information.
14215     if do_nodes or do_instances:
14216       all_nodes = lu.cfg.GetAllNodesInfo()
14217       group_to_nodes = dict((uuid, []) for uuid in self.wanted)
14218       node_to_group = {}
14219
14220       for node in all_nodes.values():
14221         if node.group in group_to_nodes:
14222           group_to_nodes[node.group].append(node.name)
14223           node_to_group[node.name] = node.group
14224
14225       if do_instances:
14226         all_instances = lu.cfg.GetAllInstancesInfo()
14227         group_to_instances = dict((uuid, []) for uuid in self.wanted)
14228
14229         for instance in all_instances.values():
14230           node = instance.primary_node
14231           if node in node_to_group:
14232             group_to_instances[node_to_group[node]].append(instance.name)
14233
14234         if not do_nodes:
14235           # Do not pass on node information if it was not requested.
14236           group_to_nodes = None
14237
14238     return query.GroupQueryData(self._cluster,
14239                                 [self._all_groups[uuid]
14240                                  for uuid in self.wanted],
14241                                 group_to_nodes, group_to_instances,
14242                                 query.GQ_DISKPARAMS in self.requested_data)
14243
14244
14245 class LUGroupQuery(NoHooksLU):
14246   """Logical unit for querying node groups.
14247
14248   """
14249   REQ_BGL = False
14250
14251   def CheckArguments(self):
14252     self.gq = _GroupQuery(qlang.MakeSimpleFilter("name", self.op.names),
14253                           self.op.output_fields, False)
14254
14255   def ExpandNames(self):
14256     self.gq.ExpandNames(self)
14257
14258   def DeclareLocks(self, level):
14259     self.gq.DeclareLocks(self, level)
14260
14261   def Exec(self, feedback_fn):
14262     return self.gq.OldStyleQuery(self)
14263
14264
14265 class LUGroupSetParams(LogicalUnit):
14266   """Modifies the parameters of a node group.
14267
14268   """
14269   HPATH = "group-modify"
14270   HTYPE = constants.HTYPE_GROUP
14271   REQ_BGL = False
14272
14273   def CheckArguments(self):
14274     all_changes = [
14275       self.op.ndparams,
14276       self.op.diskparams,
14277       self.op.alloc_policy,
14278       self.op.hv_state,
14279       self.op.disk_state,
14280       self.op.ipolicy,
14281       ]
14282
14283     if all_changes.count(None) == len(all_changes):
14284       raise errors.OpPrereqError("Please pass at least one modification",
14285                                  errors.ECODE_INVAL)
14286
14287   def ExpandNames(self):
14288     # This raises errors.OpPrereqError on its own:
14289     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14290
14291     self.needed_locks = {
14292       locking.LEVEL_INSTANCE: [],
14293       locking.LEVEL_NODEGROUP: [self.group_uuid],
14294       }
14295
14296     self.share_locks[locking.LEVEL_INSTANCE] = 1
14297
14298   def DeclareLocks(self, level):
14299     if level == locking.LEVEL_INSTANCE:
14300       assert not self.needed_locks[locking.LEVEL_INSTANCE]
14301
14302       # Lock instances optimistically, needs verification once group lock has
14303       # been acquired
14304       self.needed_locks[locking.LEVEL_INSTANCE] = \
14305           self.cfg.GetNodeGroupInstances(self.group_uuid)
14306
14307   @staticmethod
14308   def _UpdateAndVerifyDiskParams(old, new):
14309     """Updates and verifies disk parameters.
14310
14311     """
14312     new_params = _GetUpdatedParams(old, new)
14313     utils.ForceDictType(new_params, constants.DISK_DT_TYPES)
14314     return new_params
14315
14316   def CheckPrereq(self):
14317     """Check prerequisites.
14318
14319     """
14320     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
14321
14322     # Check if locked instances are still correct
14323     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
14324
14325     self.group = self.cfg.GetNodeGroup(self.group_uuid)
14326     cluster = self.cfg.GetClusterInfo()
14327
14328     if self.group is None:
14329       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
14330                                (self.op.group_name, self.group_uuid))
14331
14332     if self.op.ndparams:
14333       new_ndparams = _GetUpdatedParams(self.group.ndparams, self.op.ndparams)
14334       utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES)
14335       self.new_ndparams = new_ndparams
14336
14337     if self.op.diskparams:
14338       diskparams = self.group.diskparams
14339       uavdp = self._UpdateAndVerifyDiskParams
14340       # For each disktemplate subdict update and verify the values
14341       new_diskparams = dict((dt,
14342                              uavdp(diskparams.get(dt, {}),
14343                                    self.op.diskparams[dt]))
14344                             for dt in constants.DISK_TEMPLATES
14345                             if dt in self.op.diskparams)
14346       # As we've all subdicts of diskparams ready, lets merge the actual
14347       # dict with all updated subdicts
14348       self.new_diskparams = objects.FillDict(diskparams, new_diskparams)
14349       try:
14350         utils.VerifyDictOptions(self.new_diskparams, constants.DISK_DT_DEFAULTS)
14351       except errors.OpPrereqError, err:
14352         raise errors.OpPrereqError("While verify diskparams options: %s" % err,
14353                                    errors.ECODE_INVAL)
14354
14355     if self.op.hv_state:
14356       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
14357                                                  self.group.hv_state_static)
14358
14359     if self.op.disk_state:
14360       self.new_disk_state = \
14361         _MergeAndVerifyDiskState(self.op.disk_state,
14362                                  self.group.disk_state_static)
14363
14364     if self.op.ipolicy:
14365       self.new_ipolicy = _GetUpdatedIPolicy(self.group.ipolicy,
14366                                             self.op.ipolicy,
14367                                             group_policy=True)
14368
14369       new_ipolicy = cluster.SimpleFillIPolicy(self.new_ipolicy)
14370       inst_filter = lambda inst: inst.name in owned_instances
14371       instances = self.cfg.GetInstancesInfoByFilter(inst_filter).values()
14372       gmi = ganeti.masterd.instance
14373       violations = \
14374           _ComputeNewInstanceViolations(gmi.CalculateGroupIPolicy(cluster,
14375                                                                   self.group),
14376                                         new_ipolicy, instances)
14377
14378       if violations:
14379         self.LogWarning("After the ipolicy change the following instances"
14380                         " violate them: %s",
14381                         utils.CommaJoin(violations))
14382
14383   def BuildHooksEnv(self):
14384     """Build hooks env.
14385
14386     """
14387     return {
14388       "GROUP_NAME": self.op.group_name,
14389       "NEW_ALLOC_POLICY": self.op.alloc_policy,
14390       }
14391
14392   def BuildHooksNodes(self):
14393     """Build hooks nodes.
14394
14395     """
14396     mn = self.cfg.GetMasterNode()
14397     return ([mn], [mn])
14398
14399   def Exec(self, feedback_fn):
14400     """Modifies the node group.
14401
14402     """
14403     result = []
14404
14405     if self.op.ndparams:
14406       self.group.ndparams = self.new_ndparams
14407       result.append(("ndparams", str(self.group.ndparams)))
14408
14409     if self.op.diskparams:
14410       self.group.diskparams = self.new_diskparams
14411       result.append(("diskparams", str(self.group.diskparams)))
14412
14413     if self.op.alloc_policy:
14414       self.group.alloc_policy = self.op.alloc_policy
14415
14416     if self.op.hv_state:
14417       self.group.hv_state_static = self.new_hv_state
14418
14419     if self.op.disk_state:
14420       self.group.disk_state_static = self.new_disk_state
14421
14422     if self.op.ipolicy:
14423       self.group.ipolicy = self.new_ipolicy
14424
14425     self.cfg.Update(self.group, feedback_fn)
14426     return result
14427
14428
14429 class LUGroupRemove(LogicalUnit):
14430   HPATH = "group-remove"
14431   HTYPE = constants.HTYPE_GROUP
14432   REQ_BGL = False
14433
14434   def ExpandNames(self):
14435     # This will raises errors.OpPrereqError on its own:
14436     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14437     self.needed_locks = {
14438       locking.LEVEL_NODEGROUP: [self.group_uuid],
14439       }
14440
14441   def CheckPrereq(self):
14442     """Check prerequisites.
14443
14444     This checks that the given group name exists as a node group, that is
14445     empty (i.e., contains no nodes), and that is not the last group of the
14446     cluster.
14447
14448     """
14449     # Verify that the group is empty.
14450     group_nodes = [node.name
14451                    for node in self.cfg.GetAllNodesInfo().values()
14452                    if node.group == self.group_uuid]
14453
14454     if group_nodes:
14455       raise errors.OpPrereqError("Group '%s' not empty, has the following"
14456                                  " nodes: %s" %
14457                                  (self.op.group_name,
14458                                   utils.CommaJoin(utils.NiceSort(group_nodes))),
14459                                  errors.ECODE_STATE)
14460
14461     # Verify the cluster would not be left group-less.
14462     if len(self.cfg.GetNodeGroupList()) == 1:
14463       raise errors.OpPrereqError("Group '%s' is the only group, cannot be"
14464                                  " removed" % self.op.group_name,
14465                                  errors.ECODE_STATE)
14466
14467   def BuildHooksEnv(self):
14468     """Build hooks env.
14469
14470     """
14471     return {
14472       "GROUP_NAME": self.op.group_name,
14473       }
14474
14475   def BuildHooksNodes(self):
14476     """Build hooks nodes.
14477
14478     """
14479     mn = self.cfg.GetMasterNode()
14480     return ([mn], [mn])
14481
14482   def Exec(self, feedback_fn):
14483     """Remove the node group.
14484
14485     """
14486     try:
14487       self.cfg.RemoveNodeGroup(self.group_uuid)
14488     except errors.ConfigurationError:
14489       raise errors.OpExecError("Group '%s' with UUID %s disappeared" %
14490                                (self.op.group_name, self.group_uuid))
14491
14492     self.remove_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
14493
14494
14495 class LUGroupRename(LogicalUnit):
14496   HPATH = "group-rename"
14497   HTYPE = constants.HTYPE_GROUP
14498   REQ_BGL = False
14499
14500   def ExpandNames(self):
14501     # This raises errors.OpPrereqError on its own:
14502     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14503
14504     self.needed_locks = {
14505       locking.LEVEL_NODEGROUP: [self.group_uuid],
14506       }
14507
14508   def CheckPrereq(self):
14509     """Check prerequisites.
14510
14511     Ensures requested new name is not yet used.
14512
14513     """
14514     try:
14515       new_name_uuid = self.cfg.LookupNodeGroup(self.op.new_name)
14516     except errors.OpPrereqError:
14517       pass
14518     else:
14519       raise errors.OpPrereqError("Desired new name '%s' clashes with existing"
14520                                  " node group (UUID: %s)" %
14521                                  (self.op.new_name, new_name_uuid),
14522                                  errors.ECODE_EXISTS)
14523
14524   def BuildHooksEnv(self):
14525     """Build hooks env.
14526
14527     """
14528     return {
14529       "OLD_NAME": self.op.group_name,
14530       "NEW_NAME": self.op.new_name,
14531       }
14532
14533   def BuildHooksNodes(self):
14534     """Build hooks nodes.
14535
14536     """
14537     mn = self.cfg.GetMasterNode()
14538
14539     all_nodes = self.cfg.GetAllNodesInfo()
14540     all_nodes.pop(mn, None)
14541
14542     run_nodes = [mn]
14543     run_nodes.extend(node.name for node in all_nodes.values()
14544                      if node.group == self.group_uuid)
14545
14546     return (run_nodes, run_nodes)
14547
14548   def Exec(self, feedback_fn):
14549     """Rename the node group.
14550
14551     """
14552     group = self.cfg.GetNodeGroup(self.group_uuid)
14553
14554     if group is None:
14555       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
14556                                (self.op.group_name, self.group_uuid))
14557
14558     group.name = self.op.new_name
14559     self.cfg.Update(group, feedback_fn)
14560
14561     return self.op.new_name
14562
14563
14564 class LUGroupEvacuate(LogicalUnit):
14565   HPATH = "group-evacuate"
14566   HTYPE = constants.HTYPE_GROUP
14567   REQ_BGL = False
14568
14569   def ExpandNames(self):
14570     # This raises errors.OpPrereqError on its own:
14571     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14572
14573     if self.op.target_groups:
14574       self.req_target_uuids = map(self.cfg.LookupNodeGroup,
14575                                   self.op.target_groups)
14576     else:
14577       self.req_target_uuids = []
14578
14579     if self.group_uuid in self.req_target_uuids:
14580       raise errors.OpPrereqError("Group to be evacuated (%s) can not be used"
14581                                  " as a target group (targets are %s)" %
14582                                  (self.group_uuid,
14583                                   utils.CommaJoin(self.req_target_uuids)),
14584                                  errors.ECODE_INVAL)
14585
14586     self.op.iallocator = _GetDefaultIAllocator(self.cfg, self.op.iallocator)
14587
14588     self.share_locks = _ShareAll()
14589     self.needed_locks = {
14590       locking.LEVEL_INSTANCE: [],
14591       locking.LEVEL_NODEGROUP: [],
14592       locking.LEVEL_NODE: [],
14593       }
14594
14595   def DeclareLocks(self, level):
14596     if level == locking.LEVEL_INSTANCE:
14597       assert not self.needed_locks[locking.LEVEL_INSTANCE]
14598
14599       # Lock instances optimistically, needs verification once node and group
14600       # locks have been acquired
14601       self.needed_locks[locking.LEVEL_INSTANCE] = \
14602         self.cfg.GetNodeGroupInstances(self.group_uuid)
14603
14604     elif level == locking.LEVEL_NODEGROUP:
14605       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
14606
14607       if self.req_target_uuids:
14608         lock_groups = set([self.group_uuid] + self.req_target_uuids)
14609
14610         # Lock all groups used by instances optimistically; this requires going
14611         # via the node before it's locked, requiring verification later on
14612         lock_groups.update(group_uuid
14613                            for instance_name in
14614                              self.owned_locks(locking.LEVEL_INSTANCE)
14615                            for group_uuid in
14616                              self.cfg.GetInstanceNodeGroups(instance_name))
14617       else:
14618         # No target groups, need to lock all of them
14619         lock_groups = locking.ALL_SET
14620
14621       self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
14622
14623     elif level == locking.LEVEL_NODE:
14624       # This will only lock the nodes in the group to be evacuated which
14625       # contain actual instances
14626       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
14627       self._LockInstancesNodes()
14628
14629       # Lock all nodes in group to be evacuated and target groups
14630       owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
14631       assert self.group_uuid in owned_groups
14632       member_nodes = [node_name
14633                       for group in owned_groups
14634                       for node_name in self.cfg.GetNodeGroup(group).members]
14635       self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
14636
14637   def CheckPrereq(self):
14638     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
14639     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
14640     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
14641
14642     assert owned_groups.issuperset(self.req_target_uuids)
14643     assert self.group_uuid in owned_groups
14644
14645     # Check if locked instances are still correct
14646     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
14647
14648     # Get instance information
14649     self.instances = dict(self.cfg.GetMultiInstanceInfo(owned_instances))
14650
14651     # Check if node groups for locked instances are still correct
14652     _CheckInstancesNodeGroups(self.cfg, self.instances,
14653                               owned_groups, owned_nodes, self.group_uuid)
14654
14655     if self.req_target_uuids:
14656       # User requested specific target groups
14657       self.target_uuids = self.req_target_uuids
14658     else:
14659       # All groups except the one to be evacuated are potential targets
14660       self.target_uuids = [group_uuid for group_uuid in owned_groups
14661                            if group_uuid != self.group_uuid]
14662
14663       if not self.target_uuids:
14664         raise errors.OpPrereqError("There are no possible target groups",
14665                                    errors.ECODE_INVAL)
14666
14667   def BuildHooksEnv(self):
14668     """Build hooks env.
14669
14670     """
14671     return {
14672       "GROUP_NAME": self.op.group_name,
14673       "TARGET_GROUPS": " ".join(self.target_uuids),
14674       }
14675
14676   def BuildHooksNodes(self):
14677     """Build hooks nodes.
14678
14679     """
14680     mn = self.cfg.GetMasterNode()
14681
14682     assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
14683
14684     run_nodes = [mn] + self.cfg.GetNodeGroup(self.group_uuid).members
14685
14686     return (run_nodes, run_nodes)
14687
14688   def Exec(self, feedback_fn):
14689     instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
14690
14691     assert self.group_uuid not in self.target_uuids
14692
14693     req = iallocator.IAReqGroupChange(instances=instances,
14694                                       target_groups=self.target_uuids)
14695     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
14696
14697     ial.Run(self.op.iallocator)
14698
14699     if not ial.success:
14700       raise errors.OpPrereqError("Can't compute group evacuation using"
14701                                  " iallocator '%s': %s" %
14702                                  (self.op.iallocator, ial.info),
14703                                  errors.ECODE_NORES)
14704
14705     jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
14706
14707     self.LogInfo("Iallocator returned %s job(s) for evacuating node group %s",
14708                  len(jobs), self.op.group_name)
14709
14710     return ResultWithJobs(jobs)
14711
14712
14713 class TagsLU(NoHooksLU): # pylint: disable=W0223
14714   """Generic tags LU.
14715
14716   This is an abstract class which is the parent of all the other tags LUs.
14717
14718   """
14719   def ExpandNames(self):
14720     self.group_uuid = None
14721     self.needed_locks = {}
14722
14723     if self.op.kind == constants.TAG_NODE:
14724       self.op.name = _ExpandNodeName(self.cfg, self.op.name)
14725       lock_level = locking.LEVEL_NODE
14726       lock_name = self.op.name
14727     elif self.op.kind == constants.TAG_INSTANCE:
14728       self.op.name = _ExpandInstanceName(self.cfg, self.op.name)
14729       lock_level = locking.LEVEL_INSTANCE
14730       lock_name = self.op.name
14731     elif self.op.kind == constants.TAG_NODEGROUP:
14732       self.group_uuid = self.cfg.LookupNodeGroup(self.op.name)
14733       lock_level = locking.LEVEL_NODEGROUP
14734       lock_name = self.group_uuid
14735     else:
14736       lock_level = None
14737       lock_name = None
14738
14739     if lock_level and getattr(self.op, "use_locking", True):
14740       self.needed_locks[lock_level] = lock_name
14741
14742     # FIXME: Acquire BGL for cluster tag operations (as of this writing it's
14743     # not possible to acquire the BGL based on opcode parameters)
14744
14745   def CheckPrereq(self):
14746     """Check prerequisites.
14747
14748     """
14749     if self.op.kind == constants.TAG_CLUSTER:
14750       self.target = self.cfg.GetClusterInfo()
14751     elif self.op.kind == constants.TAG_NODE:
14752       self.target = self.cfg.GetNodeInfo(self.op.name)
14753     elif self.op.kind == constants.TAG_INSTANCE:
14754       self.target = self.cfg.GetInstanceInfo(self.op.name)
14755     elif self.op.kind == constants.TAG_NODEGROUP:
14756       self.target = self.cfg.GetNodeGroup(self.group_uuid)
14757     else:
14758       raise errors.OpPrereqError("Wrong tag type requested (%s)" %
14759                                  str(self.op.kind), errors.ECODE_INVAL)
14760
14761
14762 class LUTagsGet(TagsLU):
14763   """Returns the tags of a given object.
14764
14765   """
14766   REQ_BGL = False
14767
14768   def ExpandNames(self):
14769     TagsLU.ExpandNames(self)
14770
14771     # Share locks as this is only a read operation
14772     self.share_locks = _ShareAll()
14773
14774   def Exec(self, feedback_fn):
14775     """Returns the tag list.
14776
14777     """
14778     return list(self.target.GetTags())
14779
14780
14781 class LUTagsSearch(NoHooksLU):
14782   """Searches the tags for a given pattern.
14783
14784   """
14785   REQ_BGL = False
14786
14787   def ExpandNames(self):
14788     self.needed_locks = {}
14789
14790   def CheckPrereq(self):
14791     """Check prerequisites.
14792
14793     This checks the pattern passed for validity by compiling it.
14794
14795     """
14796     try:
14797       self.re = re.compile(self.op.pattern)
14798     except re.error, err:
14799       raise errors.OpPrereqError("Invalid search pattern '%s': %s" %
14800                                  (self.op.pattern, err), errors.ECODE_INVAL)
14801
14802   def Exec(self, feedback_fn):
14803     """Returns the tag list.
14804
14805     """
14806     cfg = self.cfg
14807     tgts = [("/cluster", cfg.GetClusterInfo())]
14808     ilist = cfg.GetAllInstancesInfo().values()
14809     tgts.extend([("/instances/%s" % i.name, i) for i in ilist])
14810     nlist = cfg.GetAllNodesInfo().values()
14811     tgts.extend([("/nodes/%s" % n.name, n) for n in nlist])
14812     tgts.extend(("/nodegroup/%s" % n.name, n)
14813                 for n in cfg.GetAllNodeGroupsInfo().values())
14814     results = []
14815     for path, target in tgts:
14816       for tag in target.GetTags():
14817         if self.re.search(tag):
14818           results.append((path, tag))
14819     return results
14820
14821
14822 class LUTagsSet(TagsLU):
14823   """Sets a tag on a given object.
14824
14825   """
14826   REQ_BGL = False
14827
14828   def CheckPrereq(self):
14829     """Check prerequisites.
14830
14831     This checks the type and length of the tag name and value.
14832
14833     """
14834     TagsLU.CheckPrereq(self)
14835     for tag in self.op.tags:
14836       objects.TaggableObject.ValidateTag(tag)
14837
14838   def Exec(self, feedback_fn):
14839     """Sets the tag.
14840
14841     """
14842     try:
14843       for tag in self.op.tags:
14844         self.target.AddTag(tag)
14845     except errors.TagError, err:
14846       raise errors.OpExecError("Error while setting tag: %s" % str(err))
14847     self.cfg.Update(self.target, feedback_fn)
14848
14849
14850 class LUTagsDel(TagsLU):
14851   """Delete a list of tags from a given object.
14852
14853   """
14854   REQ_BGL = False
14855
14856   def CheckPrereq(self):
14857     """Check prerequisites.
14858
14859     This checks that we have the given tag.
14860
14861     """
14862     TagsLU.CheckPrereq(self)
14863     for tag in self.op.tags:
14864       objects.TaggableObject.ValidateTag(tag)
14865     del_tags = frozenset(self.op.tags)
14866     cur_tags = self.target.GetTags()
14867
14868     diff_tags = del_tags - cur_tags
14869     if diff_tags:
14870       diff_names = ("'%s'" % i for i in sorted(diff_tags))
14871       raise errors.OpPrereqError("Tag(s) %s not found" %
14872                                  (utils.CommaJoin(diff_names), ),
14873                                  errors.ECODE_NOENT)
14874
14875   def Exec(self, feedback_fn):
14876     """Remove the tag from the object.
14877
14878     """
14879     for tag in self.op.tags:
14880       self.target.RemoveTag(tag)
14881     self.cfg.Update(self.target, feedback_fn)
14882
14883
14884 class LUTestDelay(NoHooksLU):
14885   """Sleep for a specified amount of time.
14886
14887   This LU sleeps on the master and/or nodes for a specified amount of
14888   time.
14889
14890   """
14891   REQ_BGL = False
14892
14893   def ExpandNames(self):
14894     """Expand names and set required locks.
14895
14896     This expands the node list, if any.
14897
14898     """
14899     self.needed_locks = {}
14900     if self.op.on_nodes:
14901       # _GetWantedNodes can be used here, but is not always appropriate to use
14902       # this way in ExpandNames. Check LogicalUnit.ExpandNames docstring for
14903       # more information.
14904       self.op.on_nodes = _GetWantedNodes(self, self.op.on_nodes)
14905       self.needed_locks[locking.LEVEL_NODE] = self.op.on_nodes
14906
14907   def _TestDelay(self):
14908     """Do the actual sleep.
14909
14910     """
14911     if self.op.on_master:
14912       if not utils.TestDelay(self.op.duration):
14913         raise errors.OpExecError("Error during master delay test")
14914     if self.op.on_nodes:
14915       result = self.rpc.call_test_delay(self.op.on_nodes, self.op.duration)
14916       for node, node_result in result.items():
14917         node_result.Raise("Failure during rpc call to node %s" % node)
14918
14919   def Exec(self, feedback_fn):
14920     """Execute the test delay opcode, with the wanted repetitions.
14921
14922     """
14923     if self.op.repeat == 0:
14924       self._TestDelay()
14925     else:
14926       top_value = self.op.repeat - 1
14927       for i in range(self.op.repeat):
14928         self.LogInfo("Test delay iteration %d/%d" % (i, top_value))
14929         self._TestDelay()
14930
14931
14932 class LUTestJqueue(NoHooksLU):
14933   """Utility LU to test some aspects of the job queue.
14934
14935   """
14936   REQ_BGL = False
14937
14938   # Must be lower than default timeout for WaitForJobChange to see whether it
14939   # notices changed jobs
14940   _CLIENT_CONNECT_TIMEOUT = 20.0
14941   _CLIENT_CONFIRM_TIMEOUT = 60.0
14942
14943   @classmethod
14944   def _NotifyUsingSocket(cls, cb, errcls):
14945     """Opens a Unix socket and waits for another program to connect.
14946
14947     @type cb: callable
14948     @param cb: Callback to send socket name to client
14949     @type errcls: class
14950     @param errcls: Exception class to use for errors
14951
14952     """
14953     # Using a temporary directory as there's no easy way to create temporary
14954     # sockets without writing a custom loop around tempfile.mktemp and
14955     # socket.bind
14956     tmpdir = tempfile.mkdtemp()
14957     try:
14958       tmpsock = utils.PathJoin(tmpdir, "sock")
14959
14960       logging.debug("Creating temporary socket at %s", tmpsock)
14961       sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
14962       try:
14963         sock.bind(tmpsock)
14964         sock.listen(1)
14965
14966         # Send details to client
14967         cb(tmpsock)
14968
14969         # Wait for client to connect before continuing
14970         sock.settimeout(cls._CLIENT_CONNECT_TIMEOUT)
14971         try:
14972           (conn, _) = sock.accept()
14973         except socket.error, err:
14974           raise errcls("Client didn't connect in time (%s)" % err)
14975       finally:
14976         sock.close()
14977     finally:
14978       # Remove as soon as client is connected
14979       shutil.rmtree(tmpdir)
14980
14981     # Wait for client to close
14982     try:
14983       try:
14984         # pylint: disable=E1101
14985         # Instance of '_socketobject' has no ... member
14986         conn.settimeout(cls._CLIENT_CONFIRM_TIMEOUT)
14987         conn.recv(1)
14988       except socket.error, err:
14989         raise errcls("Client failed to confirm notification (%s)" % err)
14990     finally:
14991       conn.close()
14992
14993   def _SendNotification(self, test, arg, sockname):
14994     """Sends a notification to the client.
14995
14996     @type test: string
14997     @param test: Test name
14998     @param arg: Test argument (depends on test)
14999     @type sockname: string
15000     @param sockname: Socket path
15001
15002     """
15003     self.Log(constants.ELOG_JQUEUE_TEST, (sockname, test, arg))
15004
15005   def _Notify(self, prereq, test, arg):
15006     """Notifies the client of a test.
15007
15008     @type prereq: bool
15009     @param prereq: Whether this is a prereq-phase test
15010     @type test: string
15011     @param test: Test name
15012     @param arg: Test argument (depends on test)
15013
15014     """
15015     if prereq:
15016       errcls = errors.OpPrereqError
15017     else:
15018       errcls = errors.OpExecError
15019
15020     return self._NotifyUsingSocket(compat.partial(self._SendNotification,
15021                                                   test, arg),
15022                                    errcls)
15023
15024   def CheckArguments(self):
15025     self.checkargs_calls = getattr(self, "checkargs_calls", 0) + 1
15026     self.expandnames_calls = 0
15027
15028   def ExpandNames(self):
15029     checkargs_calls = getattr(self, "checkargs_calls", 0)
15030     if checkargs_calls < 1:
15031       raise errors.ProgrammerError("CheckArguments was not called")
15032
15033     self.expandnames_calls += 1
15034
15035     if self.op.notify_waitlock:
15036       self._Notify(True, constants.JQT_EXPANDNAMES, None)
15037
15038     self.LogInfo("Expanding names")
15039
15040     # Get lock on master node (just to get a lock, not for a particular reason)
15041     self.needed_locks = {
15042       locking.LEVEL_NODE: self.cfg.GetMasterNode(),
15043       }
15044
15045   def Exec(self, feedback_fn):
15046     if self.expandnames_calls < 1:
15047       raise errors.ProgrammerError("ExpandNames was not called")
15048
15049     if self.op.notify_exec:
15050       self._Notify(False, constants.JQT_EXEC, None)
15051
15052     self.LogInfo("Executing")
15053
15054     if self.op.log_messages:
15055       self._Notify(False, constants.JQT_STARTMSG, len(self.op.log_messages))
15056       for idx, msg in enumerate(self.op.log_messages):
15057         self.LogInfo("Sending log message %s", idx + 1)
15058         feedback_fn(constants.JQT_MSGPREFIX + msg)
15059         # Report how many test messages have been sent
15060         self._Notify(False, constants.JQT_LOGMSG, idx + 1)
15061
15062     if self.op.fail:
15063       raise errors.OpExecError("Opcode failure was requested")
15064
15065     return True
15066
15067
15068 class LUTestAllocator(NoHooksLU):
15069   """Run allocator tests.
15070
15071   This LU runs the allocator tests
15072
15073   """
15074   def CheckPrereq(self):
15075     """Check prerequisites.
15076
15077     This checks the opcode parameters depending on the director and mode test.
15078
15079     """
15080     if self.op.mode in (constants.IALLOCATOR_MODE_ALLOC,
15081                         constants.IALLOCATOR_MODE_MULTI_ALLOC):
15082       for attr in ["memory", "disks", "disk_template",
15083                    "os", "tags", "nics", "vcpus"]:
15084         if not hasattr(self.op, attr):
15085           raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
15086                                      attr, errors.ECODE_INVAL)
15087       iname = self.cfg.ExpandInstanceName(self.op.name)
15088       if iname is not None:
15089         raise errors.OpPrereqError("Instance '%s' already in the cluster" %
15090                                    iname, errors.ECODE_EXISTS)
15091       if not isinstance(self.op.nics, list):
15092         raise errors.OpPrereqError("Invalid parameter 'nics'",
15093                                    errors.ECODE_INVAL)
15094       if not isinstance(self.op.disks, list):
15095         raise errors.OpPrereqError("Invalid parameter 'disks'",
15096                                    errors.ECODE_INVAL)
15097       for row in self.op.disks:
15098         if (not isinstance(row, dict) or
15099             constants.IDISK_SIZE not in row or
15100             not isinstance(row[constants.IDISK_SIZE], int) or
15101             constants.IDISK_MODE not in row or
15102             row[constants.IDISK_MODE] not in constants.DISK_ACCESS_SET):
15103           raise errors.OpPrereqError("Invalid contents of the 'disks'"
15104                                      " parameter", errors.ECODE_INVAL)
15105       if self.op.hypervisor is None:
15106         self.op.hypervisor = self.cfg.GetHypervisorType()
15107     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
15108       fname = _ExpandInstanceName(self.cfg, self.op.name)
15109       self.op.name = fname
15110       self.relocate_from = \
15111           list(self.cfg.GetInstanceInfo(fname).secondary_nodes)
15112     elif self.op.mode in (constants.IALLOCATOR_MODE_CHG_GROUP,
15113                           constants.IALLOCATOR_MODE_NODE_EVAC):
15114       if not self.op.instances:
15115         raise errors.OpPrereqError("Missing instances", errors.ECODE_INVAL)
15116       self.op.instances = _GetWantedInstances(self, self.op.instances)
15117     else:
15118       raise errors.OpPrereqError("Invalid test allocator mode '%s'" %
15119                                  self.op.mode, errors.ECODE_INVAL)
15120
15121     if self.op.direction == constants.IALLOCATOR_DIR_OUT:
15122       if self.op.allocator is None:
15123         raise errors.OpPrereqError("Missing allocator name",
15124                                    errors.ECODE_INVAL)
15125     elif self.op.direction != constants.IALLOCATOR_DIR_IN:
15126       raise errors.OpPrereqError("Wrong allocator test '%s'" %
15127                                  self.op.direction, errors.ECODE_INVAL)
15128
15129   def Exec(self, feedback_fn):
15130     """Run the allocator test.
15131
15132     """
15133     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
15134       req = iallocator.IAReqInstanceAlloc(name=self.op.name,
15135                                           memory=self.op.memory,
15136                                           disks=self.op.disks,
15137                                           disk_template=self.op.disk_template,
15138                                           os=self.op.os,
15139                                           tags=self.op.tags,
15140                                           nics=self.op.nics,
15141                                           vcpus=self.op.vcpus,
15142                                           spindle_use=self.op.spindle_use,
15143                                           hypervisor=self.op.hypervisor)
15144     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
15145       req = iallocator.IAReqRelocate(name=self.op.name,
15146                                      relocate_from=list(self.relocate_from))
15147     elif self.op.mode == constants.IALLOCATOR_MODE_CHG_GROUP:
15148       req = iallocator.IAReqGroupChange(instances=self.op.instances,
15149                                         target_groups=self.op.target_groups)
15150     elif self.op.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
15151       req = iallocator.IAReqNodeEvac(instances=self.op.instances,
15152                                      evac_mode=self.op.evac_mode)
15153     elif self.op.mode == constants.IALLOCATOR_MODE_MULTI_ALLOC:
15154       disk_template = self.op.disk_template
15155       insts = [iallocator.IAReqInstanceAlloc(name="%s%s" % (self.op.name, idx),
15156                                              memory=self.op.memory,
15157                                              disks=self.op.disks,
15158                                              disk_template=disk_template,
15159                                              os=self.op.os,
15160                                              tags=self.op.tags,
15161                                              nics=self.op.nics,
15162                                              vcpus=self.op.vcpus,
15163                                              spindle_use=self.op.spindle_use,
15164                                              hypervisor=self.op.hypervisor)
15165                for idx in range(self.op.count)]
15166       req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
15167     else:
15168       raise errors.ProgrammerError("Uncatched mode %s in"
15169                                    " LUTestAllocator.Exec", self.op.mode)
15170
15171     ial = iallocator.IAllocator(self.cfg, self.rpc, req)
15172     if self.op.direction == constants.IALLOCATOR_DIR_IN:
15173       result = ial.in_text
15174     else:
15175       ial.Run(self.op.allocator, validate=False)
15176       result = ial.out_text
15177     return result
15178
15179
15180 #: Query type implementations
15181 _QUERY_IMPL = {
15182   constants.QR_CLUSTER: _ClusterQuery,
15183   constants.QR_INSTANCE: _InstanceQuery,
15184   constants.QR_NODE: _NodeQuery,
15185   constants.QR_GROUP: _GroupQuery,
15186   constants.QR_OS: _OsQuery,
15187   constants.QR_EXPORT: _ExportQuery,
15188   }
15189
15190 assert set(_QUERY_IMPL.keys()) == constants.QR_VIA_OP
15191
15192
15193 def _GetQueryImplementation(name):
15194   """Returns the implemtnation for a query type.
15195
15196   @param name: Query type, must be one of L{constants.QR_VIA_OP}
15197
15198   """
15199   try:
15200     return _QUERY_IMPL[name]
15201   except KeyError:
15202     raise errors.OpPrereqError("Unknown query resource '%s'" % name,
15203                                errors.ECODE_INVAL)