cmdlib: Adapt the rpc calls
[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 serializer
52 from ganeti import ssconf
53 from ganeti import uidpool
54 from ganeti import compat
55 from ganeti import masterd
56 from ganeti import netutils
57 from ganeti import query
58 from ganeti import qlang
59 from ganeti import opcodes
60 from ganeti import ht
61 from ganeti import rpc
62 from ganeti import runtime
63
64 import ganeti.masterd.instance # pylint: disable=W0611
65
66
67 #: Size of DRBD meta block device
68 DRBD_META_SIZE = 128
69
70 # States of instance
71 INSTANCE_DOWN = [constants.ADMINST_DOWN]
72 INSTANCE_ONLINE = [constants.ADMINST_DOWN, constants.ADMINST_UP]
73 INSTANCE_NOT_RUNNING = [constants.ADMINST_DOWN, constants.ADMINST_OFFLINE]
74
75 #: Instance status in which an instance can be marked as offline/online
76 CAN_CHANGE_INSTANCE_OFFLINE = (frozenset(INSTANCE_DOWN) | frozenset([
77   constants.ADMINST_OFFLINE,
78   ]))
79
80
81 class ResultWithJobs:
82   """Data container for LU results with jobs.
83
84   Instances of this class returned from L{LogicalUnit.Exec} will be recognized
85   by L{mcpu._ProcessResult}. The latter will then submit the jobs
86   contained in the C{jobs} attribute and include the job IDs in the opcode
87   result.
88
89   """
90   def __init__(self, jobs, **kwargs):
91     """Initializes this class.
92
93     Additional return values can be specified as keyword arguments.
94
95     @type jobs: list of lists of L{opcode.OpCode}
96     @param jobs: A list of lists of opcode objects
97
98     """
99     self.jobs = jobs
100     self.other = kwargs
101
102
103 class LogicalUnit(object):
104   """Logical Unit base class.
105
106   Subclasses must follow these rules:
107     - implement ExpandNames
108     - implement CheckPrereq (except when tasklets are used)
109     - implement Exec (except when tasklets are used)
110     - implement BuildHooksEnv
111     - implement BuildHooksNodes
112     - redefine HPATH and HTYPE
113     - optionally redefine their run requirements:
114         REQ_BGL: the LU needs to hold the Big Ganeti Lock exclusively
115
116   Note that all commands require root permissions.
117
118   @ivar dry_run_result: the value (if any) that will be returned to the caller
119       in dry-run mode (signalled by opcode dry_run parameter)
120
121   """
122   HPATH = None
123   HTYPE = None
124   REQ_BGL = True
125
126   def __init__(self, processor, op, context, rpc_runner):
127     """Constructor for LogicalUnit.
128
129     This needs to be overridden in derived classes in order to check op
130     validity.
131
132     """
133     self.proc = processor
134     self.op = op
135     self.cfg = context.cfg
136     self.glm = context.glm
137     # readability alias
138     self.owned_locks = context.glm.list_owned
139     self.context = context
140     self.rpc = rpc_runner
141     # Dicts used to declare locking needs to mcpu
142     self.needed_locks = None
143     self.share_locks = dict.fromkeys(locking.LEVELS, 0)
144     self.add_locks = {}
145     self.remove_locks = {}
146     # Used to force good behavior when calling helper functions
147     self.recalculate_locks = {}
148     # logging
149     self.Log = processor.Log # pylint: disable=C0103
150     self.LogWarning = processor.LogWarning # pylint: disable=C0103
151     self.LogInfo = processor.LogInfo # pylint: disable=C0103
152     self.LogStep = processor.LogStep # pylint: disable=C0103
153     # support for dry-run
154     self.dry_run_result = None
155     # support for generic debug attribute
156     if (not hasattr(self.op, "debug_level") or
157         not isinstance(self.op.debug_level, int)):
158       self.op.debug_level = 0
159
160     # Tasklets
161     self.tasklets = None
162
163     # Validate opcode parameters and set defaults
164     self.op.Validate(True)
165
166     self.CheckArguments()
167
168   def CheckArguments(self):
169     """Check syntactic validity for the opcode arguments.
170
171     This method is for doing a simple syntactic check and ensure
172     validity of opcode parameters, without any cluster-related
173     checks. While the same can be accomplished in ExpandNames and/or
174     CheckPrereq, doing these separate is better because:
175
176       - ExpandNames is left as as purely a lock-related function
177       - CheckPrereq is run after we have acquired locks (and possible
178         waited for them)
179
180     The function is allowed to change the self.op attribute so that
181     later methods can no longer worry about missing parameters.
182
183     """
184     pass
185
186   def ExpandNames(self):
187     """Expand names for this LU.
188
189     This method is called before starting to execute the opcode, and it should
190     update all the parameters of the opcode to their canonical form (e.g. a
191     short node name must be fully expanded after this method has successfully
192     completed). This way locking, hooks, logging, etc. can work correctly.
193
194     LUs which implement this method must also populate the self.needed_locks
195     member, as a dict with lock levels as keys, and a list of needed lock names
196     as values. Rules:
197
198       - use an empty dict if you don't need any lock
199       - if you don't need any lock at a particular level omit that
200         level (note that in this case C{DeclareLocks} won't be called
201         at all for that level)
202       - if you need locks at a level, but you can't calculate it in
203         this function, initialise that level with an empty list and do
204         further processing in L{LogicalUnit.DeclareLocks} (see that
205         function's docstring)
206       - don't put anything for the BGL level
207       - if you want all locks at a level use L{locking.ALL_SET} as a value
208
209     If you need to share locks (rather than acquire them exclusively) at one
210     level you can modify self.share_locks, setting a true value (usually 1) for
211     that level. By default locks are not shared.
212
213     This function can also define a list of tasklets, which then will be
214     executed in order instead of the usual LU-level CheckPrereq and Exec
215     functions, if those are not defined by the LU.
216
217     Examples::
218
219       # Acquire all nodes and one instance
220       self.needed_locks = {
221         locking.LEVEL_NODE: locking.ALL_SET,
222         locking.LEVEL_INSTANCE: ['instance1.example.com'],
223       }
224       # Acquire just two nodes
225       self.needed_locks = {
226         locking.LEVEL_NODE: ['node1.example.com', 'node2.example.com'],
227       }
228       # Acquire no locks
229       self.needed_locks = {} # No, you can't leave it to the default value None
230
231     """
232     # The implementation of this method is mandatory only if the new LU is
233     # concurrent, so that old LUs don't need to be changed all at the same
234     # time.
235     if self.REQ_BGL:
236       self.needed_locks = {} # Exclusive LUs don't need locks.
237     else:
238       raise NotImplementedError
239
240   def DeclareLocks(self, level):
241     """Declare LU locking needs for a level
242
243     While most LUs can just declare their locking needs at ExpandNames time,
244     sometimes there's the need to calculate some locks after having acquired
245     the ones before. This function is called just before acquiring locks at a
246     particular level, but after acquiring the ones at lower levels, and permits
247     such calculations. It can be used to modify self.needed_locks, and by
248     default it does nothing.
249
250     This function is only called if you have something already set in
251     self.needed_locks for the level.
252
253     @param level: Locking level which is going to be locked
254     @type level: member of L{ganeti.locking.LEVELS}
255
256     """
257
258   def CheckPrereq(self):
259     """Check prerequisites for this LU.
260
261     This method should check that the prerequisites for the execution
262     of this LU are fulfilled. It can do internode communication, but
263     it should be idempotent - no cluster or system changes are
264     allowed.
265
266     The method should raise errors.OpPrereqError in case something is
267     not fulfilled. Its return value is ignored.
268
269     This method should also update all the parameters of the opcode to
270     their canonical form if it hasn't been done by ExpandNames before.
271
272     """
273     if self.tasklets is not None:
274       for (idx, tl) in enumerate(self.tasklets):
275         logging.debug("Checking prerequisites for tasklet %s/%s",
276                       idx + 1, len(self.tasklets))
277         tl.CheckPrereq()
278     else:
279       pass
280
281   def Exec(self, feedback_fn):
282     """Execute the LU.
283
284     This method should implement the actual work. It should raise
285     errors.OpExecError for failures that are somewhat dealt with in
286     code, or expected.
287
288     """
289     if self.tasklets is not None:
290       for (idx, tl) in enumerate(self.tasklets):
291         logging.debug("Executing tasklet %s/%s", idx + 1, len(self.tasklets))
292         tl.Exec(feedback_fn)
293     else:
294       raise NotImplementedError
295
296   def BuildHooksEnv(self):
297     """Build hooks environment for this LU.
298
299     @rtype: dict
300     @return: Dictionary containing the environment that will be used for
301       running the hooks for this LU. The keys of the dict must not be prefixed
302       with "GANETI_"--that'll be added by the hooks runner. The hooks runner
303       will extend the environment with additional variables. If no environment
304       should be defined, an empty dictionary should be returned (not C{None}).
305     @note: If the C{HPATH} attribute of the LU class is C{None}, this function
306       will not be called.
307
308     """
309     raise NotImplementedError
310
311   def BuildHooksNodes(self):
312     """Build list of nodes to run LU's hooks.
313
314     @rtype: tuple; (list, list)
315     @return: Tuple containing a list of node names on which the hook
316       should run before the execution and a list of node names on which the
317       hook should run after the execution. No nodes should be returned as an
318       empty list (and not None).
319     @note: If the C{HPATH} attribute of the LU class is C{None}, this function
320       will not be called.
321
322     """
323     raise NotImplementedError
324
325   def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result):
326     """Notify the LU about the results of its hooks.
327
328     This method is called every time a hooks phase is executed, and notifies
329     the Logical Unit about the hooks' result. The LU can then use it to alter
330     its result based on the hooks.  By default the method does nothing and the
331     previous result is passed back unchanged but any LU can define it if it
332     wants to use the local cluster hook-scripts somehow.
333
334     @param phase: one of L{constants.HOOKS_PHASE_POST} or
335         L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
336     @param hook_results: the results of the multi-node hooks rpc call
337     @param feedback_fn: function used send feedback back to the caller
338     @param lu_result: the previous Exec result this LU had, or None
339         in the PRE phase
340     @return: the new Exec result, based on the previous result
341         and hook results
342
343     """
344     # API must be kept, thus we ignore the unused argument and could
345     # be a function warnings
346     # pylint: disable=W0613,R0201
347     return lu_result
348
349   def _ExpandAndLockInstance(self):
350     """Helper function to expand and lock an instance.
351
352     Many LUs that work on an instance take its name in self.op.instance_name
353     and need to expand it and then declare the expanded name for locking. This
354     function does it, and then updates self.op.instance_name to the expanded
355     name. It also initializes needed_locks as a dict, if this hasn't been done
356     before.
357
358     """
359     if self.needed_locks is None:
360       self.needed_locks = {}
361     else:
362       assert locking.LEVEL_INSTANCE not in self.needed_locks, \
363         "_ExpandAndLockInstance called with instance-level locks set"
364     self.op.instance_name = _ExpandInstanceName(self.cfg,
365                                                 self.op.instance_name)
366     self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
367
368   def _LockInstancesNodes(self, primary_only=False,
369                           level=locking.LEVEL_NODE):
370     """Helper function to declare instances' nodes for locking.
371
372     This function should be called after locking one or more instances to lock
373     their nodes. Its effect is populating self.needed_locks[locking.LEVEL_NODE]
374     with all primary or secondary nodes for instances already locked and
375     present in self.needed_locks[locking.LEVEL_INSTANCE].
376
377     It should be called from DeclareLocks, and for safety only works if
378     self.recalculate_locks[locking.LEVEL_NODE] is set.
379
380     In the future it may grow parameters to just lock some instance's nodes, or
381     to just lock primaries or secondary nodes, if needed.
382
383     If should be called in DeclareLocks in a way similar to::
384
385       if level == locking.LEVEL_NODE:
386         self._LockInstancesNodes()
387
388     @type primary_only: boolean
389     @param primary_only: only lock primary nodes of locked instances
390     @param level: Which lock level to use for locking nodes
391
392     """
393     assert level in self.recalculate_locks, \
394       "_LockInstancesNodes helper function called with no nodes to recalculate"
395
396     # TODO: check if we're really been called with the instance locks held
397
398     # For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the
399     # future we might want to have different behaviors depending on the value
400     # of self.recalculate_locks[locking.LEVEL_NODE]
401     wanted_nodes = []
402     locked_i = self.owned_locks(locking.LEVEL_INSTANCE)
403     for _, instance in self.cfg.GetMultiInstanceInfo(locked_i):
404       wanted_nodes.append(instance.primary_node)
405       if not primary_only:
406         wanted_nodes.extend(instance.secondary_nodes)
407
408     if self.recalculate_locks[level] == constants.LOCKS_REPLACE:
409       self.needed_locks[level] = wanted_nodes
410     elif self.recalculate_locks[level] == constants.LOCKS_APPEND:
411       self.needed_locks[level].extend(wanted_nodes)
412     else:
413       raise errors.ProgrammerError("Unknown recalculation mode")
414
415     del self.recalculate_locks[level]
416
417
418 class NoHooksLU(LogicalUnit): # pylint: disable=W0223
419   """Simple LU which runs no hooks.
420
421   This LU is intended as a parent for other LogicalUnits which will
422   run no hooks, in order to reduce duplicate code.
423
424   """
425   HPATH = None
426   HTYPE = None
427
428   def BuildHooksEnv(self):
429     """Empty BuildHooksEnv for NoHooksLu.
430
431     This just raises an error.
432
433     """
434     raise AssertionError("BuildHooksEnv called for NoHooksLUs")
435
436   def BuildHooksNodes(self):
437     """Empty BuildHooksNodes for NoHooksLU.
438
439     """
440     raise AssertionError("BuildHooksNodes called for NoHooksLU")
441
442
443 class Tasklet:
444   """Tasklet base class.
445
446   Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
447   they can mix legacy code with tasklets. Locking needs to be done in the LU,
448   tasklets know nothing about locks.
449
450   Subclasses must follow these rules:
451     - Implement CheckPrereq
452     - Implement Exec
453
454   """
455   def __init__(self, lu):
456     self.lu = lu
457
458     # Shortcuts
459     self.cfg = lu.cfg
460     self.rpc = lu.rpc
461
462   def CheckPrereq(self):
463     """Check prerequisites for this tasklets.
464
465     This method should check whether the prerequisites for the execution of
466     this tasklet are fulfilled. It can do internode communication, but it
467     should be idempotent - no cluster or system changes are allowed.
468
469     The method should raise errors.OpPrereqError in case something is not
470     fulfilled. Its return value is ignored.
471
472     This method should also update all parameters to their canonical form if it
473     hasn't been done before.
474
475     """
476     pass
477
478   def Exec(self, feedback_fn):
479     """Execute the tasklet.
480
481     This method should implement the actual work. It should raise
482     errors.OpExecError for failures that are somewhat dealt with in code, or
483     expected.
484
485     """
486     raise NotImplementedError
487
488
489 class _QueryBase:
490   """Base for query utility classes.
491
492   """
493   #: Attribute holding field definitions
494   FIELDS = None
495
496   #: Field to sort by
497   SORT_FIELD = "name"
498
499   def __init__(self, qfilter, fields, use_locking):
500     """Initializes this class.
501
502     """
503     self.use_locking = use_locking
504
505     self.query = query.Query(self.FIELDS, fields, qfilter=qfilter,
506                              namefield=self.SORT_FIELD)
507     self.requested_data = self.query.RequestedData()
508     self.names = self.query.RequestedNames()
509
510     # Sort only if no names were requested
511     self.sort_by_name = not self.names
512
513     self.do_locking = None
514     self.wanted = None
515
516   def _GetNames(self, lu, all_names, lock_level):
517     """Helper function to determine names asked for in the query.
518
519     """
520     if self.do_locking:
521       names = lu.owned_locks(lock_level)
522     else:
523       names = all_names
524
525     if self.wanted == locking.ALL_SET:
526       assert not self.names
527       # caller didn't specify names, so ordering is not important
528       return utils.NiceSort(names)
529
530     # caller specified names and we must keep the same order
531     assert self.names
532     assert not self.do_locking or lu.glm.is_owned(lock_level)
533
534     missing = set(self.wanted).difference(names)
535     if missing:
536       raise errors.OpExecError("Some items were removed before retrieving"
537                                " their data: %s" % missing)
538
539     # Return expanded names
540     return self.wanted
541
542   def ExpandNames(self, lu):
543     """Expand names for this query.
544
545     See L{LogicalUnit.ExpandNames}.
546
547     """
548     raise NotImplementedError()
549
550   def DeclareLocks(self, lu, level):
551     """Declare locks for this query.
552
553     See L{LogicalUnit.DeclareLocks}.
554
555     """
556     raise NotImplementedError()
557
558   def _GetQueryData(self, lu):
559     """Collects all data for this query.
560
561     @return: Query data object
562
563     """
564     raise NotImplementedError()
565
566   def NewStyleQuery(self, lu):
567     """Collect data and execute query.
568
569     """
570     return query.GetQueryResponse(self.query, self._GetQueryData(lu),
571                                   sort_by_name=self.sort_by_name)
572
573   def OldStyleQuery(self, lu):
574     """Collect data and execute query.
575
576     """
577     return self.query.OldStyleQuery(self._GetQueryData(lu),
578                                     sort_by_name=self.sort_by_name)
579
580
581 def _ShareAll():
582   """Returns a dict declaring all lock levels shared.
583
584   """
585   return dict.fromkeys(locking.LEVELS, 1)
586
587
588 def _MakeLegacyNodeInfo(data):
589   """Formats the data returned by L{rpc.RpcRunner.call_node_info}.
590
591   Converts the data into a single dictionary. This is fine for most use cases,
592   but some require information from more than one volume group or hypervisor.
593
594   """
595   (bootid, (vg_info, ), (hv_info, )) = data
596
597   return utils.JoinDisjointDicts(utils.JoinDisjointDicts(vg_info, hv_info), {
598     "bootid": bootid,
599     })
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   """Checks if the owned node groups are still correct for an instance.
630
631   @type cfg: L{config.ConfigWriter}
632   @param cfg: The cluster configuration
633   @type instance_name: string
634   @param instance_name: Instance name
635   @type owned_groups: set or frozenset
636   @param owned_groups: List of currently owned node groups
637
638   """
639   inst_groups = cfg.GetInstanceNodeGroups(instance_name)
640
641   if not owned_groups.issuperset(inst_groups):
642     raise errors.OpPrereqError("Instance %s's node groups changed since"
643                                " locks were acquired, current groups are"
644                                " are '%s', owning groups '%s'; retry the"
645                                " operation" %
646                                (instance_name,
647                                 utils.CommaJoin(inst_groups),
648                                 utils.CommaJoin(owned_groups)),
649                                errors.ECODE_STATE)
650
651   return inst_groups
652
653
654 def _CheckNodeGroupInstances(cfg, group_uuid, owned_instances):
655   """Checks if the instances in a node group are still correct.
656
657   @type cfg: L{config.ConfigWriter}
658   @param cfg: The cluster configuration
659   @type group_uuid: string
660   @param group_uuid: Node group UUID
661   @type owned_instances: set or frozenset
662   @param owned_instances: List of currently owned instances
663
664   """
665   wanted_instances = cfg.GetNodeGroupInstances(group_uuid)
666   if owned_instances != wanted_instances:
667     raise errors.OpPrereqError("Instances in node group '%s' changed since"
668                                " locks were acquired, wanted '%s', have '%s';"
669                                " retry the operation" %
670                                (group_uuid,
671                                 utils.CommaJoin(wanted_instances),
672                                 utils.CommaJoin(owned_instances)),
673                                errors.ECODE_STATE)
674
675   return wanted_instances
676
677
678 def _SupportsOob(cfg, node):
679   """Tells if node supports OOB.
680
681   @type cfg: L{config.ConfigWriter}
682   @param cfg: The cluster configuration
683   @type node: L{objects.Node}
684   @param node: The node
685   @return: The OOB script if supported or an empty string otherwise
686
687   """
688   return cfg.GetNdParams(node)[constants.ND_OOB_PROGRAM]
689
690
691 def _GetWantedNodes(lu, nodes):
692   """Returns list of checked and expanded node names.
693
694   @type lu: L{LogicalUnit}
695   @param lu: the logical unit on whose behalf we execute
696   @type nodes: list
697   @param nodes: list of node names or None for all nodes
698   @rtype: list
699   @return: the list of nodes, sorted
700   @raise errors.ProgrammerError: if the nodes parameter is wrong type
701
702   """
703   if nodes:
704     return [_ExpandNodeName(lu.cfg, name) for name in nodes]
705
706   return utils.NiceSort(lu.cfg.GetNodeList())
707
708
709 def _GetWantedInstances(lu, instances):
710   """Returns list of checked and expanded instance names.
711
712   @type lu: L{LogicalUnit}
713   @param lu: the logical unit on whose behalf we execute
714   @type instances: list
715   @param instances: list of instance names or None for all instances
716   @rtype: list
717   @return: the list of instances, sorted
718   @raise errors.OpPrereqError: if the instances parameter is wrong type
719   @raise errors.OpPrereqError: if any of the passed instances is not found
720
721   """
722   if instances:
723     wanted = [_ExpandInstanceName(lu.cfg, name) for name in instances]
724   else:
725     wanted = utils.NiceSort(lu.cfg.GetInstanceList())
726   return wanted
727
728
729 def _GetUpdatedParams(old_params, update_dict,
730                       use_default=True, use_none=False):
731   """Return the new version of a parameter dictionary.
732
733   @type old_params: dict
734   @param old_params: old parameters
735   @type update_dict: dict
736   @param update_dict: dict containing new parameter values, or
737       constants.VALUE_DEFAULT to reset the parameter to its default
738       value
739   @param use_default: boolean
740   @type use_default: whether to recognise L{constants.VALUE_DEFAULT}
741       values as 'to be deleted' values
742   @param use_none: boolean
743   @type use_none: whether to recognise C{None} values as 'to be
744       deleted' values
745   @rtype: dict
746   @return: the new parameter dictionary
747
748   """
749   params_copy = copy.deepcopy(old_params)
750   for key, val in update_dict.iteritems():
751     if ((use_default and val == constants.VALUE_DEFAULT) or
752         (use_none and val is None)):
753       try:
754         del params_copy[key]
755       except KeyError:
756         pass
757     else:
758       params_copy[key] = val
759   return params_copy
760
761
762 def _GetUpdatedIPolicy(old_ipolicy, new_ipolicy, group_policy=False):
763   """Return the new version of a instance policy.
764
765   @param group_policy: whether this policy applies to a group and thus
766     we should support removal of policy entries
767
768   """
769   use_none = use_default = group_policy
770   ipolicy = copy.deepcopy(old_ipolicy)
771   for key, value in new_ipolicy.items():
772     if key not in constants.IPOLICY_ALL_KEYS:
773       raise errors.OpPrereqError("Invalid key in new ipolicy: %s" % key,
774                                  errors.ECODE_INVAL)
775     if key in constants.IPOLICY_ISPECS:
776       utils.ForceDictType(value, constants.ISPECS_PARAMETER_TYPES)
777       ipolicy[key] = _GetUpdatedParams(old_ipolicy.get(key, {}), value,
778                                        use_none=use_none,
779                                        use_default=use_default)
780     else:
781       if not value or value == [constants.VALUE_DEFAULT]:
782         if group_policy:
783           del ipolicy[key]
784         else:
785           raise errors.OpPrereqError("Can't unset ipolicy attribute '%s'"
786                                      " on the cluster'" % key,
787                                      errors.ECODE_INVAL)
788       else:
789         if key in constants.IPOLICY_PARAMETERS:
790           # FIXME: we assume all such values are float
791           try:
792             ipolicy[key] = float(value)
793           except (TypeError, ValueError), err:
794             raise errors.OpPrereqError("Invalid value for attribute"
795                                        " '%s': '%s', error: %s" %
796                                        (key, value, err), errors.ECODE_INVAL)
797         else:
798           # FIXME: we assume all others are lists; this should be redone
799           # in a nicer way
800           ipolicy[key] = list(value)
801   try:
802     objects.InstancePolicy.CheckParameterSyntax(ipolicy)
803   except errors.ConfigurationError, err:
804     raise errors.OpPrereqError("Invalid instance policy: %s" % err,
805                                errors.ECODE_INVAL)
806   return ipolicy
807
808
809 def _UpdateAndVerifySubDict(base, updates, type_check):
810   """Updates and verifies a dict with sub dicts of the same type.
811
812   @param base: The dict with the old data
813   @param updates: The dict with the new data
814   @param type_check: Dict suitable to ForceDictType to verify correct types
815   @returns: A new dict with updated and verified values
816
817   """
818   def fn(old, value):
819     new = _GetUpdatedParams(old, value)
820     utils.ForceDictType(new, type_check)
821     return new
822
823   ret = copy.deepcopy(base)
824   ret.update(dict((key, fn(base.get(key, {}), value))
825                   for key, value in updates.items()))
826   return ret
827
828
829 def _MergeAndVerifyHvState(op_input, obj_input):
830   """Combines the hv state from an opcode with the one of the object
831
832   @param op_input: The input dict from the opcode
833   @param obj_input: The input dict from the objects
834   @return: The verified and updated dict
835
836   """
837   if op_input:
838     invalid_hvs = set(op_input) - constants.HYPER_TYPES
839     if invalid_hvs:
840       raise errors.OpPrereqError("Invalid hypervisor(s) in hypervisor state:"
841                                  " %s" % utils.CommaJoin(invalid_hvs),
842                                  errors.ECODE_INVAL)
843     if obj_input is None:
844       obj_input = {}
845     type_check = constants.HVSTS_PARAMETER_TYPES
846     return _UpdateAndVerifySubDict(obj_input, op_input, type_check)
847
848   return None
849
850
851 def _MergeAndVerifyDiskState(op_input, obj_input):
852   """Combines the disk state from an opcode with the one of the object
853
854   @param op_input: The input dict from the opcode
855   @param obj_input: The input dict from the objects
856   @return: The verified and updated dict
857   """
858   if op_input:
859     invalid_dst = set(op_input) - constants.DS_VALID_TYPES
860     if invalid_dst:
861       raise errors.OpPrereqError("Invalid storage type(s) in disk state: %s" %
862                                  utils.CommaJoin(invalid_dst),
863                                  errors.ECODE_INVAL)
864     type_check = constants.DSS_PARAMETER_TYPES
865     if obj_input is None:
866       obj_input = {}
867     return dict((key, _UpdateAndVerifySubDict(obj_input.get(key, {}), value,
868                                               type_check))
869                 for key, value in op_input.items())
870
871   return None
872
873
874 def _ReleaseLocks(lu, level, names=None, keep=None):
875   """Releases locks owned by an LU.
876
877   @type lu: L{LogicalUnit}
878   @param level: Lock level
879   @type names: list or None
880   @param names: Names of locks to release
881   @type keep: list or None
882   @param keep: Names of locks to retain
883
884   """
885   assert not (keep is not None and names is not None), \
886          "Only one of the 'names' and the 'keep' parameters can be given"
887
888   if names is not None:
889     should_release = names.__contains__
890   elif keep:
891     should_release = lambda name: name not in keep
892   else:
893     should_release = None
894
895   owned = lu.owned_locks(level)
896   if not owned:
897     # Not owning any lock at this level, do nothing
898     pass
899
900   elif should_release:
901     retain = []
902     release = []
903
904     # Determine which locks to release
905     for name in owned:
906       if should_release(name):
907         release.append(name)
908       else:
909         retain.append(name)
910
911     assert len(lu.owned_locks(level)) == (len(retain) + len(release))
912
913     # Release just some locks
914     lu.glm.release(level, names=release)
915
916     assert frozenset(lu.owned_locks(level)) == frozenset(retain)
917   else:
918     # Release everything
919     lu.glm.release(level)
920
921     assert not lu.glm.is_owned(level), "No locks should be owned"
922
923
924 def _MapInstanceDisksToNodes(instances):
925   """Creates a map from (node, volume) to instance name.
926
927   @type instances: list of L{objects.Instance}
928   @rtype: dict; tuple of (node name, volume name) as key, instance name as value
929
930   """
931   return dict(((node, vol), inst.name)
932               for inst in instances
933               for (node, vols) in inst.MapLVsByNode().items()
934               for vol in vols)
935
936
937 def _RunPostHook(lu, node_name):
938   """Runs the post-hook for an opcode on a single node.
939
940   """
941   hm = lu.proc.BuildHooksManager(lu)
942   try:
943     hm.RunPhase(constants.HOOKS_PHASE_POST, nodes=[node_name])
944   except:
945     # pylint: disable=W0702
946     lu.LogWarning("Errors occurred running hooks on %s" % node_name)
947
948
949 def _CheckOutputFields(static, dynamic, selected):
950   """Checks whether all selected fields are valid.
951
952   @type static: L{utils.FieldSet}
953   @param static: static fields set
954   @type dynamic: L{utils.FieldSet}
955   @param dynamic: dynamic fields set
956
957   """
958   f = utils.FieldSet()
959   f.Extend(static)
960   f.Extend(dynamic)
961
962   delta = f.NonMatching(selected)
963   if delta:
964     raise errors.OpPrereqError("Unknown output fields selected: %s"
965                                % ",".join(delta), errors.ECODE_INVAL)
966
967
968 def _CheckGlobalHvParams(params):
969   """Validates that given hypervisor params are not global ones.
970
971   This will ensure that instances don't get customised versions of
972   global params.
973
974   """
975   used_globals = constants.HVC_GLOBALS.intersection(params)
976   if used_globals:
977     msg = ("The following hypervisor parameters are global and cannot"
978            " be customized at instance level, please modify them at"
979            " cluster level: %s" % utils.CommaJoin(used_globals))
980     raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
981
982
983 def _CheckNodeOnline(lu, node, msg=None):
984   """Ensure that a given node is online.
985
986   @param lu: the LU on behalf of which we make the check
987   @param node: the node to check
988   @param msg: if passed, should be a message to replace the default one
989   @raise errors.OpPrereqError: if the node is offline
990
991   """
992   if msg is None:
993     msg = "Can't use offline node"
994   if lu.cfg.GetNodeInfo(node).offline:
995     raise errors.OpPrereqError("%s: %s" % (msg, node), errors.ECODE_STATE)
996
997
998 def _CheckNodeNotDrained(lu, node):
999   """Ensure that a given node is not drained.
1000
1001   @param lu: the LU on behalf of which we make the check
1002   @param node: the node to check
1003   @raise errors.OpPrereqError: if the node is drained
1004
1005   """
1006   if lu.cfg.GetNodeInfo(node).drained:
1007     raise errors.OpPrereqError("Can't use drained node %s" % node,
1008                                errors.ECODE_STATE)
1009
1010
1011 def _CheckNodeVmCapable(lu, node):
1012   """Ensure that a given node is vm capable.
1013
1014   @param lu: the LU on behalf of which we make the check
1015   @param node: the node to check
1016   @raise errors.OpPrereqError: if the node is not vm capable
1017
1018   """
1019   if not lu.cfg.GetNodeInfo(node).vm_capable:
1020     raise errors.OpPrereqError("Can't use non-vm_capable node %s" % node,
1021                                errors.ECODE_STATE)
1022
1023
1024 def _CheckNodeHasOS(lu, node, os_name, force_variant):
1025   """Ensure that a node supports a given OS.
1026
1027   @param lu: the LU on behalf of which we make the check
1028   @param node: the node to check
1029   @param os_name: the OS to query about
1030   @param force_variant: whether to ignore variant errors
1031   @raise errors.OpPrereqError: if the node is not supporting the OS
1032
1033   """
1034   result = lu.rpc.call_os_get(node, os_name)
1035   result.Raise("OS '%s' not in supported OS list for node %s" %
1036                (os_name, node),
1037                prereq=True, ecode=errors.ECODE_INVAL)
1038   if not force_variant:
1039     _CheckOSVariant(result.payload, os_name)
1040
1041
1042 def _CheckNodeHasSecondaryIP(lu, node, secondary_ip, prereq):
1043   """Ensure that a node has the given secondary ip.
1044
1045   @type lu: L{LogicalUnit}
1046   @param lu: the LU on behalf of which we make the check
1047   @type node: string
1048   @param node: the node to check
1049   @type secondary_ip: string
1050   @param secondary_ip: the ip to check
1051   @type prereq: boolean
1052   @param prereq: whether to throw a prerequisite or an execute error
1053   @raise errors.OpPrereqError: if the node doesn't have the ip, and prereq=True
1054   @raise errors.OpExecError: if the node doesn't have the ip, and prereq=False
1055
1056   """
1057   result = lu.rpc.call_node_has_ip_address(node, secondary_ip)
1058   result.Raise("Failure checking secondary ip on node %s" % node,
1059                prereq=prereq, ecode=errors.ECODE_ENVIRON)
1060   if not result.payload:
1061     msg = ("Node claims it doesn't have the secondary ip you gave (%s),"
1062            " please fix and re-run this command" % secondary_ip)
1063     if prereq:
1064       raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
1065     else:
1066       raise errors.OpExecError(msg)
1067
1068
1069 def _GetClusterDomainSecret():
1070   """Reads the cluster domain secret.
1071
1072   """
1073   return utils.ReadOneLineFile(constants.CLUSTER_DOMAIN_SECRET_FILE,
1074                                strict=True)
1075
1076
1077 def _CheckInstanceState(lu, instance, req_states, msg=None):
1078   """Ensure that an instance is in one of the required states.
1079
1080   @param lu: the LU on behalf of which we make the check
1081   @param instance: the instance to check
1082   @param msg: if passed, should be a message to replace the default one
1083   @raise errors.OpPrereqError: if the instance is not in the required state
1084
1085   """
1086   if msg is None:
1087     msg = "can't use instance from outside %s states" % ", ".join(req_states)
1088   if instance.admin_state not in req_states:
1089     raise errors.OpPrereqError("Instance '%s' is marked to be %s, %s" %
1090                                (instance.name, instance.admin_state, msg),
1091                                errors.ECODE_STATE)
1092
1093   if constants.ADMINST_UP not in req_states:
1094     pnode = instance.primary_node
1095     ins_l = lu.rpc.call_instance_list([pnode], [instance.hypervisor])[pnode]
1096     ins_l.Raise("Can't contact node %s for instance information" % pnode,
1097                 prereq=True, ecode=errors.ECODE_ENVIRON)
1098
1099     if instance.name in ins_l.payload:
1100       raise errors.OpPrereqError("Instance %s is running, %s" %
1101                                  (instance.name, msg), errors.ECODE_STATE)
1102
1103
1104 def _ComputeMinMaxSpec(name, ipolicy, value):
1105   """Computes if value is in the desired range.
1106
1107   @param name: name of the parameter for which we perform the check
1108   @param ipolicy: dictionary containing min, max and std values
1109   @param value: actual value that we want to use
1110   @return: None or element not meeting the criteria
1111
1112
1113   """
1114   if value in [None, constants.VALUE_AUTO]:
1115     return None
1116   max_v = ipolicy[constants.ISPECS_MAX].get(name, value)
1117   min_v = ipolicy[constants.ISPECS_MIN].get(name, value)
1118   if value > max_v or min_v > value:
1119     return ("%s value %s is not in range [%s, %s]" %
1120             (name, value, min_v, max_v))
1121   return None
1122
1123
1124 def _ComputeIPolicySpecViolation(ipolicy, mem_size, cpu_count, disk_count,
1125                                  nic_count, disk_sizes, spindle_use,
1126                                  _compute_fn=_ComputeMinMaxSpec):
1127   """Verifies ipolicy against provided specs.
1128
1129   @type ipolicy: dict
1130   @param ipolicy: The ipolicy
1131   @type mem_size: int
1132   @param mem_size: The memory size
1133   @type cpu_count: int
1134   @param cpu_count: Used cpu cores
1135   @type disk_count: int
1136   @param disk_count: Number of disks used
1137   @type nic_count: int
1138   @param nic_count: Number of nics used
1139   @type disk_sizes: list of ints
1140   @param disk_sizes: Disk sizes of used disk (len must match C{disk_count})
1141   @type spindle_use: int
1142   @param spindle_use: The number of spindles this instance uses
1143   @param _compute_fn: The compute function (unittest only)
1144   @return: A list of violations, or an empty list of no violations are found
1145
1146   """
1147   assert disk_count == len(disk_sizes)
1148
1149   test_settings = [
1150     (constants.ISPEC_MEM_SIZE, mem_size),
1151     (constants.ISPEC_CPU_COUNT, cpu_count),
1152     (constants.ISPEC_DISK_COUNT, disk_count),
1153     (constants.ISPEC_NIC_COUNT, nic_count),
1154     (constants.ISPEC_SPINDLE_USE, spindle_use),
1155     ] + map((lambda d: (constants.ISPEC_DISK_SIZE, d)), disk_sizes)
1156
1157   return filter(None,
1158                 (_compute_fn(name, ipolicy, value)
1159                  for (name, value) in test_settings))
1160
1161
1162 def _ComputeIPolicyInstanceViolation(ipolicy, instance,
1163                                      _compute_fn=_ComputeIPolicySpecViolation):
1164   """Compute if instance meets the specs of ipolicy.
1165
1166   @type ipolicy: dict
1167   @param ipolicy: The ipolicy to verify against
1168   @type instance: L{objects.Instance}
1169   @param instance: The instance to verify
1170   @param _compute_fn: The function to verify ipolicy (unittest only)
1171   @see: L{_ComputeIPolicySpecViolation}
1172
1173   """
1174   mem_size = instance.beparams.get(constants.BE_MAXMEM, None)
1175   cpu_count = instance.beparams.get(constants.BE_VCPUS, None)
1176   spindle_use = instance.beparams.get(constants.BE_SPINDLE_USE, None)
1177   disk_count = len(instance.disks)
1178   disk_sizes = [disk.size for disk in instance.disks]
1179   nic_count = len(instance.nics)
1180
1181   return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
1182                      disk_sizes, spindle_use)
1183
1184
1185 def _ComputeIPolicyInstanceSpecViolation(ipolicy, instance_spec,
1186     _compute_fn=_ComputeIPolicySpecViolation):
1187   """Compute if instance specs meets the specs of ipolicy.
1188
1189   @type ipolicy: dict
1190   @param ipolicy: The ipolicy to verify against
1191   @param instance_spec: dict
1192   @param instance_spec: The instance spec to verify
1193   @param _compute_fn: The function to verify ipolicy (unittest only)
1194   @see: L{_ComputeIPolicySpecViolation}
1195
1196   """
1197   mem_size = instance_spec.get(constants.ISPEC_MEM_SIZE, None)
1198   cpu_count = instance_spec.get(constants.ISPEC_CPU_COUNT, None)
1199   disk_count = instance_spec.get(constants.ISPEC_DISK_COUNT, 0)
1200   disk_sizes = instance_spec.get(constants.ISPEC_DISK_SIZE, [])
1201   nic_count = instance_spec.get(constants.ISPEC_NIC_COUNT, 0)
1202   spindle_use = instance_spec.get(constants.ISPEC_SPINDLE_USE, None)
1203
1204   return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
1205                      disk_sizes, spindle_use)
1206
1207
1208 def _ComputeIPolicyNodeViolation(ipolicy, instance, current_group,
1209                                  target_group,
1210                                  _compute_fn=_ComputeIPolicyInstanceViolation):
1211   """Compute if instance meets the specs of the new target group.
1212
1213   @param ipolicy: The ipolicy to verify
1214   @param instance: The instance object to verify
1215   @param current_group: The current group of the instance
1216   @param target_group: The new group of the instance
1217   @param _compute_fn: The function to verify ipolicy (unittest only)
1218   @see: L{_ComputeIPolicySpecViolation}
1219
1220   """
1221   if current_group == target_group:
1222     return []
1223   else:
1224     return _compute_fn(ipolicy, instance)
1225
1226
1227 def _CheckTargetNodeIPolicy(lu, ipolicy, instance, node, ignore=False,
1228                             _compute_fn=_ComputeIPolicyNodeViolation):
1229   """Checks that the target node is correct in terms of instance policy.
1230
1231   @param ipolicy: The ipolicy to verify
1232   @param instance: The instance object to verify
1233   @param node: The new node to relocate
1234   @param ignore: Ignore violations of the ipolicy
1235   @param _compute_fn: The function to verify ipolicy (unittest only)
1236   @see: L{_ComputeIPolicySpecViolation}
1237
1238   """
1239   primary_node = lu.cfg.GetNodeInfo(instance.primary_node)
1240   res = _compute_fn(ipolicy, instance, primary_node.group, node.group)
1241
1242   if res:
1243     msg = ("Instance does not meet target node group's (%s) instance"
1244            " policy: %s") % (node.group, utils.CommaJoin(res))
1245     if ignore:
1246       lu.LogWarning(msg)
1247     else:
1248       raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
1249
1250
1251 def _ComputeNewInstanceViolations(old_ipolicy, new_ipolicy, instances):
1252   """Computes a set of any instances that would violate the new ipolicy.
1253
1254   @param old_ipolicy: The current (still in-place) ipolicy
1255   @param new_ipolicy: The new (to become) ipolicy
1256   @param instances: List of instances to verify
1257   @return: A list of instances which violates the new ipolicy but did not before
1258
1259   """
1260   return (_ComputeViolatingInstances(old_ipolicy, instances) -
1261           _ComputeViolatingInstances(new_ipolicy, instances))
1262
1263
1264 def _ExpandItemName(fn, name, kind):
1265   """Expand an item name.
1266
1267   @param fn: the function to use for expansion
1268   @param name: requested item name
1269   @param kind: text description ('Node' or 'Instance')
1270   @return: the resolved (full) name
1271   @raise errors.OpPrereqError: if the item is not found
1272
1273   """
1274   full_name = fn(name)
1275   if full_name is None:
1276     raise errors.OpPrereqError("%s '%s' not known" % (kind, name),
1277                                errors.ECODE_NOENT)
1278   return full_name
1279
1280
1281 def _ExpandNodeName(cfg, name):
1282   """Wrapper over L{_ExpandItemName} for nodes."""
1283   return _ExpandItemName(cfg.ExpandNodeName, name, "Node")
1284
1285
1286 def _ExpandInstanceName(cfg, name):
1287   """Wrapper over L{_ExpandItemName} for instance."""
1288   return _ExpandItemName(cfg.ExpandInstanceName, name, "Instance")
1289
1290
1291 def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
1292                           minmem, maxmem, vcpus, nics, disk_template, disks,
1293                           bep, hvp, hypervisor_name, tags):
1294   """Builds instance related env variables for hooks
1295
1296   This builds the hook environment from individual variables.
1297
1298   @type name: string
1299   @param name: the name of the instance
1300   @type primary_node: string
1301   @param primary_node: the name of the instance's primary node
1302   @type secondary_nodes: list
1303   @param secondary_nodes: list of secondary nodes as strings
1304   @type os_type: string
1305   @param os_type: the name of the instance's OS
1306   @type status: string
1307   @param status: the desired status of the instance
1308   @type minmem: string
1309   @param minmem: the minimum memory size of the instance
1310   @type maxmem: string
1311   @param maxmem: the maximum memory size of the instance
1312   @type vcpus: string
1313   @param vcpus: the count of VCPUs the instance has
1314   @type nics: list
1315   @param nics: list of tuples (ip, mac, mode, link) representing
1316       the NICs the instance has
1317   @type disk_template: string
1318   @param disk_template: the disk template of the instance
1319   @type disks: list
1320   @param disks: the list of (size, mode) pairs
1321   @type bep: dict
1322   @param bep: the backend parameters for the instance
1323   @type hvp: dict
1324   @param hvp: the hypervisor parameters for the instance
1325   @type hypervisor_name: string
1326   @param hypervisor_name: the hypervisor for the instance
1327   @type tags: list
1328   @param tags: list of instance tags as strings
1329   @rtype: dict
1330   @return: the hook environment for this instance
1331
1332   """
1333   env = {
1334     "OP_TARGET": name,
1335     "INSTANCE_NAME": name,
1336     "INSTANCE_PRIMARY": primary_node,
1337     "INSTANCE_SECONDARIES": " ".join(secondary_nodes),
1338     "INSTANCE_OS_TYPE": os_type,
1339     "INSTANCE_STATUS": status,
1340     "INSTANCE_MINMEM": minmem,
1341     "INSTANCE_MAXMEM": maxmem,
1342     # TODO(2.7) remove deprecated "memory" value
1343     "INSTANCE_MEMORY": maxmem,
1344     "INSTANCE_VCPUS": vcpus,
1345     "INSTANCE_DISK_TEMPLATE": disk_template,
1346     "INSTANCE_HYPERVISOR": hypervisor_name,
1347   }
1348   if nics:
1349     nic_count = len(nics)
1350     for idx, (ip, mac, mode, link) in enumerate(nics):
1351       if ip is None:
1352         ip = ""
1353       env["INSTANCE_NIC%d_IP" % idx] = ip
1354       env["INSTANCE_NIC%d_MAC" % idx] = mac
1355       env["INSTANCE_NIC%d_MODE" % idx] = mode
1356       env["INSTANCE_NIC%d_LINK" % idx] = link
1357       if mode == constants.NIC_MODE_BRIDGED:
1358         env["INSTANCE_NIC%d_BRIDGE" % idx] = link
1359   else:
1360     nic_count = 0
1361
1362   env["INSTANCE_NIC_COUNT"] = nic_count
1363
1364   if disks:
1365     disk_count = len(disks)
1366     for idx, (size, mode) in enumerate(disks):
1367       env["INSTANCE_DISK%d_SIZE" % idx] = size
1368       env["INSTANCE_DISK%d_MODE" % idx] = mode
1369   else:
1370     disk_count = 0
1371
1372   env["INSTANCE_DISK_COUNT"] = disk_count
1373
1374   if not tags:
1375     tags = []
1376
1377   env["INSTANCE_TAGS"] = " ".join(tags)
1378
1379   for source, kind in [(bep, "BE"), (hvp, "HV")]:
1380     for key, value in source.items():
1381       env["INSTANCE_%s_%s" % (kind, key)] = value
1382
1383   return env
1384
1385
1386 def _NICListToTuple(lu, nics):
1387   """Build a list of nic information tuples.
1388
1389   This list is suitable to be passed to _BuildInstanceHookEnv or as a return
1390   value in LUInstanceQueryData.
1391
1392   @type lu:  L{LogicalUnit}
1393   @param lu: the logical unit on whose behalf we execute
1394   @type nics: list of L{objects.NIC}
1395   @param nics: list of nics to convert to hooks tuples
1396
1397   """
1398   hooks_nics = []
1399   cluster = lu.cfg.GetClusterInfo()
1400   for nic in nics:
1401     ip = nic.ip
1402     mac = nic.mac
1403     filled_params = cluster.SimpleFillNIC(nic.nicparams)
1404     mode = filled_params[constants.NIC_MODE]
1405     link = filled_params[constants.NIC_LINK]
1406     hooks_nics.append((ip, mac, mode, link))
1407   return hooks_nics
1408
1409
1410 def _BuildInstanceHookEnvByObject(lu, instance, override=None):
1411   """Builds instance related env variables for hooks from an object.
1412
1413   @type lu: L{LogicalUnit}
1414   @param lu: the logical unit on whose behalf we execute
1415   @type instance: L{objects.Instance}
1416   @param instance: the instance for which we should build the
1417       environment
1418   @type override: dict
1419   @param override: dictionary with key/values that will override
1420       our values
1421   @rtype: dict
1422   @return: the hook environment dictionary
1423
1424   """
1425   cluster = lu.cfg.GetClusterInfo()
1426   bep = cluster.FillBE(instance)
1427   hvp = cluster.FillHV(instance)
1428   args = {
1429     "name": instance.name,
1430     "primary_node": instance.primary_node,
1431     "secondary_nodes": instance.secondary_nodes,
1432     "os_type": instance.os,
1433     "status": instance.admin_state,
1434     "maxmem": bep[constants.BE_MAXMEM],
1435     "minmem": bep[constants.BE_MINMEM],
1436     "vcpus": bep[constants.BE_VCPUS],
1437     "nics": _NICListToTuple(lu, instance.nics),
1438     "disk_template": instance.disk_template,
1439     "disks": [(disk.size, disk.mode) for disk in instance.disks],
1440     "bep": bep,
1441     "hvp": hvp,
1442     "hypervisor_name": instance.hypervisor,
1443     "tags": instance.tags,
1444   }
1445   if override:
1446     args.update(override)
1447   return _BuildInstanceHookEnv(**args) # pylint: disable=W0142
1448
1449
1450 def _AdjustCandidatePool(lu, exceptions):
1451   """Adjust the candidate pool after node operations.
1452
1453   """
1454   mod_list = lu.cfg.MaintainCandidatePool(exceptions)
1455   if mod_list:
1456     lu.LogInfo("Promoted nodes to master candidate role: %s",
1457                utils.CommaJoin(node.name for node in mod_list))
1458     for name in mod_list:
1459       lu.context.ReaddNode(name)
1460   mc_now, mc_max, _ = lu.cfg.GetMasterCandidateStats(exceptions)
1461   if mc_now > mc_max:
1462     lu.LogInfo("Note: more nodes are candidates (%d) than desired (%d)" %
1463                (mc_now, mc_max))
1464
1465
1466 def _DecideSelfPromotion(lu, exceptions=None):
1467   """Decide whether I should promote myself as a master candidate.
1468
1469   """
1470   cp_size = lu.cfg.GetClusterInfo().candidate_pool_size
1471   mc_now, mc_should, _ = lu.cfg.GetMasterCandidateStats(exceptions)
1472   # the new node will increase mc_max with one, so:
1473   mc_should = min(mc_should + 1, cp_size)
1474   return mc_now < mc_should
1475
1476
1477 def _CalculateGroupIPolicy(cluster, group):
1478   """Calculate instance policy for group.
1479
1480   """
1481   return cluster.SimpleFillIPolicy(group.ipolicy)
1482
1483
1484 def _ComputeViolatingInstances(ipolicy, instances):
1485   """Computes a set of instances who violates given ipolicy.
1486
1487   @param ipolicy: The ipolicy to verify
1488   @type instances: object.Instance
1489   @param instances: List of instances to verify
1490   @return: A frozenset of instance names violating the ipolicy
1491
1492   """
1493   return frozenset([inst.name for inst in instances
1494                     if _ComputeIPolicyInstanceViolation(ipolicy, inst)])
1495
1496
1497 def _CheckNicsBridgesExist(lu, target_nics, target_node):
1498   """Check that the brigdes needed by a list of nics exist.
1499
1500   """
1501   cluster = lu.cfg.GetClusterInfo()
1502   paramslist = [cluster.SimpleFillNIC(nic.nicparams) for nic in target_nics]
1503   brlist = [params[constants.NIC_LINK] for params in paramslist
1504             if params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED]
1505   if brlist:
1506     result = lu.rpc.call_bridges_exist(target_node, brlist)
1507     result.Raise("Error checking bridges on destination node '%s'" %
1508                  target_node, prereq=True, ecode=errors.ECODE_ENVIRON)
1509
1510
1511 def _CheckInstanceBridgesExist(lu, instance, node=None):
1512   """Check that the brigdes needed by an instance exist.
1513
1514   """
1515   if node is None:
1516     node = instance.primary_node
1517   _CheckNicsBridgesExist(lu, instance.nics, node)
1518
1519
1520 def _CheckOSVariant(os_obj, name):
1521   """Check whether an OS name conforms to the os variants specification.
1522
1523   @type os_obj: L{objects.OS}
1524   @param os_obj: OS object to check
1525   @type name: string
1526   @param name: OS name passed by the user, to check for validity
1527
1528   """
1529   variant = objects.OS.GetVariant(name)
1530   if not os_obj.supported_variants:
1531     if variant:
1532       raise errors.OpPrereqError("OS '%s' doesn't support variants ('%s'"
1533                                  " passed)" % (os_obj.name, variant),
1534                                  errors.ECODE_INVAL)
1535     return
1536   if not variant:
1537     raise errors.OpPrereqError("OS name must include a variant",
1538                                errors.ECODE_INVAL)
1539
1540   if variant not in os_obj.supported_variants:
1541     raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
1542
1543
1544 def _GetNodeInstancesInner(cfg, fn):
1545   return [i for i in cfg.GetAllInstancesInfo().values() if fn(i)]
1546
1547
1548 def _GetNodeInstances(cfg, node_name):
1549   """Returns a list of all primary and secondary instances on a node.
1550
1551   """
1552
1553   return _GetNodeInstancesInner(cfg, lambda inst: node_name in inst.all_nodes)
1554
1555
1556 def _GetNodePrimaryInstances(cfg, node_name):
1557   """Returns primary instances on a node.
1558
1559   """
1560   return _GetNodeInstancesInner(cfg,
1561                                 lambda inst: node_name == inst.primary_node)
1562
1563
1564 def _GetNodeSecondaryInstances(cfg, node_name):
1565   """Returns secondary instances on a node.
1566
1567   """
1568   return _GetNodeInstancesInner(cfg,
1569                                 lambda inst: node_name in inst.secondary_nodes)
1570
1571
1572 def _GetStorageTypeArgs(cfg, storage_type):
1573   """Returns the arguments for a storage type.
1574
1575   """
1576   # Special case for file storage
1577   if storage_type == constants.ST_FILE:
1578     # storage.FileStorage wants a list of storage directories
1579     return [[cfg.GetFileStorageDir(), cfg.GetSharedFileStorageDir()]]
1580
1581   return []
1582
1583
1584 def _FindFaultyInstanceDisks(cfg, rpc_runner, instance, node_name, prereq):
1585   faulty = []
1586
1587   for dev in instance.disks:
1588     cfg.SetDiskID(dev, node_name)
1589
1590   result = rpc_runner.call_blockdev_getmirrorstatus(node_name, instance.disks)
1591   result.Raise("Failed to get disk status from node %s" % node_name,
1592                prereq=prereq, ecode=errors.ECODE_ENVIRON)
1593
1594   for idx, bdev_status in enumerate(result.payload):
1595     if bdev_status and bdev_status.ldisk_status == constants.LDS_FAULTY:
1596       faulty.append(idx)
1597
1598   return faulty
1599
1600
1601 def _CheckIAllocatorOrNode(lu, iallocator_slot, node_slot):
1602   """Check the sanity of iallocator and node arguments and use the
1603   cluster-wide iallocator if appropriate.
1604
1605   Check that at most one of (iallocator, node) is specified. If none is
1606   specified, then the LU's opcode's iallocator slot is filled with the
1607   cluster-wide default iallocator.
1608
1609   @type iallocator_slot: string
1610   @param iallocator_slot: the name of the opcode iallocator slot
1611   @type node_slot: string
1612   @param node_slot: the name of the opcode target node slot
1613
1614   """
1615   node = getattr(lu.op, node_slot, None)
1616   iallocator = getattr(lu.op, iallocator_slot, None)
1617
1618   if node is not None and iallocator is not None:
1619     raise errors.OpPrereqError("Do not specify both, iallocator and node",
1620                                errors.ECODE_INVAL)
1621   elif node is None and iallocator is None:
1622     default_iallocator = lu.cfg.GetDefaultIAllocator()
1623     if default_iallocator:
1624       setattr(lu.op, iallocator_slot, default_iallocator)
1625     else:
1626       raise errors.OpPrereqError("No iallocator or node given and no"
1627                                  " cluster-wide default iallocator found;"
1628                                  " please specify either an iallocator or a"
1629                                  " node, or set a cluster-wide default"
1630                                  " iallocator")
1631
1632
1633 def _GetDefaultIAllocator(cfg, iallocator):
1634   """Decides on which iallocator to use.
1635
1636   @type cfg: L{config.ConfigWriter}
1637   @param cfg: Cluster configuration object
1638   @type iallocator: string or None
1639   @param iallocator: Iallocator specified in opcode
1640   @rtype: string
1641   @return: Iallocator name
1642
1643   """
1644   if not iallocator:
1645     # Use default iallocator
1646     iallocator = cfg.GetDefaultIAllocator()
1647
1648   if not iallocator:
1649     raise errors.OpPrereqError("No iallocator was specified, neither in the"
1650                                " opcode nor as a cluster-wide default",
1651                                errors.ECODE_INVAL)
1652
1653   return iallocator
1654
1655
1656 class LUClusterPostInit(LogicalUnit):
1657   """Logical unit for running hooks after cluster initialization.
1658
1659   """
1660   HPATH = "cluster-init"
1661   HTYPE = constants.HTYPE_CLUSTER
1662
1663   def BuildHooksEnv(self):
1664     """Build hooks env.
1665
1666     """
1667     return {
1668       "OP_TARGET": self.cfg.GetClusterName(),
1669       }
1670
1671   def BuildHooksNodes(self):
1672     """Build hooks nodes.
1673
1674     """
1675     return ([], [self.cfg.GetMasterNode()])
1676
1677   def Exec(self, feedback_fn):
1678     """Nothing to do.
1679
1680     """
1681     return True
1682
1683
1684 class LUClusterDestroy(LogicalUnit):
1685   """Logical unit for destroying the cluster.
1686
1687   """
1688   HPATH = "cluster-destroy"
1689   HTYPE = constants.HTYPE_CLUSTER
1690
1691   def BuildHooksEnv(self):
1692     """Build hooks env.
1693
1694     """
1695     return {
1696       "OP_TARGET": self.cfg.GetClusterName(),
1697       }
1698
1699   def BuildHooksNodes(self):
1700     """Build hooks nodes.
1701
1702     """
1703     return ([], [])
1704
1705   def CheckPrereq(self):
1706     """Check prerequisites.
1707
1708     This checks whether the cluster is empty.
1709
1710     Any errors are signaled by raising errors.OpPrereqError.
1711
1712     """
1713     master = self.cfg.GetMasterNode()
1714
1715     nodelist = self.cfg.GetNodeList()
1716     if len(nodelist) != 1 or nodelist[0] != master:
1717       raise errors.OpPrereqError("There are still %d node(s) in"
1718                                  " this cluster." % (len(nodelist) - 1),
1719                                  errors.ECODE_INVAL)
1720     instancelist = self.cfg.GetInstanceList()
1721     if instancelist:
1722       raise errors.OpPrereqError("There are still %d instance(s) in"
1723                                  " this cluster." % len(instancelist),
1724                                  errors.ECODE_INVAL)
1725
1726   def Exec(self, feedback_fn):
1727     """Destroys the cluster.
1728
1729     """
1730     master_params = self.cfg.GetMasterNetworkParameters()
1731
1732     # Run post hooks on master node before it's removed
1733     _RunPostHook(self, master_params.name)
1734
1735     ems = self.cfg.GetUseExternalMipScript()
1736     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
1737                                                      master_params, ems)
1738     if result.fail_msg:
1739       self.LogWarning("Error disabling the master IP address: %s",
1740                       result.fail_msg)
1741
1742     return master_params.name
1743
1744
1745 def _VerifyCertificate(filename):
1746   """Verifies a certificate for L{LUClusterVerifyConfig}.
1747
1748   @type filename: string
1749   @param filename: Path to PEM file
1750
1751   """
1752   try:
1753     cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
1754                                            utils.ReadFile(filename))
1755   except Exception, err: # pylint: disable=W0703
1756     return (LUClusterVerifyConfig.ETYPE_ERROR,
1757             "Failed to load X509 certificate %s: %s" % (filename, err))
1758
1759   (errcode, msg) = \
1760     utils.VerifyX509Certificate(cert, constants.SSL_CERT_EXPIRATION_WARN,
1761                                 constants.SSL_CERT_EXPIRATION_ERROR)
1762
1763   if msg:
1764     fnamemsg = "While verifying %s: %s" % (filename, msg)
1765   else:
1766     fnamemsg = None
1767
1768   if errcode is None:
1769     return (None, fnamemsg)
1770   elif errcode == utils.CERT_WARNING:
1771     return (LUClusterVerifyConfig.ETYPE_WARNING, fnamemsg)
1772   elif errcode == utils.CERT_ERROR:
1773     return (LUClusterVerifyConfig.ETYPE_ERROR, fnamemsg)
1774
1775   raise errors.ProgrammerError("Unhandled certificate error code %r" % errcode)
1776
1777
1778 def _GetAllHypervisorParameters(cluster, instances):
1779   """Compute the set of all hypervisor parameters.
1780
1781   @type cluster: L{objects.Cluster}
1782   @param cluster: the cluster object
1783   @param instances: list of L{objects.Instance}
1784   @param instances: additional instances from which to obtain parameters
1785   @rtype: list of (origin, hypervisor, parameters)
1786   @return: a list with all parameters found, indicating the hypervisor they
1787        apply to, and the origin (can be "cluster", "os X", or "instance Y")
1788
1789   """
1790   hvp_data = []
1791
1792   for hv_name in cluster.enabled_hypervisors:
1793     hvp_data.append(("cluster", hv_name, cluster.GetHVDefaults(hv_name)))
1794
1795   for os_name, os_hvp in cluster.os_hvp.items():
1796     for hv_name, hv_params in os_hvp.items():
1797       if hv_params:
1798         full_params = cluster.GetHVDefaults(hv_name, os_name=os_name)
1799         hvp_data.append(("os %s" % os_name, hv_name, full_params))
1800
1801   # TODO: collapse identical parameter values in a single one
1802   for instance in instances:
1803     if instance.hvparams:
1804       hvp_data.append(("instance %s" % instance.name, instance.hypervisor,
1805                        cluster.FillHV(instance)))
1806
1807   return hvp_data
1808
1809
1810 class _VerifyErrors(object):
1811   """Mix-in for cluster/group verify LUs.
1812
1813   It provides _Error and _ErrorIf, and updates the self.bad boolean. (Expects
1814   self.op and self._feedback_fn to be available.)
1815
1816   """
1817
1818   ETYPE_FIELD = "code"
1819   ETYPE_ERROR = "ERROR"
1820   ETYPE_WARNING = "WARNING"
1821
1822   def _Error(self, ecode, item, msg, *args, **kwargs):
1823     """Format an error message.
1824
1825     Based on the opcode's error_codes parameter, either format a
1826     parseable error code, or a simpler error string.
1827
1828     This must be called only from Exec and functions called from Exec.
1829
1830     """
1831     ltype = kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR)
1832     itype, etxt, _ = ecode
1833     # first complete the msg
1834     if args:
1835       msg = msg % args
1836     # then format the whole message
1837     if self.op.error_codes: # This is a mix-in. pylint: disable=E1101
1838       msg = "%s:%s:%s:%s:%s" % (ltype, etxt, itype, item, msg)
1839     else:
1840       if item:
1841         item = " " + item
1842       else:
1843         item = ""
1844       msg = "%s: %s%s: %s" % (ltype, itype, item, msg)
1845     # and finally report it via the feedback_fn
1846     self._feedback_fn("  - %s" % msg) # Mix-in. pylint: disable=E1101
1847
1848   def _ErrorIf(self, cond, ecode, *args, **kwargs):
1849     """Log an error message if the passed condition is True.
1850
1851     """
1852     cond = (bool(cond)
1853             or self.op.debug_simulate_errors) # pylint: disable=E1101
1854
1855     # If the error code is in the list of ignored errors, demote the error to a
1856     # warning
1857     (_, etxt, _) = ecode
1858     if etxt in self.op.ignore_errors:     # pylint: disable=E1101
1859       kwargs[self.ETYPE_FIELD] = self.ETYPE_WARNING
1860
1861     if cond:
1862       self._Error(ecode, *args, **kwargs)
1863
1864     # do not mark the operation as failed for WARN cases only
1865     if kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR) == self.ETYPE_ERROR:
1866       self.bad = self.bad or cond
1867
1868
1869 class LUClusterVerify(NoHooksLU):
1870   """Submits all jobs necessary to verify the cluster.
1871
1872   """
1873   REQ_BGL = False
1874
1875   def ExpandNames(self):
1876     self.needed_locks = {}
1877
1878   def Exec(self, feedback_fn):
1879     jobs = []
1880
1881     if self.op.group_name:
1882       groups = [self.op.group_name]
1883       depends_fn = lambda: None
1884     else:
1885       groups = self.cfg.GetNodeGroupList()
1886
1887       # Verify global configuration
1888       jobs.append([
1889         opcodes.OpClusterVerifyConfig(ignore_errors=self.op.ignore_errors)
1890         ])
1891
1892       # Always depend on global verification
1893       depends_fn = lambda: [(-len(jobs), [])]
1894
1895     jobs.extend([opcodes.OpClusterVerifyGroup(group_name=group,
1896                                             ignore_errors=self.op.ignore_errors,
1897                                             depends=depends_fn())]
1898                 for group in groups)
1899
1900     # Fix up all parameters
1901     for op in itertools.chain(*jobs): # pylint: disable=W0142
1902       op.debug_simulate_errors = self.op.debug_simulate_errors
1903       op.verbose = self.op.verbose
1904       op.error_codes = self.op.error_codes
1905       try:
1906         op.skip_checks = self.op.skip_checks
1907       except AttributeError:
1908         assert not isinstance(op, opcodes.OpClusterVerifyGroup)
1909
1910     return ResultWithJobs(jobs)
1911
1912
1913 class LUClusterVerifyConfig(NoHooksLU, _VerifyErrors):
1914   """Verifies the cluster config.
1915
1916   """
1917   REQ_BGL = False
1918
1919   def _VerifyHVP(self, hvp_data):
1920     """Verifies locally the syntax of the hypervisor parameters.
1921
1922     """
1923     for item, hv_name, hv_params in hvp_data:
1924       msg = ("hypervisor %s parameters syntax check (source %s): %%s" %
1925              (item, hv_name))
1926       try:
1927         hv_class = hypervisor.GetHypervisor(hv_name)
1928         utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
1929         hv_class.CheckParameterSyntax(hv_params)
1930       except errors.GenericError, err:
1931         self._ErrorIf(True, constants.CV_ECLUSTERCFG, None, msg % str(err))
1932
1933   def ExpandNames(self):
1934     self.needed_locks = dict.fromkeys(locking.LEVELS, locking.ALL_SET)
1935     self.share_locks = _ShareAll()
1936
1937   def CheckPrereq(self):
1938     """Check prerequisites.
1939
1940     """
1941     # Retrieve all information
1942     self.all_group_info = self.cfg.GetAllNodeGroupsInfo()
1943     self.all_node_info = self.cfg.GetAllNodesInfo()
1944     self.all_inst_info = self.cfg.GetAllInstancesInfo()
1945
1946   def Exec(self, feedback_fn):
1947     """Verify integrity of cluster, performing various test on nodes.
1948
1949     """
1950     self.bad = False
1951     self._feedback_fn = feedback_fn
1952
1953     feedback_fn("* Verifying cluster config")
1954
1955     for msg in self.cfg.VerifyConfig():
1956       self._ErrorIf(True, constants.CV_ECLUSTERCFG, None, msg)
1957
1958     feedback_fn("* Verifying cluster certificate files")
1959
1960     for cert_filename in constants.ALL_CERT_FILES:
1961       (errcode, msg) = _VerifyCertificate(cert_filename)
1962       self._ErrorIf(errcode, constants.CV_ECLUSTERCERT, None, msg, code=errcode)
1963
1964     feedback_fn("* Verifying hypervisor parameters")
1965
1966     self._VerifyHVP(_GetAllHypervisorParameters(self.cfg.GetClusterInfo(),
1967                                                 self.all_inst_info.values()))
1968
1969     feedback_fn("* Verifying all nodes belong to an existing group")
1970
1971     # We do this verification here because, should this bogus circumstance
1972     # occur, it would never be caught by VerifyGroup, which only acts on
1973     # nodes/instances reachable from existing node groups.
1974
1975     dangling_nodes = set(node.name for node in self.all_node_info.values()
1976                          if node.group not in self.all_group_info)
1977
1978     dangling_instances = {}
1979     no_node_instances = []
1980
1981     for inst in self.all_inst_info.values():
1982       if inst.primary_node in dangling_nodes:
1983         dangling_instances.setdefault(inst.primary_node, []).append(inst.name)
1984       elif inst.primary_node not in self.all_node_info:
1985         no_node_instances.append(inst.name)
1986
1987     pretty_dangling = [
1988         "%s (%s)" %
1989         (node.name,
1990          utils.CommaJoin(dangling_instances.get(node.name,
1991                                                 ["no instances"])))
1992         for node in dangling_nodes]
1993
1994     self._ErrorIf(bool(dangling_nodes), constants.CV_ECLUSTERDANGLINGNODES,
1995                   None,
1996                   "the following nodes (and their instances) belong to a non"
1997                   " existing group: %s", utils.CommaJoin(pretty_dangling))
1998
1999     self._ErrorIf(bool(no_node_instances), constants.CV_ECLUSTERDANGLINGINST,
2000                   None,
2001                   "the following instances have a non-existing primary-node:"
2002                   " %s", utils.CommaJoin(no_node_instances))
2003
2004     return not self.bad
2005
2006
2007 class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
2008   """Verifies the status of a node group.
2009
2010   """
2011   HPATH = "cluster-verify"
2012   HTYPE = constants.HTYPE_CLUSTER
2013   REQ_BGL = False
2014
2015   _HOOKS_INDENT_RE = re.compile("^", re.M)
2016
2017   class NodeImage(object):
2018     """A class representing the logical and physical status of a node.
2019
2020     @type name: string
2021     @ivar name: the node name to which this object refers
2022     @ivar volumes: a structure as returned from
2023         L{ganeti.backend.GetVolumeList} (runtime)
2024     @ivar instances: a list of running instances (runtime)
2025     @ivar pinst: list of configured primary instances (config)
2026     @ivar sinst: list of configured secondary instances (config)
2027     @ivar sbp: dictionary of {primary-node: list of instances} for all
2028         instances for which this node is secondary (config)
2029     @ivar mfree: free memory, as reported by hypervisor (runtime)
2030     @ivar dfree: free disk, as reported by the node (runtime)
2031     @ivar offline: the offline status (config)
2032     @type rpc_fail: boolean
2033     @ivar rpc_fail: whether the RPC verify call was successfull (overall,
2034         not whether the individual keys were correct) (runtime)
2035     @type lvm_fail: boolean
2036     @ivar lvm_fail: whether the RPC call didn't return valid LVM data
2037     @type hyp_fail: boolean
2038     @ivar hyp_fail: whether the RPC call didn't return the instance list
2039     @type ghost: boolean
2040     @ivar ghost: whether this is a known node or not (config)
2041     @type os_fail: boolean
2042     @ivar os_fail: whether the RPC call didn't return valid OS data
2043     @type oslist: list
2044     @ivar oslist: list of OSes as diagnosed by DiagnoseOS
2045     @type vm_capable: boolean
2046     @ivar vm_capable: whether the node can host instances
2047
2048     """
2049     def __init__(self, offline=False, name=None, vm_capable=True):
2050       self.name = name
2051       self.volumes = {}
2052       self.instances = []
2053       self.pinst = []
2054       self.sinst = []
2055       self.sbp = {}
2056       self.mfree = 0
2057       self.dfree = 0
2058       self.offline = offline
2059       self.vm_capable = vm_capable
2060       self.rpc_fail = False
2061       self.lvm_fail = False
2062       self.hyp_fail = False
2063       self.ghost = False
2064       self.os_fail = False
2065       self.oslist = {}
2066
2067   def ExpandNames(self):
2068     # This raises errors.OpPrereqError on its own:
2069     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
2070
2071     # Get instances in node group; this is unsafe and needs verification later
2072     inst_names = \
2073       self.cfg.GetNodeGroupInstances(self.group_uuid, primary_only=True)
2074
2075     self.needed_locks = {
2076       locking.LEVEL_INSTANCE: inst_names,
2077       locking.LEVEL_NODEGROUP: [self.group_uuid],
2078       locking.LEVEL_NODE: [],
2079       }
2080
2081     self.share_locks = _ShareAll()
2082
2083   def DeclareLocks(self, level):
2084     if level == locking.LEVEL_NODE:
2085       # Get members of node group; this is unsafe and needs verification later
2086       nodes = set(self.cfg.GetNodeGroup(self.group_uuid).members)
2087
2088       all_inst_info = self.cfg.GetAllInstancesInfo()
2089
2090       # In Exec(), we warn about mirrored instances that have primary and
2091       # secondary living in separate node groups. To fully verify that
2092       # volumes for these instances are healthy, we will need to do an
2093       # extra call to their secondaries. We ensure here those nodes will
2094       # be locked.
2095       for inst in self.owned_locks(locking.LEVEL_INSTANCE):
2096         # Important: access only the instances whose lock is owned
2097         if all_inst_info[inst].disk_template in constants.DTS_INT_MIRROR:
2098           nodes.update(all_inst_info[inst].secondary_nodes)
2099
2100       self.needed_locks[locking.LEVEL_NODE] = nodes
2101
2102   def CheckPrereq(self):
2103     assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
2104     self.group_info = self.cfg.GetNodeGroup(self.group_uuid)
2105
2106     group_nodes = set(self.group_info.members)
2107     group_instances = \
2108       self.cfg.GetNodeGroupInstances(self.group_uuid, primary_only=True)
2109
2110     unlocked_nodes = \
2111         group_nodes.difference(self.owned_locks(locking.LEVEL_NODE))
2112
2113     unlocked_instances = \
2114         group_instances.difference(self.owned_locks(locking.LEVEL_INSTANCE))
2115
2116     if unlocked_nodes:
2117       raise errors.OpPrereqError("Missing lock for nodes: %s" %
2118                                  utils.CommaJoin(unlocked_nodes),
2119                                  errors.ECODE_STATE)
2120
2121     if unlocked_instances:
2122       raise errors.OpPrereqError("Missing lock for instances: %s" %
2123                                  utils.CommaJoin(unlocked_instances),
2124                                  errors.ECODE_STATE)
2125
2126     self.all_node_info = self.cfg.GetAllNodesInfo()
2127     self.all_inst_info = self.cfg.GetAllInstancesInfo()
2128
2129     self.my_node_names = utils.NiceSort(group_nodes)
2130     self.my_inst_names = utils.NiceSort(group_instances)
2131
2132     self.my_node_info = dict((name, self.all_node_info[name])
2133                              for name in self.my_node_names)
2134
2135     self.my_inst_info = dict((name, self.all_inst_info[name])
2136                              for name in self.my_inst_names)
2137
2138     # We detect here the nodes that will need the extra RPC calls for verifying
2139     # split LV volumes; they should be locked.
2140     extra_lv_nodes = set()
2141
2142     for inst in self.my_inst_info.values():
2143       if inst.disk_template in constants.DTS_INT_MIRROR:
2144         for nname in inst.all_nodes:
2145           if self.all_node_info[nname].group != self.group_uuid:
2146             extra_lv_nodes.add(nname)
2147
2148     unlocked_lv_nodes = \
2149         extra_lv_nodes.difference(self.owned_locks(locking.LEVEL_NODE))
2150
2151     if unlocked_lv_nodes:
2152       raise errors.OpPrereqError("Missing node locks for LV check: %s" %
2153                                  utils.CommaJoin(unlocked_lv_nodes),
2154                                  errors.ECODE_STATE)
2155     self.extra_lv_nodes = list(extra_lv_nodes)
2156
2157   def _VerifyNode(self, ninfo, nresult):
2158     """Perform some basic validation on data returned from a node.
2159
2160       - check the result data structure is well formed and has all the
2161         mandatory fields
2162       - check ganeti version
2163
2164     @type ninfo: L{objects.Node}
2165     @param ninfo: the node to check
2166     @param nresult: the results from the node
2167     @rtype: boolean
2168     @return: whether overall this call was successful (and we can expect
2169          reasonable values in the respose)
2170
2171     """
2172     node = ninfo.name
2173     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2174
2175     # main result, nresult should be a non-empty dict
2176     test = not nresult or not isinstance(nresult, dict)
2177     _ErrorIf(test, constants.CV_ENODERPC, node,
2178                   "unable to verify node: no data returned")
2179     if test:
2180       return False
2181
2182     # compares ganeti version
2183     local_version = constants.PROTOCOL_VERSION
2184     remote_version = nresult.get("version", None)
2185     test = not (remote_version and
2186                 isinstance(remote_version, (list, tuple)) and
2187                 len(remote_version) == 2)
2188     _ErrorIf(test, constants.CV_ENODERPC, node,
2189              "connection to node returned invalid data")
2190     if test:
2191       return False
2192
2193     test = local_version != remote_version[0]
2194     _ErrorIf(test, constants.CV_ENODEVERSION, node,
2195              "incompatible protocol versions: master %s,"
2196              " node %s", local_version, remote_version[0])
2197     if test:
2198       return False
2199
2200     # node seems compatible, we can actually try to look into its results
2201
2202     # full package version
2203     self._ErrorIf(constants.RELEASE_VERSION != remote_version[1],
2204                   constants.CV_ENODEVERSION, node,
2205                   "software version mismatch: master %s, node %s",
2206                   constants.RELEASE_VERSION, remote_version[1],
2207                   code=self.ETYPE_WARNING)
2208
2209     hyp_result = nresult.get(constants.NV_HYPERVISOR, None)
2210     if ninfo.vm_capable and isinstance(hyp_result, dict):
2211       for hv_name, hv_result in hyp_result.iteritems():
2212         test = hv_result is not None
2213         _ErrorIf(test, constants.CV_ENODEHV, node,
2214                  "hypervisor %s verify failure: '%s'", hv_name, hv_result)
2215
2216     hvp_result = nresult.get(constants.NV_HVPARAMS, None)
2217     if ninfo.vm_capable and isinstance(hvp_result, list):
2218       for item, hv_name, hv_result in hvp_result:
2219         _ErrorIf(True, constants.CV_ENODEHV, node,
2220                  "hypervisor %s parameter verify failure (source %s): %s",
2221                  hv_name, item, hv_result)
2222
2223     test = nresult.get(constants.NV_NODESETUP,
2224                        ["Missing NODESETUP results"])
2225     _ErrorIf(test, constants.CV_ENODESETUP, node, "node setup error: %s",
2226              "; ".join(test))
2227
2228     return True
2229
2230   def _VerifyNodeTime(self, ninfo, nresult,
2231                       nvinfo_starttime, nvinfo_endtime):
2232     """Check the node time.
2233
2234     @type ninfo: L{objects.Node}
2235     @param ninfo: the node to check
2236     @param nresult: the remote results for the node
2237     @param nvinfo_starttime: the start time of the RPC call
2238     @param nvinfo_endtime: the end time of the RPC call
2239
2240     """
2241     node = ninfo.name
2242     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2243
2244     ntime = nresult.get(constants.NV_TIME, None)
2245     try:
2246       ntime_merged = utils.MergeTime(ntime)
2247     except (ValueError, TypeError):
2248       _ErrorIf(True, constants.CV_ENODETIME, node, "Node returned invalid time")
2249       return
2250
2251     if ntime_merged < (nvinfo_starttime - constants.NODE_MAX_CLOCK_SKEW):
2252       ntime_diff = "%.01fs" % abs(nvinfo_starttime - ntime_merged)
2253     elif ntime_merged > (nvinfo_endtime + constants.NODE_MAX_CLOCK_SKEW):
2254       ntime_diff = "%.01fs" % abs(ntime_merged - nvinfo_endtime)
2255     else:
2256       ntime_diff = None
2257
2258     _ErrorIf(ntime_diff is not None, constants.CV_ENODETIME, node,
2259              "Node time diverges by at least %s from master node time",
2260              ntime_diff)
2261
2262   def _VerifyNodeLVM(self, ninfo, nresult, vg_name):
2263     """Check the node LVM results.
2264
2265     @type ninfo: L{objects.Node}
2266     @param ninfo: the node to check
2267     @param nresult: the remote results for the node
2268     @param vg_name: the configured VG name
2269
2270     """
2271     if vg_name is None:
2272       return
2273
2274     node = ninfo.name
2275     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2276
2277     # checks vg existence and size > 20G
2278     vglist = nresult.get(constants.NV_VGLIST, None)
2279     test = not vglist
2280     _ErrorIf(test, constants.CV_ENODELVM, node, "unable to check volume groups")
2281     if not test:
2282       vgstatus = utils.CheckVolumeGroupSize(vglist, vg_name,
2283                                             constants.MIN_VG_SIZE)
2284       _ErrorIf(vgstatus, constants.CV_ENODELVM, node, vgstatus)
2285
2286     # check pv names
2287     pvlist = nresult.get(constants.NV_PVLIST, None)
2288     test = pvlist is None
2289     _ErrorIf(test, constants.CV_ENODELVM, node, "Can't get PV list from node")
2290     if not test:
2291       # check that ':' is not present in PV names, since it's a
2292       # special character for lvcreate (denotes the range of PEs to
2293       # use on the PV)
2294       for _, pvname, owner_vg in pvlist:
2295         test = ":" in pvname
2296         _ErrorIf(test, constants.CV_ENODELVM, node,
2297                  "Invalid character ':' in PV '%s' of VG '%s'",
2298                  pvname, owner_vg)
2299
2300   def _VerifyNodeBridges(self, ninfo, nresult, bridges):
2301     """Check the node bridges.
2302
2303     @type ninfo: L{objects.Node}
2304     @param ninfo: the node to check
2305     @param nresult: the remote results for the node
2306     @param bridges: the expected list of bridges
2307
2308     """
2309     if not bridges:
2310       return
2311
2312     node = ninfo.name
2313     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2314
2315     missing = nresult.get(constants.NV_BRIDGES, None)
2316     test = not isinstance(missing, list)
2317     _ErrorIf(test, constants.CV_ENODENET, node,
2318              "did not return valid bridge information")
2319     if not test:
2320       _ErrorIf(bool(missing), constants.CV_ENODENET, node,
2321                "missing bridges: %s" % utils.CommaJoin(sorted(missing)))
2322
2323   def _VerifyNodeUserScripts(self, ninfo, nresult):
2324     """Check the results of user scripts presence and executability on the node
2325
2326     @type ninfo: L{objects.Node}
2327     @param ninfo: the node to check
2328     @param nresult: the remote results for the node
2329
2330     """
2331     node = ninfo.name
2332
2333     test = not constants.NV_USERSCRIPTS in nresult
2334     self._ErrorIf(test, constants.CV_ENODEUSERSCRIPTS, node,
2335                   "did not return user scripts information")
2336
2337     broken_scripts = nresult.get(constants.NV_USERSCRIPTS, None)
2338     if not test:
2339       self._ErrorIf(broken_scripts, constants.CV_ENODEUSERSCRIPTS, node,
2340                     "user scripts not present or not executable: %s" %
2341                     utils.CommaJoin(sorted(broken_scripts)))
2342
2343   def _VerifyNodeNetwork(self, ninfo, nresult):
2344     """Check the node network connectivity results.
2345
2346     @type ninfo: L{objects.Node}
2347     @param ninfo: the node to check
2348     @param nresult: the remote results for the node
2349
2350     """
2351     node = ninfo.name
2352     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2353
2354     test = constants.NV_NODELIST not in nresult
2355     _ErrorIf(test, constants.CV_ENODESSH, node,
2356              "node hasn't returned node ssh connectivity data")
2357     if not test:
2358       if nresult[constants.NV_NODELIST]:
2359         for a_node, a_msg in nresult[constants.NV_NODELIST].items():
2360           _ErrorIf(True, constants.CV_ENODESSH, node,
2361                    "ssh communication with node '%s': %s", a_node, a_msg)
2362
2363     test = constants.NV_NODENETTEST not in nresult
2364     _ErrorIf(test, constants.CV_ENODENET, node,
2365              "node hasn't returned node tcp connectivity data")
2366     if not test:
2367       if nresult[constants.NV_NODENETTEST]:
2368         nlist = utils.NiceSort(nresult[constants.NV_NODENETTEST].keys())
2369         for anode in nlist:
2370           _ErrorIf(True, constants.CV_ENODENET, node,
2371                    "tcp communication with node '%s': %s",
2372                    anode, nresult[constants.NV_NODENETTEST][anode])
2373
2374     test = constants.NV_MASTERIP not in nresult
2375     _ErrorIf(test, constants.CV_ENODENET, node,
2376              "node hasn't returned node master IP reachability data")
2377     if not test:
2378       if not nresult[constants.NV_MASTERIP]:
2379         if node == self.master_node:
2380           msg = "the master node cannot reach the master IP (not configured?)"
2381         else:
2382           msg = "cannot reach the master IP"
2383         _ErrorIf(True, constants.CV_ENODENET, node, msg)
2384
2385   def _VerifyInstance(self, instance, instanceconfig, node_image,
2386                       diskstatus):
2387     """Verify an instance.
2388
2389     This function checks to see if the required block devices are
2390     available on the instance's node.
2391
2392     """
2393     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2394     node_current = instanceconfig.primary_node
2395
2396     node_vol_should = {}
2397     instanceconfig.MapLVsByNode(node_vol_should)
2398
2399     ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(), self.group_info)
2400     err = _ComputeIPolicyInstanceViolation(ipolicy, instanceconfig)
2401     _ErrorIf(err, constants.CV_EINSTANCEPOLICY, instance, err)
2402
2403     for node in node_vol_should:
2404       n_img = node_image[node]
2405       if n_img.offline or n_img.rpc_fail or n_img.lvm_fail:
2406         # ignore missing volumes on offline or broken nodes
2407         continue
2408       for volume in node_vol_should[node]:
2409         test = volume not in n_img.volumes
2410         _ErrorIf(test, constants.CV_EINSTANCEMISSINGDISK, instance,
2411                  "volume %s missing on node %s", volume, node)
2412
2413     if instanceconfig.admin_state == constants.ADMINST_UP:
2414       pri_img = node_image[node_current]
2415       test = instance not in pri_img.instances and not pri_img.offline
2416       _ErrorIf(test, constants.CV_EINSTANCEDOWN, instance,
2417                "instance not running on its primary node %s",
2418                node_current)
2419
2420     diskdata = [(nname, success, status, idx)
2421                 for (nname, disks) in diskstatus.items()
2422                 for idx, (success, status) in enumerate(disks)]
2423
2424     for nname, success, bdev_status, idx in diskdata:
2425       # the 'ghost node' construction in Exec() ensures that we have a
2426       # node here
2427       snode = node_image[nname]
2428       bad_snode = snode.ghost or snode.offline
2429       _ErrorIf(instanceconfig.admin_state == constants.ADMINST_UP and
2430                not success and not bad_snode,
2431                constants.CV_EINSTANCEFAULTYDISK, instance,
2432                "couldn't retrieve status for disk/%s on %s: %s",
2433                idx, nname, bdev_status)
2434       _ErrorIf((instanceconfig.admin_state == constants.ADMINST_UP and
2435                 success and bdev_status.ldisk_status == constants.LDS_FAULTY),
2436                constants.CV_EINSTANCEFAULTYDISK, instance,
2437                "disk/%s on %s is faulty", idx, nname)
2438
2439   def _VerifyOrphanVolumes(self, node_vol_should, node_image, reserved):
2440     """Verify if there are any unknown volumes in the cluster.
2441
2442     The .os, .swap and backup volumes are ignored. All other volumes are
2443     reported as unknown.
2444
2445     @type reserved: L{ganeti.utils.FieldSet}
2446     @param reserved: a FieldSet of reserved volume names
2447
2448     """
2449     for node, n_img in node_image.items():
2450       if (n_img.offline or n_img.rpc_fail or n_img.lvm_fail or
2451           self.all_node_info[node].group != self.group_uuid):
2452         # skip non-healthy nodes
2453         continue
2454       for volume in n_img.volumes:
2455         test = ((node not in node_vol_should or
2456                 volume not in node_vol_should[node]) and
2457                 not reserved.Matches(volume))
2458         self._ErrorIf(test, constants.CV_ENODEORPHANLV, node,
2459                       "volume %s is unknown", volume)
2460
2461   def _VerifyNPlusOneMemory(self, node_image, instance_cfg):
2462     """Verify N+1 Memory Resilience.
2463
2464     Check that if one single node dies we can still start all the
2465     instances it was primary for.
2466
2467     """
2468     cluster_info = self.cfg.GetClusterInfo()
2469     for node, n_img in node_image.items():
2470       # This code checks that every node which is now listed as
2471       # secondary has enough memory to host all instances it is
2472       # supposed to should a single other node in the cluster fail.
2473       # FIXME: not ready for failover to an arbitrary node
2474       # FIXME: does not support file-backed instances
2475       # WARNING: we currently take into account down instances as well
2476       # as up ones, considering that even if they're down someone
2477       # might want to start them even in the event of a node failure.
2478       if n_img.offline or self.all_node_info[node].group != self.group_uuid:
2479         # we're skipping nodes marked offline and nodes in other groups from
2480         # the N+1 warning, since most likely we don't have good memory
2481         # infromation from them; we already list instances living on such
2482         # nodes, and that's enough warning
2483         continue
2484       #TODO(dynmem): also consider ballooning out other instances
2485       for prinode, instances in n_img.sbp.items():
2486         needed_mem = 0
2487         for instance in instances:
2488           bep = cluster_info.FillBE(instance_cfg[instance])
2489           if bep[constants.BE_AUTO_BALANCE]:
2490             needed_mem += bep[constants.BE_MINMEM]
2491         test = n_img.mfree < needed_mem
2492         self._ErrorIf(test, constants.CV_ENODEN1, node,
2493                       "not enough memory to accomodate instance failovers"
2494                       " should node %s fail (%dMiB needed, %dMiB available)",
2495                       prinode, needed_mem, n_img.mfree)
2496
2497   @classmethod
2498   def _VerifyFiles(cls, errorif, nodeinfo, master_node, all_nvinfo,
2499                    (files_all, files_opt, files_mc, files_vm)):
2500     """Verifies file checksums collected from all nodes.
2501
2502     @param errorif: Callback for reporting errors
2503     @param nodeinfo: List of L{objects.Node} objects
2504     @param master_node: Name of master node
2505     @param all_nvinfo: RPC results
2506
2507     """
2508     # Define functions determining which nodes to consider for a file
2509     files2nodefn = [
2510       (files_all, None),
2511       (files_mc, lambda node: (node.master_candidate or
2512                                node.name == master_node)),
2513       (files_vm, lambda node: node.vm_capable),
2514       ]
2515
2516     # Build mapping from filename to list of nodes which should have the file
2517     nodefiles = {}
2518     for (files, fn) in files2nodefn:
2519       if fn is None:
2520         filenodes = nodeinfo
2521       else:
2522         filenodes = filter(fn, nodeinfo)
2523       nodefiles.update((filename,
2524                         frozenset(map(operator.attrgetter("name"), filenodes)))
2525                        for filename in files)
2526
2527     assert set(nodefiles) == (files_all | files_mc | files_vm)
2528
2529     fileinfo = dict((filename, {}) for filename in nodefiles)
2530     ignore_nodes = set()
2531
2532     for node in nodeinfo:
2533       if node.offline:
2534         ignore_nodes.add(node.name)
2535         continue
2536
2537       nresult = all_nvinfo[node.name]
2538
2539       if nresult.fail_msg or not nresult.payload:
2540         node_files = None
2541       else:
2542         node_files = nresult.payload.get(constants.NV_FILELIST, None)
2543
2544       test = not (node_files and isinstance(node_files, dict))
2545       errorif(test, constants.CV_ENODEFILECHECK, node.name,
2546               "Node did not return file checksum data")
2547       if test:
2548         ignore_nodes.add(node.name)
2549         continue
2550
2551       # Build per-checksum mapping from filename to nodes having it
2552       for (filename, checksum) in node_files.items():
2553         assert filename in nodefiles
2554         fileinfo[filename].setdefault(checksum, set()).add(node.name)
2555
2556     for (filename, checksums) in fileinfo.items():
2557       assert compat.all(len(i) > 10 for i in checksums), "Invalid checksum"
2558
2559       # Nodes having the file
2560       with_file = frozenset(node_name
2561                             for nodes in fileinfo[filename].values()
2562                             for node_name in nodes) - ignore_nodes
2563
2564       expected_nodes = nodefiles[filename] - ignore_nodes
2565
2566       # Nodes missing file
2567       missing_file = expected_nodes - with_file
2568
2569       if filename in files_opt:
2570         # All or no nodes
2571         errorif(missing_file and missing_file != expected_nodes,
2572                 constants.CV_ECLUSTERFILECHECK, None,
2573                 "File %s is optional, but it must exist on all or no"
2574                 " nodes (not found on %s)",
2575                 filename, utils.CommaJoin(utils.NiceSort(missing_file)))
2576       else:
2577         errorif(missing_file, constants.CV_ECLUSTERFILECHECK, None,
2578                 "File %s is missing from node(s) %s", filename,
2579                 utils.CommaJoin(utils.NiceSort(missing_file)))
2580
2581         # Warn if a node has a file it shouldn't
2582         unexpected = with_file - expected_nodes
2583         errorif(unexpected,
2584                 constants.CV_ECLUSTERFILECHECK, None,
2585                 "File %s should not exist on node(s) %s",
2586                 filename, utils.CommaJoin(utils.NiceSort(unexpected)))
2587
2588       # See if there are multiple versions of the file
2589       test = len(checksums) > 1
2590       if test:
2591         variants = ["variant %s on %s" %
2592                     (idx + 1, utils.CommaJoin(utils.NiceSort(nodes)))
2593                     for (idx, (checksum, nodes)) in
2594                       enumerate(sorted(checksums.items()))]
2595       else:
2596         variants = []
2597
2598       errorif(test, constants.CV_ECLUSTERFILECHECK, None,
2599               "File %s found with %s different checksums (%s)",
2600               filename, len(checksums), "; ".join(variants))
2601
2602   def _VerifyNodeDrbd(self, ninfo, nresult, instanceinfo, drbd_helper,
2603                       drbd_map):
2604     """Verifies and the node DRBD status.
2605
2606     @type ninfo: L{objects.Node}
2607     @param ninfo: the node to check
2608     @param nresult: the remote results for the node
2609     @param instanceinfo: the dict of instances
2610     @param drbd_helper: the configured DRBD usermode helper
2611     @param drbd_map: the DRBD map as returned by
2612         L{ganeti.config.ConfigWriter.ComputeDRBDMap}
2613
2614     """
2615     node = ninfo.name
2616     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2617
2618     if drbd_helper:
2619       helper_result = nresult.get(constants.NV_DRBDHELPER, None)
2620       test = (helper_result == None)
2621       _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2622                "no drbd usermode helper returned")
2623       if helper_result:
2624         status, payload = helper_result
2625         test = not status
2626         _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2627                  "drbd usermode helper check unsuccessful: %s", payload)
2628         test = status and (payload != drbd_helper)
2629         _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2630                  "wrong drbd usermode helper: %s", payload)
2631
2632     # compute the DRBD minors
2633     node_drbd = {}
2634     for minor, instance in drbd_map[node].items():
2635       test = instance not in instanceinfo
2636       _ErrorIf(test, constants.CV_ECLUSTERCFG, None,
2637                "ghost instance '%s' in temporary DRBD map", instance)
2638         # ghost instance should not be running, but otherwise we
2639         # don't give double warnings (both ghost instance and
2640         # unallocated minor in use)
2641       if test:
2642         node_drbd[minor] = (instance, False)
2643       else:
2644         instance = instanceinfo[instance]
2645         node_drbd[minor] = (instance.name,
2646                             instance.admin_state == constants.ADMINST_UP)
2647
2648     # and now check them
2649     used_minors = nresult.get(constants.NV_DRBDLIST, [])
2650     test = not isinstance(used_minors, (tuple, list))
2651     _ErrorIf(test, constants.CV_ENODEDRBD, node,
2652              "cannot parse drbd status file: %s", str(used_minors))
2653     if test:
2654       # we cannot check drbd status
2655       return
2656
2657     for minor, (iname, must_exist) in node_drbd.items():
2658       test = minor not in used_minors and must_exist
2659       _ErrorIf(test, constants.CV_ENODEDRBD, node,
2660                "drbd minor %d of instance %s is not active", minor, iname)
2661     for minor in used_minors:
2662       test = minor not in node_drbd
2663       _ErrorIf(test, constants.CV_ENODEDRBD, node,
2664                "unallocated drbd minor %d is in use", minor)
2665
2666   def _UpdateNodeOS(self, ninfo, nresult, nimg):
2667     """Builds the node OS structures.
2668
2669     @type ninfo: L{objects.Node}
2670     @param ninfo: the node to check
2671     @param nresult: the remote results for the node
2672     @param nimg: the node image object
2673
2674     """
2675     node = ninfo.name
2676     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2677
2678     remote_os = nresult.get(constants.NV_OSLIST, None)
2679     test = (not isinstance(remote_os, list) or
2680             not compat.all(isinstance(v, list) and len(v) == 7
2681                            for v in remote_os))
2682
2683     _ErrorIf(test, constants.CV_ENODEOS, node,
2684              "node hasn't returned valid OS data")
2685
2686     nimg.os_fail = test
2687
2688     if test:
2689       return
2690
2691     os_dict = {}
2692
2693     for (name, os_path, status, diagnose,
2694          variants, parameters, api_ver) in nresult[constants.NV_OSLIST]:
2695
2696       if name not in os_dict:
2697         os_dict[name] = []
2698
2699       # parameters is a list of lists instead of list of tuples due to
2700       # JSON lacking a real tuple type, fix it:
2701       parameters = [tuple(v) for v in parameters]
2702       os_dict[name].append((os_path, status, diagnose,
2703                             set(variants), set(parameters), set(api_ver)))
2704
2705     nimg.oslist = os_dict
2706
2707   def _VerifyNodeOS(self, ninfo, nimg, base):
2708     """Verifies the node OS list.
2709
2710     @type ninfo: L{objects.Node}
2711     @param ninfo: the node to check
2712     @param nimg: the node image object
2713     @param base: the 'template' node we match against (e.g. from the master)
2714
2715     """
2716     node = ninfo.name
2717     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2718
2719     assert not nimg.os_fail, "Entered _VerifyNodeOS with failed OS rpc?"
2720
2721     beautify_params = lambda l: ["%s: %s" % (k, v) for (k, v) in l]
2722     for os_name, os_data in nimg.oslist.items():
2723       assert os_data, "Empty OS status for OS %s?!" % os_name
2724       f_path, f_status, f_diag, f_var, f_param, f_api = os_data[0]
2725       _ErrorIf(not f_status, constants.CV_ENODEOS, node,
2726                "Invalid OS %s (located at %s): %s", os_name, f_path, f_diag)
2727       _ErrorIf(len(os_data) > 1, constants.CV_ENODEOS, node,
2728                "OS '%s' has multiple entries (first one shadows the rest): %s",
2729                os_name, utils.CommaJoin([v[0] for v in os_data]))
2730       # comparisons with the 'base' image
2731       test = os_name not in base.oslist
2732       _ErrorIf(test, constants.CV_ENODEOS, node,
2733                "Extra OS %s not present on reference node (%s)",
2734                os_name, base.name)
2735       if test:
2736         continue
2737       assert base.oslist[os_name], "Base node has empty OS status?"
2738       _, b_status, _, b_var, b_param, b_api = base.oslist[os_name][0]
2739       if not b_status:
2740         # base OS is invalid, skipping
2741         continue
2742       for kind, a, b in [("API version", f_api, b_api),
2743                          ("variants list", f_var, b_var),
2744                          ("parameters", beautify_params(f_param),
2745                           beautify_params(b_param))]:
2746         _ErrorIf(a != b, constants.CV_ENODEOS, node,
2747                  "OS %s for %s differs from reference node %s: [%s] vs. [%s]",
2748                  kind, os_name, base.name,
2749                  utils.CommaJoin(sorted(a)), utils.CommaJoin(sorted(b)))
2750
2751     # check any missing OSes
2752     missing = set(base.oslist.keys()).difference(nimg.oslist.keys())
2753     _ErrorIf(missing, constants.CV_ENODEOS, node,
2754              "OSes present on reference node %s but missing on this node: %s",
2755              base.name, utils.CommaJoin(missing))
2756
2757   def _VerifyOob(self, ninfo, nresult):
2758     """Verifies out of band functionality of a node.
2759
2760     @type ninfo: L{objects.Node}
2761     @param ninfo: the node to check
2762     @param nresult: the remote results for the node
2763
2764     """
2765     node = ninfo.name
2766     # We just have to verify the paths on master and/or master candidates
2767     # as the oob helper is invoked on the master
2768     if ((ninfo.master_candidate or ninfo.master_capable) and
2769         constants.NV_OOB_PATHS in nresult):
2770       for path_result in nresult[constants.NV_OOB_PATHS]:
2771         self._ErrorIf(path_result, constants.CV_ENODEOOBPATH, node, path_result)
2772
2773   def _UpdateNodeVolumes(self, ninfo, nresult, nimg, vg_name):
2774     """Verifies and updates the node volume data.
2775
2776     This function will update a L{NodeImage}'s internal structures
2777     with data from the remote call.
2778
2779     @type ninfo: L{objects.Node}
2780     @param ninfo: the node to check
2781     @param nresult: the remote results for the node
2782     @param nimg: the node image object
2783     @param vg_name: the configured VG name
2784
2785     """
2786     node = ninfo.name
2787     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2788
2789     nimg.lvm_fail = True
2790     lvdata = nresult.get(constants.NV_LVLIST, "Missing LV data")
2791     if vg_name is None:
2792       pass
2793     elif isinstance(lvdata, basestring):
2794       _ErrorIf(True, constants.CV_ENODELVM, node, "LVM problem on node: %s",
2795                utils.SafeEncode(lvdata))
2796     elif not isinstance(lvdata, dict):
2797       _ErrorIf(True, constants.CV_ENODELVM, node,
2798                "rpc call to node failed (lvlist)")
2799     else:
2800       nimg.volumes = lvdata
2801       nimg.lvm_fail = False
2802
2803   def _UpdateNodeInstances(self, ninfo, nresult, nimg):
2804     """Verifies and updates the node instance list.
2805
2806     If the listing was successful, then updates this node's instance
2807     list. Otherwise, it marks the RPC call as failed for the instance
2808     list key.
2809
2810     @type ninfo: L{objects.Node}
2811     @param ninfo: the node to check
2812     @param nresult: the remote results for the node
2813     @param nimg: the node image object
2814
2815     """
2816     idata = nresult.get(constants.NV_INSTANCELIST, None)
2817     test = not isinstance(idata, list)
2818     self._ErrorIf(test, constants.CV_ENODEHV, ninfo.name,
2819                   "rpc call to node failed (instancelist): %s",
2820                   utils.SafeEncode(str(idata)))
2821     if test:
2822       nimg.hyp_fail = True
2823     else:
2824       nimg.instances = idata
2825
2826   def _UpdateNodeInfo(self, ninfo, nresult, nimg, vg_name):
2827     """Verifies and computes a node information map
2828
2829     @type ninfo: L{objects.Node}
2830     @param ninfo: the node to check
2831     @param nresult: the remote results for the node
2832     @param nimg: the node image object
2833     @param vg_name: the configured VG name
2834
2835     """
2836     node = ninfo.name
2837     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2838
2839     # try to read free memory (from the hypervisor)
2840     hv_info = nresult.get(constants.NV_HVINFO, None)
2841     test = not isinstance(hv_info, dict) or "memory_free" not in hv_info
2842     _ErrorIf(test, constants.CV_ENODEHV, node,
2843              "rpc call to node failed (hvinfo)")
2844     if not test:
2845       try:
2846         nimg.mfree = int(hv_info["memory_free"])
2847       except (ValueError, TypeError):
2848         _ErrorIf(True, constants.CV_ENODERPC, node,
2849                  "node returned invalid nodeinfo, check hypervisor")
2850
2851     # FIXME: devise a free space model for file based instances as well
2852     if vg_name is not None:
2853       test = (constants.NV_VGLIST not in nresult or
2854               vg_name not in nresult[constants.NV_VGLIST])
2855       _ErrorIf(test, constants.CV_ENODELVM, node,
2856                "node didn't return data for the volume group '%s'"
2857                " - it is either missing or broken", vg_name)
2858       if not test:
2859         try:
2860           nimg.dfree = int(nresult[constants.NV_VGLIST][vg_name])
2861         except (ValueError, TypeError):
2862           _ErrorIf(True, constants.CV_ENODERPC, node,
2863                    "node returned invalid LVM info, check LVM status")
2864
2865   def _CollectDiskInfo(self, nodelist, node_image, instanceinfo):
2866     """Gets per-disk status information for all instances.
2867
2868     @type nodelist: list of strings
2869     @param nodelist: Node names
2870     @type node_image: dict of (name, L{objects.Node})
2871     @param node_image: Node objects
2872     @type instanceinfo: dict of (name, L{objects.Instance})
2873     @param instanceinfo: Instance objects
2874     @rtype: {instance: {node: [(succes, payload)]}}
2875     @return: a dictionary of per-instance dictionaries with nodes as
2876         keys and disk information as values; the disk information is a
2877         list of tuples (success, payload)
2878
2879     """
2880     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2881
2882     node_disks = {}
2883     node_disks_devonly = {}
2884     diskless_instances = set()
2885     diskless = constants.DT_DISKLESS
2886
2887     for nname in nodelist:
2888       node_instances = list(itertools.chain(node_image[nname].pinst,
2889                                             node_image[nname].sinst))
2890       diskless_instances.update(inst for inst in node_instances
2891                                 if instanceinfo[inst].disk_template == diskless)
2892       disks = [(inst, disk)
2893                for inst in node_instances
2894                for disk in instanceinfo[inst].disks]
2895
2896       if not disks:
2897         # No need to collect data
2898         continue
2899
2900       node_disks[nname] = disks
2901
2902       # Creating copies as SetDiskID below will modify the objects and that can
2903       # lead to incorrect data returned from nodes
2904       devonly = [dev.Copy() for (_, dev) in disks]
2905
2906       for dev in devonly:
2907         self.cfg.SetDiskID(dev, nname)
2908
2909       node_disks_devonly[nname] = devonly
2910
2911     assert len(node_disks) == len(node_disks_devonly)
2912
2913     # Collect data from all nodes with disks
2914     result = self.rpc.call_blockdev_getmirrorstatus_multi(node_disks.keys(),
2915                                                           node_disks_devonly)
2916
2917     assert len(result) == len(node_disks)
2918
2919     instdisk = {}
2920
2921     for (nname, nres) in result.items():
2922       disks = node_disks[nname]
2923
2924       if nres.offline:
2925         # No data from this node
2926         data = len(disks) * [(False, "node offline")]
2927       else:
2928         msg = nres.fail_msg
2929         _ErrorIf(msg, constants.CV_ENODERPC, nname,
2930                  "while getting disk information: %s", msg)
2931         if msg:
2932           # No data from this node
2933           data = len(disks) * [(False, msg)]
2934         else:
2935           data = []
2936           for idx, i in enumerate(nres.payload):
2937             if isinstance(i, (tuple, list)) and len(i) == 2:
2938               data.append(i)
2939             else:
2940               logging.warning("Invalid result from node %s, entry %d: %s",
2941                               nname, idx, i)
2942               data.append((False, "Invalid result from the remote node"))
2943
2944       for ((inst, _), status) in zip(disks, data):
2945         instdisk.setdefault(inst, {}).setdefault(nname, []).append(status)
2946
2947     # Add empty entries for diskless instances.
2948     for inst in diskless_instances:
2949       assert inst not in instdisk
2950       instdisk[inst] = {}
2951
2952     assert compat.all(len(statuses) == len(instanceinfo[inst].disks) and
2953                       len(nnames) <= len(instanceinfo[inst].all_nodes) and
2954                       compat.all(isinstance(s, (tuple, list)) and
2955                                  len(s) == 2 for s in statuses)
2956                       for inst, nnames in instdisk.items()
2957                       for nname, statuses in nnames.items())
2958     assert set(instdisk) == set(instanceinfo), "instdisk consistency failure"
2959
2960     return instdisk
2961
2962   @staticmethod
2963   def _SshNodeSelector(group_uuid, all_nodes):
2964     """Create endless iterators for all potential SSH check hosts.
2965
2966     """
2967     nodes = [node for node in all_nodes
2968              if (node.group != group_uuid and
2969                  not node.offline)]
2970     keyfunc = operator.attrgetter("group")
2971
2972     return map(itertools.cycle,
2973                [sorted(map(operator.attrgetter("name"), names))
2974                 for _, names in itertools.groupby(sorted(nodes, key=keyfunc),
2975                                                   keyfunc)])
2976
2977   @classmethod
2978   def _SelectSshCheckNodes(cls, group_nodes, group_uuid, all_nodes):
2979     """Choose which nodes should talk to which other nodes.
2980
2981     We will make nodes contact all nodes in their group, and one node from
2982     every other group.
2983
2984     @warning: This algorithm has a known issue if one node group is much
2985       smaller than others (e.g. just one node). In such a case all other
2986       nodes will talk to the single node.
2987
2988     """
2989     online_nodes = sorted(node.name for node in group_nodes if not node.offline)
2990     sel = cls._SshNodeSelector(group_uuid, all_nodes)
2991
2992     return (online_nodes,
2993             dict((name, sorted([i.next() for i in sel]))
2994                  for name in online_nodes))
2995
2996   def BuildHooksEnv(self):
2997     """Build hooks env.
2998
2999     Cluster-Verify hooks just ran in the post phase and their failure makes
3000     the output be logged in the verify output and the verification to fail.
3001
3002     """
3003     env = {
3004       "CLUSTER_TAGS": " ".join(self.cfg.GetClusterInfo().GetTags())
3005       }
3006
3007     env.update(("NODE_TAGS_%s" % node.name, " ".join(node.GetTags()))
3008                for node in self.my_node_info.values())
3009
3010     return env
3011
3012   def BuildHooksNodes(self):
3013     """Build hooks nodes.
3014
3015     """
3016     return ([], self.my_node_names)
3017
3018   def Exec(self, feedback_fn):
3019     """Verify integrity of the node group, performing various test on nodes.
3020
3021     """
3022     # This method has too many local variables. pylint: disable=R0914
3023     feedback_fn("* Verifying group '%s'" % self.group_info.name)
3024
3025     if not self.my_node_names:
3026       # empty node group
3027       feedback_fn("* Empty node group, skipping verification")
3028       return True
3029
3030     self.bad = False
3031     _ErrorIf = self._ErrorIf # pylint: disable=C0103
3032     verbose = self.op.verbose
3033     self._feedback_fn = feedback_fn
3034
3035     vg_name = self.cfg.GetVGName()
3036     drbd_helper = self.cfg.GetDRBDHelper()
3037     cluster = self.cfg.GetClusterInfo()
3038     groupinfo = self.cfg.GetAllNodeGroupsInfo()
3039     hypervisors = cluster.enabled_hypervisors
3040     node_data_list = [self.my_node_info[name] for name in self.my_node_names]
3041
3042     i_non_redundant = [] # Non redundant instances
3043     i_non_a_balanced = [] # Non auto-balanced instances
3044     i_offline = 0 # Count of offline instances
3045     n_offline = 0 # Count of offline nodes
3046     n_drained = 0 # Count of nodes being drained
3047     node_vol_should = {}
3048
3049     # FIXME: verify OS list
3050
3051     # File verification
3052     filemap = _ComputeAncillaryFiles(cluster, False)
3053
3054     # do local checksums
3055     master_node = self.master_node = self.cfg.GetMasterNode()
3056     master_ip = self.cfg.GetMasterIP()
3057
3058     feedback_fn("* Gathering data (%d nodes)" % len(self.my_node_names))
3059
3060     user_scripts = []
3061     if self.cfg.GetUseExternalMipScript():
3062       user_scripts.append(constants.EXTERNAL_MASTER_SETUP_SCRIPT)
3063
3064     node_verify_param = {
3065       constants.NV_FILELIST:
3066         utils.UniqueSequence(filename
3067                              for files in filemap
3068                              for filename in files),
3069       constants.NV_NODELIST:
3070         self._SelectSshCheckNodes(node_data_list, self.group_uuid,
3071                                   self.all_node_info.values()),
3072       constants.NV_HYPERVISOR: hypervisors,
3073       constants.NV_HVPARAMS:
3074         _GetAllHypervisorParameters(cluster, self.all_inst_info.values()),
3075       constants.NV_NODENETTEST: [(node.name, node.primary_ip, node.secondary_ip)
3076                                  for node in node_data_list
3077                                  if not node.offline],
3078       constants.NV_INSTANCELIST: hypervisors,
3079       constants.NV_VERSION: None,
3080       constants.NV_HVINFO: self.cfg.GetHypervisorType(),
3081       constants.NV_NODESETUP: None,
3082       constants.NV_TIME: None,
3083       constants.NV_MASTERIP: (master_node, master_ip),
3084       constants.NV_OSLIST: None,
3085       constants.NV_VMNODES: self.cfg.GetNonVmCapableNodeList(),
3086       constants.NV_USERSCRIPTS: user_scripts,
3087       }
3088
3089     if vg_name is not None:
3090       node_verify_param[constants.NV_VGLIST] = None
3091       node_verify_param[constants.NV_LVLIST] = vg_name
3092       node_verify_param[constants.NV_PVLIST] = [vg_name]
3093       node_verify_param[constants.NV_DRBDLIST] = None
3094
3095     if drbd_helper:
3096       node_verify_param[constants.NV_DRBDHELPER] = drbd_helper
3097
3098     # bridge checks
3099     # FIXME: this needs to be changed per node-group, not cluster-wide
3100     bridges = set()
3101     default_nicpp = cluster.nicparams[constants.PP_DEFAULT]
3102     if default_nicpp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
3103       bridges.add(default_nicpp[constants.NIC_LINK])
3104     for instance in self.my_inst_info.values():
3105       for nic in instance.nics:
3106         full_nic = cluster.SimpleFillNIC(nic.nicparams)
3107         if full_nic[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
3108           bridges.add(full_nic[constants.NIC_LINK])
3109
3110     if bridges:
3111       node_verify_param[constants.NV_BRIDGES] = list(bridges)
3112
3113     # Build our expected cluster state
3114     node_image = dict((node.name, self.NodeImage(offline=node.offline,
3115                                                  name=node.name,
3116                                                  vm_capable=node.vm_capable))
3117                       for node in node_data_list)
3118
3119     # Gather OOB paths
3120     oob_paths = []
3121     for node in self.all_node_info.values():
3122       path = _SupportsOob(self.cfg, node)
3123       if path and path not in oob_paths:
3124         oob_paths.append(path)
3125
3126     if oob_paths:
3127       node_verify_param[constants.NV_OOB_PATHS] = oob_paths
3128
3129     for instance in self.my_inst_names:
3130       inst_config = self.my_inst_info[instance]
3131
3132       for nname in inst_config.all_nodes:
3133         if nname not in node_image:
3134           gnode = self.NodeImage(name=nname)
3135           gnode.ghost = (nname not in self.all_node_info)
3136           node_image[nname] = gnode
3137
3138       inst_config.MapLVsByNode(node_vol_should)
3139
3140       pnode = inst_config.primary_node
3141       node_image[pnode].pinst.append(instance)
3142
3143       for snode in inst_config.secondary_nodes:
3144         nimg = node_image[snode]
3145         nimg.sinst.append(instance)
3146         if pnode not in nimg.sbp:
3147           nimg.sbp[pnode] = []
3148         nimg.sbp[pnode].append(instance)
3149
3150     # At this point, we have the in-memory data structures complete,
3151     # except for the runtime information, which we'll gather next
3152
3153     # Due to the way our RPC system works, exact response times cannot be
3154     # guaranteed (e.g. a broken node could run into a timeout). By keeping the
3155     # time before and after executing the request, we can at least have a time
3156     # window.
3157     nvinfo_starttime = time.time()
3158     all_nvinfo = self.rpc.call_node_verify(self.my_node_names,
3159                                            node_verify_param,
3160                                            self.cfg.GetClusterName())
3161     nvinfo_endtime = time.time()
3162
3163     if self.extra_lv_nodes and vg_name is not None:
3164       extra_lv_nvinfo = \
3165           self.rpc.call_node_verify(self.extra_lv_nodes,
3166                                     {constants.NV_LVLIST: vg_name},
3167                                     self.cfg.GetClusterName())
3168     else:
3169       extra_lv_nvinfo = {}
3170
3171     all_drbd_map = self.cfg.ComputeDRBDMap()
3172
3173     feedback_fn("* Gathering disk information (%s nodes)" %
3174                 len(self.my_node_names))
3175     instdisk = self._CollectDiskInfo(self.my_node_names, node_image,
3176                                      self.my_inst_info)
3177
3178     feedback_fn("* Verifying configuration file consistency")
3179
3180     # If not all nodes are being checked, we need to make sure the master node
3181     # and a non-checked vm_capable node are in the list.
3182     absent_nodes = set(self.all_node_info).difference(self.my_node_info)
3183     if absent_nodes:
3184       vf_nvinfo = all_nvinfo.copy()
3185       vf_node_info = list(self.my_node_info.values())
3186       additional_nodes = []
3187       if master_node not in self.my_node_info:
3188         additional_nodes.append(master_node)
3189         vf_node_info.append(self.all_node_info[master_node])
3190       # Add the first vm_capable node we find which is not included
3191       for node in absent_nodes:
3192         nodeinfo = self.all_node_info[node]
3193         if nodeinfo.vm_capable and not nodeinfo.offline:
3194           additional_nodes.append(node)
3195           vf_node_info.append(self.all_node_info[node])
3196           break
3197       key = constants.NV_FILELIST
3198       vf_nvinfo.update(self.rpc.call_node_verify(additional_nodes,
3199                                                  {key: node_verify_param[key]},
3200                                                  self.cfg.GetClusterName()))
3201     else:
3202       vf_nvinfo = all_nvinfo
3203       vf_node_info = self.my_node_info.values()
3204
3205     self._VerifyFiles(_ErrorIf, vf_node_info, master_node, vf_nvinfo, filemap)
3206
3207     feedback_fn("* Verifying node status")
3208
3209     refos_img = None
3210
3211     for node_i in node_data_list:
3212       node = node_i.name
3213       nimg = node_image[node]
3214
3215       if node_i.offline:
3216         if verbose:
3217           feedback_fn("* Skipping offline node %s" % (node,))
3218         n_offline += 1
3219         continue
3220
3221       if node == master_node:
3222         ntype = "master"
3223       elif node_i.master_candidate:
3224         ntype = "master candidate"
3225       elif node_i.drained:
3226         ntype = "drained"
3227         n_drained += 1
3228       else:
3229         ntype = "regular"
3230       if verbose:
3231         feedback_fn("* Verifying node %s (%s)" % (node, ntype))
3232
3233       msg = all_nvinfo[node].fail_msg
3234       _ErrorIf(msg, constants.CV_ENODERPC, node, "while contacting node: %s",
3235                msg)
3236       if msg:
3237         nimg.rpc_fail = True
3238         continue
3239
3240       nresult = all_nvinfo[node].payload
3241
3242       nimg.call_ok = self._VerifyNode(node_i, nresult)
3243       self._VerifyNodeTime(node_i, nresult, nvinfo_starttime, nvinfo_endtime)
3244       self._VerifyNodeNetwork(node_i, nresult)
3245       self._VerifyNodeUserScripts(node_i, nresult)
3246       self._VerifyOob(node_i, nresult)
3247
3248       if nimg.vm_capable:
3249         self._VerifyNodeLVM(node_i, nresult, vg_name)
3250         self._VerifyNodeDrbd(node_i, nresult, self.all_inst_info, drbd_helper,
3251                              all_drbd_map)
3252
3253         self._UpdateNodeVolumes(node_i, nresult, nimg, vg_name)
3254         self._UpdateNodeInstances(node_i, nresult, nimg)
3255         self._UpdateNodeInfo(node_i, nresult, nimg, vg_name)
3256         self._UpdateNodeOS(node_i, nresult, nimg)
3257
3258         if not nimg.os_fail:
3259           if refos_img is None:
3260             refos_img = nimg
3261           self._VerifyNodeOS(node_i, nimg, refos_img)
3262         self._VerifyNodeBridges(node_i, nresult, bridges)
3263
3264         # Check whether all running instancies are primary for the node. (This
3265         # can no longer be done from _VerifyInstance below, since some of the
3266         # wrong instances could be from other node groups.)
3267         non_primary_inst = set(nimg.instances).difference(nimg.pinst)
3268
3269         for inst in non_primary_inst:
3270           # FIXME: investigate best way to handle offline insts
3271           if inst.admin_state == constants.ADMINST_OFFLINE:
3272             if verbose:
3273               feedback_fn("* Skipping offline instance %s" % inst.name)
3274             i_offline += 1
3275             continue
3276           test = inst in self.all_inst_info
3277           _ErrorIf(test, constants.CV_EINSTANCEWRONGNODE, inst,
3278                    "instance should not run on node %s", node_i.name)
3279           _ErrorIf(not test, constants.CV_ENODEORPHANINSTANCE, node_i.name,
3280                    "node is running unknown instance %s", inst)
3281
3282     for node, result in extra_lv_nvinfo.items():
3283       self._UpdateNodeVolumes(self.all_node_info[node], result.payload,
3284                               node_image[node], vg_name)
3285
3286     feedback_fn("* Verifying instance status")
3287     for instance in self.my_inst_names:
3288       if verbose:
3289         feedback_fn("* Verifying instance %s" % instance)
3290       inst_config = self.my_inst_info[instance]
3291       self._VerifyInstance(instance, inst_config, node_image,
3292                            instdisk[instance])
3293       inst_nodes_offline = []
3294
3295       pnode = inst_config.primary_node
3296       pnode_img = node_image[pnode]
3297       _ErrorIf(pnode_img.rpc_fail and not pnode_img.offline,
3298                constants.CV_ENODERPC, pnode, "instance %s, connection to"
3299                " primary node failed", instance)
3300
3301       _ErrorIf(inst_config.admin_state == constants.ADMINST_UP and
3302                pnode_img.offline,
3303                constants.CV_EINSTANCEBADNODE, instance,
3304                "instance is marked as running and lives on offline node %s",
3305                inst_config.primary_node)
3306
3307       # If the instance is non-redundant we cannot survive losing its primary
3308       # node, so we are not N+1 compliant. On the other hand we have no disk
3309       # templates with more than one secondary so that situation is not well
3310       # supported either.
3311       # FIXME: does not support file-backed instances
3312       if not inst_config.secondary_nodes:
3313         i_non_redundant.append(instance)
3314
3315       _ErrorIf(len(inst_config.secondary_nodes) > 1,
3316                constants.CV_EINSTANCELAYOUT,
3317                instance, "instance has multiple secondary nodes: %s",
3318                utils.CommaJoin(inst_config.secondary_nodes),
3319                code=self.ETYPE_WARNING)
3320
3321       if inst_config.disk_template in constants.DTS_INT_MIRROR:
3322         pnode = inst_config.primary_node
3323         instance_nodes = utils.NiceSort(inst_config.all_nodes)
3324         instance_groups = {}
3325
3326         for node in instance_nodes:
3327           instance_groups.setdefault(self.all_node_info[node].group,
3328                                      []).append(node)
3329
3330         pretty_list = [
3331           "%s (group %s)" % (utils.CommaJoin(nodes), groupinfo[group].name)
3332           # Sort so that we always list the primary node first.
3333           for group, nodes in sorted(instance_groups.items(),
3334                                      key=lambda (_, nodes): pnode in nodes,
3335                                      reverse=True)]
3336
3337         self._ErrorIf(len(instance_groups) > 1,
3338                       constants.CV_EINSTANCESPLITGROUPS,
3339                       instance, "instance has primary and secondary nodes in"
3340                       " different groups: %s", utils.CommaJoin(pretty_list),
3341                       code=self.ETYPE_WARNING)
3342
3343       if not cluster.FillBE(inst_config)[constants.BE_AUTO_BALANCE]:
3344         i_non_a_balanced.append(instance)
3345
3346       for snode in inst_config.secondary_nodes:
3347         s_img = node_image[snode]
3348         _ErrorIf(s_img.rpc_fail and not s_img.offline, constants.CV_ENODERPC,
3349                  snode, "instance %s, connection to secondary node failed",
3350                  instance)
3351
3352         if s_img.offline:
3353           inst_nodes_offline.append(snode)
3354
3355       # warn that the instance lives on offline nodes
3356       _ErrorIf(inst_nodes_offline, constants.CV_EINSTANCEBADNODE, instance,
3357                "instance has offline secondary node(s) %s",
3358                utils.CommaJoin(inst_nodes_offline))
3359       # ... or ghost/non-vm_capable nodes
3360       for node in inst_config.all_nodes:
3361         _ErrorIf(node_image[node].ghost, constants.CV_EINSTANCEBADNODE,
3362                  instance, "instance lives on ghost node %s", node)
3363         _ErrorIf(not node_image[node].vm_capable, constants.CV_EINSTANCEBADNODE,
3364                  instance, "instance lives on non-vm_capable node %s", node)
3365
3366     feedback_fn("* Verifying orphan volumes")
3367     reserved = utils.FieldSet(*cluster.reserved_lvs)
3368
3369     # We will get spurious "unknown volume" warnings if any node of this group
3370     # is secondary for an instance whose primary is in another group. To avoid
3371     # them, we find these instances and add their volumes to node_vol_should.
3372     for inst in self.all_inst_info.values():
3373       for secondary in inst.secondary_nodes:
3374         if (secondary in self.my_node_info
3375             and inst.name not in self.my_inst_info):
3376           inst.MapLVsByNode(node_vol_should)
3377           break
3378
3379     self._VerifyOrphanVolumes(node_vol_should, node_image, reserved)
3380
3381     if constants.VERIFY_NPLUSONE_MEM not in self.op.skip_checks:
3382       feedback_fn("* Verifying N+1 Memory redundancy")
3383       self._VerifyNPlusOneMemory(node_image, self.my_inst_info)
3384
3385     feedback_fn("* Other Notes")
3386     if i_non_redundant:
3387       feedback_fn("  - NOTICE: %d non-redundant instance(s) found."
3388                   % len(i_non_redundant))
3389
3390     if i_non_a_balanced:
3391       feedback_fn("  - NOTICE: %d non-auto-balanced instance(s) found."
3392                   % len(i_non_a_balanced))
3393
3394     if i_offline:
3395       feedback_fn("  - NOTICE: %d offline instance(s) found." % i_offline)
3396
3397     if n_offline:
3398       feedback_fn("  - NOTICE: %d offline node(s) found." % n_offline)
3399
3400     if n_drained:
3401       feedback_fn("  - NOTICE: %d drained node(s) found." % n_drained)
3402
3403     return not self.bad
3404
3405   def HooksCallBack(self, phase, hooks_results, feedback_fn, lu_result):
3406     """Analyze the post-hooks' result
3407
3408     This method analyses the hook result, handles it, and sends some
3409     nicely-formatted feedback back to the user.
3410
3411     @param phase: one of L{constants.HOOKS_PHASE_POST} or
3412         L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
3413     @param hooks_results: the results of the multi-node hooks rpc call
3414     @param feedback_fn: function used send feedback back to the caller
3415     @param lu_result: previous Exec result
3416     @return: the new Exec result, based on the previous result
3417         and hook results
3418
3419     """
3420     # We only really run POST phase hooks, only for non-empty groups,
3421     # and are only interested in their results
3422     if not self.my_node_names:
3423       # empty node group
3424       pass
3425     elif phase == constants.HOOKS_PHASE_POST:
3426       # Used to change hooks' output to proper indentation
3427       feedback_fn("* Hooks Results")
3428       assert hooks_results, "invalid result from hooks"
3429
3430       for node_name in hooks_results:
3431         res = hooks_results[node_name]
3432         msg = res.fail_msg
3433         test = msg and not res.offline
3434         self._ErrorIf(test, constants.CV_ENODEHOOKS, node_name,
3435                       "Communication failure in hooks execution: %s", msg)
3436         if res.offline or msg:
3437           # No need to investigate payload if node is offline or gave
3438           # an error.
3439           continue
3440         for script, hkr, output in res.payload:
3441           test = hkr == constants.HKR_FAIL
3442           self._ErrorIf(test, constants.CV_ENODEHOOKS, node_name,
3443                         "Script %s failed, output:", script)
3444           if test:
3445             output = self._HOOKS_INDENT_RE.sub("      ", output)
3446             feedback_fn("%s" % output)
3447             lu_result = False
3448
3449     return lu_result
3450
3451
3452 class LUClusterVerifyDisks(NoHooksLU):
3453   """Verifies the cluster disks status.
3454
3455   """
3456   REQ_BGL = False
3457
3458   def ExpandNames(self):
3459     self.share_locks = _ShareAll()
3460     self.needed_locks = {
3461       locking.LEVEL_NODEGROUP: locking.ALL_SET,
3462       }
3463
3464   def Exec(self, feedback_fn):
3465     group_names = self.owned_locks(locking.LEVEL_NODEGROUP)
3466
3467     # Submit one instance of L{opcodes.OpGroupVerifyDisks} per node group
3468     return ResultWithJobs([[opcodes.OpGroupVerifyDisks(group_name=group)]
3469                            for group in group_names])
3470
3471
3472 class LUGroupVerifyDisks(NoHooksLU):
3473   """Verifies the status of all disks in a node group.
3474
3475   """
3476   REQ_BGL = False
3477
3478   def ExpandNames(self):
3479     # Raises errors.OpPrereqError on its own if group can't be found
3480     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
3481
3482     self.share_locks = _ShareAll()
3483     self.needed_locks = {
3484       locking.LEVEL_INSTANCE: [],
3485       locking.LEVEL_NODEGROUP: [],
3486       locking.LEVEL_NODE: [],
3487       }
3488
3489   def DeclareLocks(self, level):
3490     if level == locking.LEVEL_INSTANCE:
3491       assert not self.needed_locks[locking.LEVEL_INSTANCE]
3492
3493       # Lock instances optimistically, needs verification once node and group
3494       # locks have been acquired
3495       self.needed_locks[locking.LEVEL_INSTANCE] = \
3496         self.cfg.GetNodeGroupInstances(self.group_uuid)
3497
3498     elif level == locking.LEVEL_NODEGROUP:
3499       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
3500
3501       self.needed_locks[locking.LEVEL_NODEGROUP] = \
3502         set([self.group_uuid] +
3503             # Lock all groups used by instances optimistically; this requires
3504             # going via the node before it's locked, requiring verification
3505             # later on
3506             [group_uuid
3507              for instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
3508              for group_uuid in self.cfg.GetInstanceNodeGroups(instance_name)])
3509
3510     elif level == locking.LEVEL_NODE:
3511       # This will only lock the nodes in the group to be verified which contain
3512       # actual instances
3513       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
3514       self._LockInstancesNodes()
3515
3516       # Lock all nodes in group to be verified
3517       assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
3518       member_nodes = self.cfg.GetNodeGroup(self.group_uuid).members
3519       self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
3520
3521   def CheckPrereq(self):
3522     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
3523     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
3524     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
3525
3526     assert self.group_uuid in owned_groups
3527
3528     # Check if locked instances are still correct
3529     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
3530
3531     # Get instance information
3532     self.instances = dict(self.cfg.GetMultiInstanceInfo(owned_instances))
3533
3534     # Check if node groups for locked instances are still correct
3535     _CheckInstancesNodeGroups(self.cfg, self.instances,
3536                               owned_groups, owned_nodes, self.group_uuid)
3537
3538   def Exec(self, feedback_fn):
3539     """Verify integrity of cluster disks.
3540
3541     @rtype: tuple of three items
3542     @return: a tuple of (dict of node-to-node_error, list of instances
3543         which need activate-disks, dict of instance: (node, volume) for
3544         missing volumes
3545
3546     """
3547     res_nodes = {}
3548     res_instances = set()
3549     res_missing = {}
3550
3551     nv_dict = _MapInstanceDisksToNodes([inst
3552             for inst in self.instances.values()
3553             if inst.admin_state == constants.ADMINST_UP])
3554
3555     if nv_dict:
3556       nodes = utils.NiceSort(set(self.owned_locks(locking.LEVEL_NODE)) &
3557                              set(self.cfg.GetVmCapableNodeList()))
3558
3559       node_lvs = self.rpc.call_lv_list(nodes, [])
3560
3561       for (node, node_res) in node_lvs.items():
3562         if node_res.offline:
3563           continue
3564
3565         msg = node_res.fail_msg
3566         if msg:
3567           logging.warning("Error enumerating LVs on node %s: %s", node, msg)
3568           res_nodes[node] = msg
3569           continue
3570
3571         for lv_name, (_, _, lv_online) in node_res.payload.items():
3572           inst = nv_dict.pop((node, lv_name), None)
3573           if not (lv_online or inst is None):
3574             res_instances.add(inst)
3575
3576       # any leftover items in nv_dict are missing LVs, let's arrange the data
3577       # better
3578       for key, inst in nv_dict.iteritems():
3579         res_missing.setdefault(inst, []).append(list(key))
3580
3581     return (res_nodes, list(res_instances), res_missing)
3582
3583
3584 class LUClusterRepairDiskSizes(NoHooksLU):
3585   """Verifies the cluster disks sizes.
3586
3587   """
3588   REQ_BGL = False
3589
3590   def ExpandNames(self):
3591     if self.op.instances:
3592       self.wanted_names = _GetWantedInstances(self, self.op.instances)
3593       self.needed_locks = {
3594         locking.LEVEL_NODE_RES: [],
3595         locking.LEVEL_INSTANCE: self.wanted_names,
3596         }
3597       self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
3598     else:
3599       self.wanted_names = None
3600       self.needed_locks = {
3601         locking.LEVEL_NODE_RES: locking.ALL_SET,
3602         locking.LEVEL_INSTANCE: locking.ALL_SET,
3603         }
3604     self.share_locks = {
3605       locking.LEVEL_NODE_RES: 1,
3606       locking.LEVEL_INSTANCE: 0,
3607       }
3608
3609   def DeclareLocks(self, level):
3610     if level == locking.LEVEL_NODE_RES and self.wanted_names is not None:
3611       self._LockInstancesNodes(primary_only=True, level=level)
3612
3613   def CheckPrereq(self):
3614     """Check prerequisites.
3615
3616     This only checks the optional instance list against the existing names.
3617
3618     """
3619     if self.wanted_names is None:
3620       self.wanted_names = self.owned_locks(locking.LEVEL_INSTANCE)
3621
3622     self.wanted_instances = \
3623         map(compat.snd, self.cfg.GetMultiInstanceInfo(self.wanted_names))
3624
3625   def _EnsureChildSizes(self, disk):
3626     """Ensure children of the disk have the needed disk size.
3627
3628     This is valid mainly for DRBD8 and fixes an issue where the
3629     children have smaller disk size.
3630
3631     @param disk: an L{ganeti.objects.Disk} object
3632
3633     """
3634     if disk.dev_type == constants.LD_DRBD8:
3635       assert disk.children, "Empty children for DRBD8?"
3636       fchild = disk.children[0]
3637       mismatch = fchild.size < disk.size
3638       if mismatch:
3639         self.LogInfo("Child disk has size %d, parent %d, fixing",
3640                      fchild.size, disk.size)
3641         fchild.size = disk.size
3642
3643       # and we recurse on this child only, not on the metadev
3644       return self._EnsureChildSizes(fchild) or mismatch
3645     else:
3646       return False
3647
3648   def Exec(self, feedback_fn):
3649     """Verify the size of cluster disks.
3650
3651     """
3652     # TODO: check child disks too
3653     # TODO: check differences in size between primary/secondary nodes
3654     per_node_disks = {}
3655     for instance in self.wanted_instances:
3656       pnode = instance.primary_node
3657       if pnode not in per_node_disks:
3658         per_node_disks[pnode] = []
3659       for idx, disk in enumerate(instance.disks):
3660         per_node_disks[pnode].append((instance, idx, disk))
3661
3662     assert not (frozenset(per_node_disks.keys()) -
3663                 self.owned_locks(locking.LEVEL_NODE_RES)), \
3664       "Not owning correct locks"
3665     assert not self.owned_locks(locking.LEVEL_NODE)
3666
3667     changed = []
3668     for node, dskl in per_node_disks.items():
3669       newl = [v[2].Copy() for v in dskl]
3670       for dsk in newl:
3671         self.cfg.SetDiskID(dsk, node)
3672       result = self.rpc.call_blockdev_getsize(node, newl)
3673       if result.fail_msg:
3674         self.LogWarning("Failure in blockdev_getsize call to node"
3675                         " %s, ignoring", node)
3676         continue
3677       if len(result.payload) != len(dskl):
3678         logging.warning("Invalid result from node %s: len(dksl)=%d,"
3679                         " result.payload=%s", node, len(dskl), result.payload)
3680         self.LogWarning("Invalid result from node %s, ignoring node results",
3681                         node)
3682         continue
3683       for ((instance, idx, disk), size) in zip(dskl, result.payload):
3684         if size is None:
3685           self.LogWarning("Disk %d of instance %s did not return size"
3686                           " information, ignoring", idx, instance.name)
3687           continue
3688         if not isinstance(size, (int, long)):
3689           self.LogWarning("Disk %d of instance %s did not return valid"
3690                           " size information, ignoring", idx, instance.name)
3691           continue
3692         size = size >> 20
3693         if size != disk.size:
3694           self.LogInfo("Disk %d of instance %s has mismatched size,"
3695                        " correcting: recorded %d, actual %d", idx,
3696                        instance.name, disk.size, size)
3697           disk.size = size
3698           self.cfg.Update(instance, feedback_fn)
3699           changed.append((instance.name, idx, size))
3700         if self._EnsureChildSizes(disk):
3701           self.cfg.Update(instance, feedback_fn)
3702           changed.append((instance.name, idx, disk.size))
3703     return changed
3704
3705
3706 class LUClusterRename(LogicalUnit):
3707   """Rename the cluster.
3708
3709   """
3710   HPATH = "cluster-rename"
3711   HTYPE = constants.HTYPE_CLUSTER
3712
3713   def BuildHooksEnv(self):
3714     """Build hooks env.
3715
3716     """
3717     return {
3718       "OP_TARGET": self.cfg.GetClusterName(),
3719       "NEW_NAME": self.op.name,
3720       }
3721
3722   def BuildHooksNodes(self):
3723     """Build hooks nodes.
3724
3725     """
3726     return ([self.cfg.GetMasterNode()], self.cfg.GetNodeList())
3727
3728   def CheckPrereq(self):
3729     """Verify that the passed name is a valid one.
3730
3731     """
3732     hostname = netutils.GetHostname(name=self.op.name,
3733                                     family=self.cfg.GetPrimaryIPFamily())
3734
3735     new_name = hostname.name
3736     self.ip = new_ip = hostname.ip
3737     old_name = self.cfg.GetClusterName()
3738     old_ip = self.cfg.GetMasterIP()
3739     if new_name == old_name and new_ip == old_ip:
3740       raise errors.OpPrereqError("Neither the name nor the IP address of the"
3741                                  " cluster has changed",
3742                                  errors.ECODE_INVAL)
3743     if new_ip != old_ip:
3744       if netutils.TcpPing(new_ip, constants.DEFAULT_NODED_PORT):
3745         raise errors.OpPrereqError("The given cluster IP address (%s) is"
3746                                    " reachable on the network" %
3747                                    new_ip, errors.ECODE_NOTUNIQUE)
3748
3749     self.op.name = new_name
3750
3751   def Exec(self, feedback_fn):
3752     """Rename the cluster.
3753
3754     """
3755     clustername = self.op.name
3756     new_ip = self.ip
3757
3758     # shutdown the master IP
3759     master_params = self.cfg.GetMasterNetworkParameters()
3760     ems = self.cfg.GetUseExternalMipScript()
3761     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
3762                                                      master_params, ems)
3763     result.Raise("Could not disable the master role")
3764
3765     try:
3766       cluster = self.cfg.GetClusterInfo()
3767       cluster.cluster_name = clustername
3768       cluster.master_ip = new_ip
3769       self.cfg.Update(cluster, feedback_fn)
3770
3771       # update the known hosts file
3772       ssh.WriteKnownHostsFile(self.cfg, constants.SSH_KNOWN_HOSTS_FILE)
3773       node_list = self.cfg.GetOnlineNodeList()
3774       try:
3775         node_list.remove(master_params.name)
3776       except ValueError:
3777         pass
3778       _UploadHelper(self, node_list, constants.SSH_KNOWN_HOSTS_FILE)
3779     finally:
3780       master_params.ip = new_ip
3781       result = self.rpc.call_node_activate_master_ip(master_params.name,
3782                                                      master_params, ems)
3783       msg = result.fail_msg
3784       if msg:
3785         self.LogWarning("Could not re-enable the master role on"
3786                         " the master, please restart manually: %s", msg)
3787
3788     return clustername
3789
3790
3791 def _ValidateNetmask(cfg, netmask):
3792   """Checks if a netmask is valid.
3793
3794   @type cfg: L{config.ConfigWriter}
3795   @param cfg: The cluster configuration
3796   @type netmask: int
3797   @param netmask: the netmask to be verified
3798   @raise errors.OpPrereqError: if the validation fails
3799
3800   """
3801   ip_family = cfg.GetPrimaryIPFamily()
3802   try:
3803     ipcls = netutils.IPAddress.GetClassFromIpFamily(ip_family)
3804   except errors.ProgrammerError:
3805     raise errors.OpPrereqError("Invalid primary ip family: %s." %
3806                                ip_family)
3807   if not ipcls.ValidateNetmask(netmask):
3808     raise errors.OpPrereqError("CIDR netmask (%s) not valid" %
3809                                 (netmask))
3810
3811
3812 class LUClusterSetParams(LogicalUnit):
3813   """Change the parameters of the cluster.
3814
3815   """
3816   HPATH = "cluster-modify"
3817   HTYPE = constants.HTYPE_CLUSTER
3818   REQ_BGL = False
3819
3820   def CheckArguments(self):
3821     """Check parameters
3822
3823     """
3824     if self.op.uid_pool:
3825       uidpool.CheckUidPool(self.op.uid_pool)
3826
3827     if self.op.add_uids:
3828       uidpool.CheckUidPool(self.op.add_uids)
3829
3830     if self.op.remove_uids:
3831       uidpool.CheckUidPool(self.op.remove_uids)
3832
3833     if self.op.master_netmask is not None:
3834       _ValidateNetmask(self.cfg, self.op.master_netmask)
3835
3836     if self.op.diskparams:
3837       for dt_params in self.op.diskparams.values():
3838         utils.ForceDictType(dt_params, constants.DISK_DT_TYPES)
3839
3840   def ExpandNames(self):
3841     # FIXME: in the future maybe other cluster params won't require checking on
3842     # all nodes to be modified.
3843     self.needed_locks = {
3844       locking.LEVEL_NODE: locking.ALL_SET,
3845       locking.LEVEL_INSTANCE: locking.ALL_SET,
3846       locking.LEVEL_NODEGROUP: locking.ALL_SET,
3847     }
3848     self.share_locks = {
3849         locking.LEVEL_NODE: 1,
3850         locking.LEVEL_INSTANCE: 1,
3851         locking.LEVEL_NODEGROUP: 1,
3852     }
3853
3854   def BuildHooksEnv(self):
3855     """Build hooks env.
3856
3857     """
3858     return {
3859       "OP_TARGET": self.cfg.GetClusterName(),
3860       "NEW_VG_NAME": self.op.vg_name,
3861       }
3862
3863   def BuildHooksNodes(self):
3864     """Build hooks nodes.
3865
3866     """
3867     mn = self.cfg.GetMasterNode()
3868     return ([mn], [mn])
3869
3870   def CheckPrereq(self):
3871     """Check prerequisites.
3872
3873     This checks whether the given params don't conflict and
3874     if the given volume group is valid.
3875
3876     """
3877     if self.op.vg_name is not None and not self.op.vg_name:
3878       if self.cfg.HasAnyDiskOfType(constants.LD_LV):
3879         raise errors.OpPrereqError("Cannot disable lvm storage while lvm-based"
3880                                    " instances exist", errors.ECODE_INVAL)
3881
3882     if self.op.drbd_helper is not None and not self.op.drbd_helper:
3883       if self.cfg.HasAnyDiskOfType(constants.LD_DRBD8):
3884         raise errors.OpPrereqError("Cannot disable drbd helper while"
3885                                    " drbd-based instances exist",
3886                                    errors.ECODE_INVAL)
3887
3888     node_list = self.owned_locks(locking.LEVEL_NODE)
3889
3890     # if vg_name not None, checks given volume group on all nodes
3891     if self.op.vg_name:
3892       vglist = self.rpc.call_vg_list(node_list)
3893       for node in node_list:
3894         msg = vglist[node].fail_msg
3895         if msg:
3896           # ignoring down node
3897           self.LogWarning("Error while gathering data on node %s"
3898                           " (ignoring node): %s", node, msg)
3899           continue
3900         vgstatus = utils.CheckVolumeGroupSize(vglist[node].payload,
3901                                               self.op.vg_name,
3902                                               constants.MIN_VG_SIZE)
3903         if vgstatus:
3904           raise errors.OpPrereqError("Error on node '%s': %s" %
3905                                      (node, vgstatus), errors.ECODE_ENVIRON)
3906
3907     if self.op.drbd_helper:
3908       # checks given drbd helper on all nodes
3909       helpers = self.rpc.call_drbd_helper(node_list)
3910       for (node, ninfo) in self.cfg.GetMultiNodeInfo(node_list):
3911         if ninfo.offline:
3912           self.LogInfo("Not checking drbd helper on offline node %s", node)
3913           continue
3914         msg = helpers[node].fail_msg
3915         if msg:
3916           raise errors.OpPrereqError("Error checking drbd helper on node"
3917                                      " '%s': %s" % (node, msg),
3918                                      errors.ECODE_ENVIRON)
3919         node_helper = helpers[node].payload
3920         if node_helper != self.op.drbd_helper:
3921           raise errors.OpPrereqError("Error on node '%s': drbd helper is %s" %
3922                                      (node, node_helper), errors.ECODE_ENVIRON)
3923
3924     self.cluster = cluster = self.cfg.GetClusterInfo()
3925     # validate params changes
3926     if self.op.beparams:
3927       objects.UpgradeBeParams(self.op.beparams)
3928       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
3929       self.new_beparams = cluster.SimpleFillBE(self.op.beparams)
3930
3931     if self.op.ndparams:
3932       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
3933       self.new_ndparams = cluster.SimpleFillND(self.op.ndparams)
3934
3935       # TODO: we need a more general way to handle resetting
3936       # cluster-level parameters to default values
3937       if self.new_ndparams["oob_program"] == "":
3938         self.new_ndparams["oob_program"] = \
3939             constants.NDC_DEFAULTS[constants.ND_OOB_PROGRAM]
3940
3941     if self.op.hv_state:
3942       new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
3943                                             self.cluster.hv_state_static)
3944       self.new_hv_state = dict((hv, cluster.SimpleFillHvState(values))
3945                                for hv, values in new_hv_state.items())
3946
3947     if self.op.disk_state:
3948       new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state,
3949                                                 self.cluster.disk_state_static)
3950       self.new_disk_state = \
3951         dict((storage, dict((name, cluster.SimpleFillDiskState(values))
3952                             for name, values in svalues.items()))
3953              for storage, svalues in new_disk_state.items())
3954
3955     if self.op.ipolicy:
3956       self.new_ipolicy = _GetUpdatedIPolicy(cluster.ipolicy, self.op.ipolicy,
3957                                             group_policy=False)
3958
3959       all_instances = self.cfg.GetAllInstancesInfo().values()
3960       violations = set()
3961       for group in self.cfg.GetAllNodeGroupsInfo().values():
3962         instances = frozenset([inst for inst in all_instances
3963                                if compat.any(node in group.members
3964                                              for node in inst.all_nodes)])
3965         new_ipolicy = objects.FillIPolicy(self.new_ipolicy, group.ipolicy)
3966         new = _ComputeNewInstanceViolations(_CalculateGroupIPolicy(cluster,
3967                                                                    group),
3968                                             new_ipolicy, instances)
3969         if new:
3970           violations.update(new)
3971
3972       if violations:
3973         self.LogWarning("After the ipolicy change the following instances"
3974                         " violate them: %s",
3975                         utils.CommaJoin(violations))
3976
3977     if self.op.nicparams:
3978       utils.ForceDictType(self.op.nicparams, constants.NICS_PARAMETER_TYPES)
3979       self.new_nicparams = cluster.SimpleFillNIC(self.op.nicparams)
3980       objects.NIC.CheckParameterSyntax(self.new_nicparams)
3981       nic_errors = []
3982
3983       # check all instances for consistency
3984       for instance in self.cfg.GetAllInstancesInfo().values():
3985         for nic_idx, nic in enumerate(instance.nics):
3986           params_copy = copy.deepcopy(nic.nicparams)
3987           params_filled = objects.FillDict(self.new_nicparams, params_copy)
3988
3989           # check parameter syntax
3990           try:
3991             objects.NIC.CheckParameterSyntax(params_filled)
3992           except errors.ConfigurationError, err:
3993             nic_errors.append("Instance %s, nic/%d: %s" %
3994                               (instance.name, nic_idx, err))
3995
3996           # if we're moving instances to routed, check that they have an ip
3997           target_mode = params_filled[constants.NIC_MODE]
3998           if target_mode == constants.NIC_MODE_ROUTED and not nic.ip:
3999             nic_errors.append("Instance %s, nic/%d: routed NIC with no ip"
4000                               " address" % (instance.name, nic_idx))
4001       if nic_errors:
4002         raise errors.OpPrereqError("Cannot apply the change, errors:\n%s" %
4003                                    "\n".join(nic_errors))
4004
4005     # hypervisor list/parameters
4006     self.new_hvparams = new_hvp = objects.FillDict(cluster.hvparams, {})
4007     if self.op.hvparams:
4008       for hv_name, hv_dict in self.op.hvparams.items():
4009         if hv_name not in self.new_hvparams:
4010           self.new_hvparams[hv_name] = hv_dict
4011         else:
4012           self.new_hvparams[hv_name].update(hv_dict)
4013
4014     # disk template parameters
4015     self.new_diskparams = objects.FillDict(cluster.diskparams, {})
4016     if self.op.diskparams:
4017       for dt_name, dt_params in self.op.diskparams.items():
4018         if dt_name not in self.op.diskparams:
4019           self.new_diskparams[dt_name] = dt_params
4020         else:
4021           self.new_diskparams[dt_name].update(dt_params)
4022
4023     # os hypervisor parameters
4024     self.new_os_hvp = objects.FillDict(cluster.os_hvp, {})
4025     if self.op.os_hvp:
4026       for os_name, hvs in self.op.os_hvp.items():
4027         if os_name not in self.new_os_hvp:
4028           self.new_os_hvp[os_name] = hvs
4029         else:
4030           for hv_name, hv_dict in hvs.items():
4031             if hv_name not in self.new_os_hvp[os_name]:
4032               self.new_os_hvp[os_name][hv_name] = hv_dict
4033             else:
4034               self.new_os_hvp[os_name][hv_name].update(hv_dict)
4035
4036     # os parameters
4037     self.new_osp = objects.FillDict(cluster.osparams, {})
4038     if self.op.osparams:
4039       for os_name, osp in self.op.osparams.items():
4040         if os_name not in self.new_osp:
4041           self.new_osp[os_name] = {}
4042
4043         self.new_osp[os_name] = _GetUpdatedParams(self.new_osp[os_name], osp,
4044                                                   use_none=True)
4045
4046         if not self.new_osp[os_name]:
4047           # we removed all parameters
4048           del self.new_osp[os_name]
4049         else:
4050           # check the parameter validity (remote check)
4051           _CheckOSParams(self, False, [self.cfg.GetMasterNode()],
4052                          os_name, self.new_osp[os_name])
4053
4054     # changes to the hypervisor list
4055     if self.op.enabled_hypervisors is not None:
4056       self.hv_list = self.op.enabled_hypervisors
4057       for hv in self.hv_list:
4058         # if the hypervisor doesn't already exist in the cluster
4059         # hvparams, we initialize it to empty, and then (in both
4060         # cases) we make sure to fill the defaults, as we might not
4061         # have a complete defaults list if the hypervisor wasn't
4062         # enabled before
4063         if hv not in new_hvp:
4064           new_hvp[hv] = {}
4065         new_hvp[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], new_hvp[hv])
4066         utils.ForceDictType(new_hvp[hv], constants.HVS_PARAMETER_TYPES)
4067     else:
4068       self.hv_list = cluster.enabled_hypervisors
4069
4070     if self.op.hvparams or self.op.enabled_hypervisors is not None:
4071       # either the enabled list has changed, or the parameters have, validate
4072       for hv_name, hv_params in self.new_hvparams.items():
4073         if ((self.op.hvparams and hv_name in self.op.hvparams) or
4074             (self.op.enabled_hypervisors and
4075              hv_name in self.op.enabled_hypervisors)):
4076           # either this is a new hypervisor, or its parameters have changed
4077           hv_class = hypervisor.GetHypervisor(hv_name)
4078           utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
4079           hv_class.CheckParameterSyntax(hv_params)
4080           _CheckHVParams(self, node_list, hv_name, hv_params)
4081
4082     if self.op.os_hvp:
4083       # no need to check any newly-enabled hypervisors, since the
4084       # defaults have already been checked in the above code-block
4085       for os_name, os_hvp in self.new_os_hvp.items():
4086         for hv_name, hv_params in os_hvp.items():
4087           utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
4088           # we need to fill in the new os_hvp on top of the actual hv_p
4089           cluster_defaults = self.new_hvparams.get(hv_name, {})
4090           new_osp = objects.FillDict(cluster_defaults, hv_params)
4091           hv_class = hypervisor.GetHypervisor(hv_name)
4092           hv_class.CheckParameterSyntax(new_osp)
4093           _CheckHVParams(self, node_list, hv_name, new_osp)
4094
4095     if self.op.default_iallocator:
4096       alloc_script = utils.FindFile(self.op.default_iallocator,
4097                                     constants.IALLOCATOR_SEARCH_PATH,
4098                                     os.path.isfile)
4099       if alloc_script is None:
4100         raise errors.OpPrereqError("Invalid default iallocator script '%s'"
4101                                    " specified" % self.op.default_iallocator,
4102                                    errors.ECODE_INVAL)
4103
4104   def Exec(self, feedback_fn):
4105     """Change the parameters of the cluster.
4106
4107     """
4108     if self.op.vg_name is not None:
4109       new_volume = self.op.vg_name
4110       if not new_volume:
4111         new_volume = None
4112       if new_volume != self.cfg.GetVGName():
4113         self.cfg.SetVGName(new_volume)
4114       else:
4115         feedback_fn("Cluster LVM configuration already in desired"
4116                     " state, not changing")
4117     if self.op.drbd_helper is not None:
4118       new_helper = self.op.drbd_helper
4119       if not new_helper:
4120         new_helper = None
4121       if new_helper != self.cfg.GetDRBDHelper():
4122         self.cfg.SetDRBDHelper(new_helper)
4123       else:
4124         feedback_fn("Cluster DRBD helper already in desired state,"
4125                     " not changing")
4126     if self.op.hvparams:
4127       self.cluster.hvparams = self.new_hvparams
4128     if self.op.os_hvp:
4129       self.cluster.os_hvp = self.new_os_hvp
4130     if self.op.enabled_hypervisors is not None:
4131       self.cluster.hvparams = self.new_hvparams
4132       self.cluster.enabled_hypervisors = self.op.enabled_hypervisors
4133     if self.op.beparams:
4134       self.cluster.beparams[constants.PP_DEFAULT] = self.new_beparams
4135     if self.op.nicparams:
4136       self.cluster.nicparams[constants.PP_DEFAULT] = self.new_nicparams
4137     if self.op.ipolicy:
4138       self.cluster.ipolicy = self.new_ipolicy
4139     if self.op.osparams:
4140       self.cluster.osparams = self.new_osp
4141     if self.op.ndparams:
4142       self.cluster.ndparams = self.new_ndparams
4143     if self.op.diskparams:
4144       self.cluster.diskparams = self.new_diskparams
4145     if self.op.hv_state:
4146       self.cluster.hv_state_static = self.new_hv_state
4147     if self.op.disk_state:
4148       self.cluster.disk_state_static = self.new_disk_state
4149
4150     if self.op.candidate_pool_size is not None:
4151       self.cluster.candidate_pool_size = self.op.candidate_pool_size
4152       # we need to update the pool size here, otherwise the save will fail
4153       _AdjustCandidatePool(self, [])
4154
4155     if self.op.maintain_node_health is not None:
4156       if self.op.maintain_node_health and not constants.ENABLE_CONFD:
4157         feedback_fn("Note: CONFD was disabled at build time, node health"
4158                     " maintenance is not useful (still enabling it)")
4159       self.cluster.maintain_node_health = self.op.maintain_node_health
4160
4161     if self.op.prealloc_wipe_disks is not None:
4162       self.cluster.prealloc_wipe_disks = self.op.prealloc_wipe_disks
4163
4164     if self.op.add_uids is not None:
4165       uidpool.AddToUidPool(self.cluster.uid_pool, self.op.add_uids)
4166
4167     if self.op.remove_uids is not None:
4168       uidpool.RemoveFromUidPool(self.cluster.uid_pool, self.op.remove_uids)
4169
4170     if self.op.uid_pool is not None:
4171       self.cluster.uid_pool = self.op.uid_pool
4172
4173     if self.op.default_iallocator is not None:
4174       self.cluster.default_iallocator = self.op.default_iallocator
4175
4176     if self.op.reserved_lvs is not None:
4177       self.cluster.reserved_lvs = self.op.reserved_lvs
4178
4179     if self.op.use_external_mip_script is not None:
4180       self.cluster.use_external_mip_script = self.op.use_external_mip_script
4181
4182     def helper_os(aname, mods, desc):
4183       desc += " OS list"
4184       lst = getattr(self.cluster, aname)
4185       for key, val in mods:
4186         if key == constants.DDM_ADD:
4187           if val in lst:
4188             feedback_fn("OS %s already in %s, ignoring" % (val, desc))
4189           else:
4190             lst.append(val)
4191         elif key == constants.DDM_REMOVE:
4192           if val in lst:
4193             lst.remove(val)
4194           else:
4195             feedback_fn("OS %s not found in %s, ignoring" % (val, desc))
4196         else:
4197           raise errors.ProgrammerError("Invalid modification '%s'" % key)
4198
4199     if self.op.hidden_os:
4200       helper_os("hidden_os", self.op.hidden_os, "hidden")
4201
4202     if self.op.blacklisted_os:
4203       helper_os("blacklisted_os", self.op.blacklisted_os, "blacklisted")
4204
4205     if self.op.master_netdev:
4206       master_params = self.cfg.GetMasterNetworkParameters()
4207       ems = self.cfg.GetUseExternalMipScript()
4208       feedback_fn("Shutting down master ip on the current netdev (%s)" %
4209                   self.cluster.master_netdev)
4210       result = self.rpc.call_node_deactivate_master_ip(master_params.name,
4211                                                        master_params, ems)
4212       result.Raise("Could not disable the master ip")
4213       feedback_fn("Changing master_netdev from %s to %s" %
4214                   (master_params.netdev, self.op.master_netdev))
4215       self.cluster.master_netdev = self.op.master_netdev
4216
4217     if self.op.master_netmask:
4218       master_params = self.cfg.GetMasterNetworkParameters()
4219       feedback_fn("Changing master IP netmask to %s" % self.op.master_netmask)
4220       result = self.rpc.call_node_change_master_netmask(master_params.name,
4221                                                         master_params.netmask,
4222                                                         self.op.master_netmask,
4223                                                         master_params.ip,
4224                                                         master_params.netdev)
4225       if result.fail_msg:
4226         msg = "Could not change the master IP netmask: %s" % result.fail_msg
4227         feedback_fn(msg)
4228
4229       self.cluster.master_netmask = self.op.master_netmask
4230
4231     self.cfg.Update(self.cluster, feedback_fn)
4232
4233     if self.op.master_netdev:
4234       master_params = self.cfg.GetMasterNetworkParameters()
4235       feedback_fn("Starting the master ip on the new master netdev (%s)" %
4236                   self.op.master_netdev)
4237       ems = self.cfg.GetUseExternalMipScript()
4238       result = self.rpc.call_node_activate_master_ip(master_params.name,
4239                                                      master_params, ems)
4240       if result.fail_msg:
4241         self.LogWarning("Could not re-enable the master ip on"
4242                         " the master, please restart manually: %s",
4243                         result.fail_msg)
4244
4245
4246 def _UploadHelper(lu, nodes, fname):
4247   """Helper for uploading a file and showing warnings.
4248
4249   """
4250   if os.path.exists(fname):
4251     result = lu.rpc.call_upload_file(nodes, fname)
4252     for to_node, to_result in result.items():
4253       msg = to_result.fail_msg
4254       if msg:
4255         msg = ("Copy of file %s to node %s failed: %s" %
4256                (fname, to_node, msg))
4257         lu.proc.LogWarning(msg)
4258
4259
4260 def _ComputeAncillaryFiles(cluster, redist):
4261   """Compute files external to Ganeti which need to be consistent.
4262
4263   @type redist: boolean
4264   @param redist: Whether to include files which need to be redistributed
4265
4266   """
4267   # Compute files for all nodes
4268   files_all = set([
4269     constants.SSH_KNOWN_HOSTS_FILE,
4270     constants.CONFD_HMAC_KEY,
4271     constants.CLUSTER_DOMAIN_SECRET_FILE,
4272     constants.SPICE_CERT_FILE,
4273     constants.SPICE_CACERT_FILE,
4274     constants.RAPI_USERS_FILE,
4275     ])
4276
4277   if not redist:
4278     files_all.update(constants.ALL_CERT_FILES)
4279     files_all.update(ssconf.SimpleStore().GetFileList())
4280   else:
4281     # we need to ship at least the RAPI certificate
4282     files_all.add(constants.RAPI_CERT_FILE)
4283
4284   if cluster.modify_etc_hosts:
4285     files_all.add(constants.ETC_HOSTS)
4286
4287   # Files which are optional, these must:
4288   # - be present in one other category as well
4289   # - either exist or not exist on all nodes of that category (mc, vm all)
4290   files_opt = set([
4291     constants.RAPI_USERS_FILE,
4292     ])
4293
4294   # Files which should only be on master candidates
4295   files_mc = set()
4296
4297   if not redist:
4298     files_mc.add(constants.CLUSTER_CONF_FILE)
4299
4300     # FIXME: this should also be replicated but Ganeti doesn't support files_mc
4301     # replication
4302     files_mc.add(constants.DEFAULT_MASTER_SETUP_SCRIPT)
4303
4304   # Files which should only be on VM-capable nodes
4305   files_vm = set(filename
4306     for hv_name in cluster.enabled_hypervisors
4307     for filename in hypervisor.GetHypervisor(hv_name).GetAncillaryFiles()[0])
4308
4309   files_opt |= set(filename
4310     for hv_name in cluster.enabled_hypervisors
4311     for filename in hypervisor.GetHypervisor(hv_name).GetAncillaryFiles()[1])
4312
4313   # Filenames in each category must be unique
4314   all_files_set = files_all | files_mc | files_vm
4315   assert (len(all_files_set) ==
4316           sum(map(len, [files_all, files_mc, files_vm]))), \
4317          "Found file listed in more than one file list"
4318
4319   # Optional files must be present in one other category
4320   assert all_files_set.issuperset(files_opt), \
4321          "Optional file not in a different required list"
4322
4323   return (files_all, files_opt, files_mc, files_vm)
4324
4325
4326 def _RedistributeAncillaryFiles(lu, additional_nodes=None, additional_vm=True):
4327   """Distribute additional files which are part of the cluster configuration.
4328
4329   ConfigWriter takes care of distributing the config and ssconf files, but
4330   there are more files which should be distributed to all nodes. This function
4331   makes sure those are copied.
4332
4333   @param lu: calling logical unit
4334   @param additional_nodes: list of nodes not in the config to distribute to
4335   @type additional_vm: boolean
4336   @param additional_vm: whether the additional nodes are vm-capable or not
4337
4338   """
4339   # Gather target nodes
4340   cluster = lu.cfg.GetClusterInfo()
4341   master_info = lu.cfg.GetNodeInfo(lu.cfg.GetMasterNode())
4342
4343   online_nodes = lu.cfg.GetOnlineNodeList()
4344   vm_nodes = lu.cfg.GetVmCapableNodeList()
4345
4346   if additional_nodes is not None:
4347     online_nodes.extend(additional_nodes)
4348     if additional_vm:
4349       vm_nodes.extend(additional_nodes)
4350
4351   # Never distribute to master node
4352   for nodelist in [online_nodes, vm_nodes]:
4353     if master_info.name in nodelist:
4354       nodelist.remove(master_info.name)
4355
4356   # Gather file lists
4357   (files_all, _, files_mc, files_vm) = \
4358     _ComputeAncillaryFiles(cluster, True)
4359
4360   # Never re-distribute configuration file from here
4361   assert not (constants.CLUSTER_CONF_FILE in files_all or
4362               constants.CLUSTER_CONF_FILE in files_vm)
4363   assert not files_mc, "Master candidates not handled in this function"
4364
4365   filemap = [
4366     (online_nodes, files_all),
4367     (vm_nodes, files_vm),
4368     ]
4369
4370   # Upload the files
4371   for (node_list, files) in filemap:
4372     for fname in files:
4373       _UploadHelper(lu, node_list, fname)
4374
4375
4376 class LUClusterRedistConf(NoHooksLU):
4377   """Force the redistribution of cluster configuration.
4378
4379   This is a very simple LU.
4380
4381   """
4382   REQ_BGL = False
4383
4384   def ExpandNames(self):
4385     self.needed_locks = {
4386       locking.LEVEL_NODE: locking.ALL_SET,
4387     }
4388     self.share_locks[locking.LEVEL_NODE] = 1
4389
4390   def Exec(self, feedback_fn):
4391     """Redistribute the configuration.
4392
4393     """
4394     self.cfg.Update(self.cfg.GetClusterInfo(), feedback_fn)
4395     _RedistributeAncillaryFiles(self)
4396
4397
4398 class LUClusterActivateMasterIp(NoHooksLU):
4399   """Activate the master IP on the master node.
4400
4401   """
4402   def Exec(self, feedback_fn):
4403     """Activate the master IP.
4404
4405     """
4406     master_params = self.cfg.GetMasterNetworkParameters()
4407     ems = self.cfg.GetUseExternalMipScript()
4408     result = self.rpc.call_node_activate_master_ip(master_params.name,
4409                                                    master_params, ems)
4410     result.Raise("Could not activate the master IP")
4411
4412
4413 class LUClusterDeactivateMasterIp(NoHooksLU):
4414   """Deactivate the master IP on the master node.
4415
4416   """
4417   def Exec(self, feedback_fn):
4418     """Deactivate the master IP.
4419
4420     """
4421     master_params = self.cfg.GetMasterNetworkParameters()
4422     ems = self.cfg.GetUseExternalMipScript()
4423     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
4424                                                      master_params, ems)
4425     result.Raise("Could not deactivate the master IP")
4426
4427
4428 def _WaitForSync(lu, instance, disks=None, oneshot=False):
4429   """Sleep and poll for an instance's disk to sync.
4430
4431   """
4432   if not instance.disks or disks is not None and not disks:
4433     return True
4434
4435   disks = _ExpandCheckDisks(instance, disks)
4436
4437   if not oneshot:
4438     lu.proc.LogInfo("Waiting for instance %s to sync disks." % instance.name)
4439
4440   node = instance.primary_node
4441
4442   for dev in disks:
4443     lu.cfg.SetDiskID(dev, node)
4444
4445   # TODO: Convert to utils.Retry
4446
4447   retries = 0
4448   degr_retries = 10 # in seconds, as we sleep 1 second each time
4449   while True:
4450     max_time = 0
4451     done = True
4452     cumul_degraded = False
4453     rstats = lu.rpc.call_blockdev_getmirrorstatus(node, disks)
4454     msg = rstats.fail_msg
4455     if msg:
4456       lu.LogWarning("Can't get any data from node %s: %s", node, msg)
4457       retries += 1
4458       if retries >= 10:
4459         raise errors.RemoteError("Can't contact node %s for mirror data,"
4460                                  " aborting." % node)
4461       time.sleep(6)
4462       continue
4463     rstats = rstats.payload
4464     retries = 0
4465     for i, mstat in enumerate(rstats):
4466       if mstat is None:
4467         lu.LogWarning("Can't compute data for node %s/%s",
4468                            node, disks[i].iv_name)
4469         continue
4470
4471       cumul_degraded = (cumul_degraded or
4472                         (mstat.is_degraded and mstat.sync_percent is None))
4473       if mstat.sync_percent is not None:
4474         done = False
4475         if mstat.estimated_time is not None:
4476           rem_time = ("%s remaining (estimated)" %
4477                       utils.FormatSeconds(mstat.estimated_time))
4478           max_time = mstat.estimated_time
4479         else:
4480           rem_time = "no time estimate"
4481         lu.proc.LogInfo("- device %s: %5.2f%% done, %s" %
4482                         (disks[i].iv_name, mstat.sync_percent, rem_time))
4483
4484     # if we're done but degraded, let's do a few small retries, to
4485     # make sure we see a stable and not transient situation; therefore
4486     # we force restart of the loop
4487     if (done or oneshot) and cumul_degraded and degr_retries > 0:
4488       logging.info("Degraded disks found, %d retries left", degr_retries)
4489       degr_retries -= 1
4490       time.sleep(1)
4491       continue
4492
4493     if done or oneshot:
4494       break
4495
4496     time.sleep(min(60, max_time))
4497
4498   if done:
4499     lu.proc.LogInfo("Instance %s's disks are in sync." % instance.name)
4500   return not cumul_degraded
4501
4502
4503 def _CheckDiskConsistency(lu, instance, dev, node, on_primary, ldisk=False):
4504   """Check that mirrors are not degraded.
4505
4506   The ldisk parameter, if True, will change the test from the
4507   is_degraded attribute (which represents overall non-ok status for
4508   the device(s)) to the ldisk (representing the local storage status).
4509
4510   """
4511   lu.cfg.SetDiskID(dev, node)
4512
4513   result = True
4514
4515   if on_primary or dev.AssembleOnSecondary():
4516     rstats = lu.rpc.call_blockdev_find(node, dev)
4517     msg = rstats.fail_msg
4518     if msg:
4519       lu.LogWarning("Can't find disk on node %s: %s", node, msg)
4520       result = False
4521     elif not rstats.payload:
4522       lu.LogWarning("Can't find disk on node %s", node)
4523       result = False
4524     else:
4525       if ldisk:
4526         result = result and rstats.payload.ldisk_status == constants.LDS_OKAY
4527       else:
4528         result = result and not rstats.payload.is_degraded
4529
4530   if dev.children:
4531     for child in dev.children:
4532       result = result and _CheckDiskConsistency(lu, instance, child, node,
4533                                                 on_primary)
4534
4535   return result
4536
4537
4538 class LUOobCommand(NoHooksLU):
4539   """Logical unit for OOB handling.
4540
4541   """
4542   REQ_BGL = False
4543   _SKIP_MASTER = (constants.OOB_POWER_OFF, constants.OOB_POWER_CYCLE)
4544
4545   def ExpandNames(self):
4546     """Gather locks we need.
4547
4548     """
4549     if self.op.node_names:
4550       self.op.node_names = _GetWantedNodes(self, self.op.node_names)
4551       lock_names = self.op.node_names
4552     else:
4553       lock_names = locking.ALL_SET
4554
4555     self.needed_locks = {
4556       locking.LEVEL_NODE: lock_names,
4557       }
4558
4559   def CheckPrereq(self):
4560     """Check prerequisites.
4561
4562     This checks:
4563      - the node exists in the configuration
4564      - OOB is supported
4565
4566     Any errors are signaled by raising errors.OpPrereqError.
4567
4568     """
4569     self.nodes = []
4570     self.master_node = self.cfg.GetMasterNode()
4571
4572     assert self.op.power_delay >= 0.0
4573
4574     if self.op.node_names:
4575       if (self.op.command in self._SKIP_MASTER and
4576           self.master_node in self.op.node_names):
4577         master_node_obj = self.cfg.GetNodeInfo(self.master_node)
4578         master_oob_handler = _SupportsOob(self.cfg, master_node_obj)
4579
4580         if master_oob_handler:
4581           additional_text = ("run '%s %s %s' if you want to operate on the"
4582                              " master regardless") % (master_oob_handler,
4583                                                       self.op.command,
4584                                                       self.master_node)
4585         else:
4586           additional_text = "it does not support out-of-band operations"
4587
4588         raise errors.OpPrereqError(("Operating on the master node %s is not"
4589                                     " allowed for %s; %s") %
4590                                    (self.master_node, self.op.command,
4591                                     additional_text), errors.ECODE_INVAL)
4592     else:
4593       self.op.node_names = self.cfg.GetNodeList()
4594       if self.op.command in self._SKIP_MASTER:
4595         self.op.node_names.remove(self.master_node)
4596
4597     if self.op.command in self._SKIP_MASTER:
4598       assert self.master_node not in self.op.node_names
4599
4600     for (node_name, node) in self.cfg.GetMultiNodeInfo(self.op.node_names):
4601       if node is None:
4602         raise errors.OpPrereqError("Node %s not found" % node_name,
4603                                    errors.ECODE_NOENT)
4604       else:
4605         self.nodes.append(node)
4606
4607       if (not self.op.ignore_status and
4608           (self.op.command == constants.OOB_POWER_OFF and not node.offline)):
4609         raise errors.OpPrereqError(("Cannot power off node %s because it is"
4610                                     " not marked offline") % node_name,
4611                                    errors.ECODE_STATE)
4612
4613   def Exec(self, feedback_fn):
4614     """Execute OOB and return result if we expect any.
4615
4616     """
4617     master_node = self.master_node
4618     ret = []
4619
4620     for idx, node in enumerate(utils.NiceSort(self.nodes,
4621                                               key=lambda node: node.name)):
4622       node_entry = [(constants.RS_NORMAL, node.name)]
4623       ret.append(node_entry)
4624
4625       oob_program = _SupportsOob(self.cfg, node)
4626
4627       if not oob_program:
4628         node_entry.append((constants.RS_UNAVAIL, None))
4629         continue
4630
4631       logging.info("Executing out-of-band command '%s' using '%s' on %s",
4632                    self.op.command, oob_program, node.name)
4633       result = self.rpc.call_run_oob(master_node, oob_program,
4634                                      self.op.command, node.name,
4635                                      self.op.timeout)
4636
4637       if result.fail_msg:
4638         self.LogWarning("Out-of-band RPC failed on node '%s': %s",
4639                         node.name, result.fail_msg)
4640         node_entry.append((constants.RS_NODATA, None))
4641       else:
4642         try:
4643           self._CheckPayload(result)
4644         except errors.OpExecError, err:
4645           self.LogWarning("Payload returned by node '%s' is not valid: %s",
4646                           node.name, err)
4647           node_entry.append((constants.RS_NODATA, None))
4648         else:
4649           if self.op.command == constants.OOB_HEALTH:
4650             # For health we should log important events
4651             for item, status in result.payload:
4652               if status in [constants.OOB_STATUS_WARNING,
4653                             constants.OOB_STATUS_CRITICAL]:
4654                 self.LogWarning("Item '%s' on node '%s' has status '%s'",
4655                                 item, node.name, status)
4656
4657           if self.op.command == constants.OOB_POWER_ON:
4658             node.powered = True
4659           elif self.op.command == constants.OOB_POWER_OFF:
4660             node.powered = False
4661           elif self.op.command == constants.OOB_POWER_STATUS:
4662             powered = result.payload[constants.OOB_POWER_STATUS_POWERED]
4663             if powered != node.powered:
4664               logging.warning(("Recorded power state (%s) of node '%s' does not"
4665                                " match actual power state (%s)"), node.powered,
4666                               node.name, powered)
4667
4668           # For configuration changing commands we should update the node
4669           if self.op.command in (constants.OOB_POWER_ON,
4670                                  constants.OOB_POWER_OFF):
4671             self.cfg.Update(node, feedback_fn)
4672
4673           node_entry.append((constants.RS_NORMAL, result.payload))
4674
4675           if (self.op.command == constants.OOB_POWER_ON and
4676               idx < len(self.nodes) - 1):
4677             time.sleep(self.op.power_delay)
4678
4679     return ret
4680
4681   def _CheckPayload(self, result):
4682     """Checks if the payload is valid.
4683
4684     @param result: RPC result
4685     @raises errors.OpExecError: If payload is not valid
4686
4687     """
4688     errs = []
4689     if self.op.command == constants.OOB_HEALTH:
4690       if not isinstance(result.payload, list):
4691         errs.append("command 'health' is expected to return a list but got %s" %
4692                     type(result.payload))
4693       else:
4694         for item, status in result.payload:
4695           if status not in constants.OOB_STATUSES:
4696             errs.append("health item '%s' has invalid status '%s'" %
4697                         (item, status))
4698
4699     if self.op.command == constants.OOB_POWER_STATUS:
4700       if not isinstance(result.payload, dict):
4701         errs.append("power-status is expected to return a dict but got %s" %
4702                     type(result.payload))
4703
4704     if self.op.command in [
4705         constants.OOB_POWER_ON,
4706         constants.OOB_POWER_OFF,
4707         constants.OOB_POWER_CYCLE,
4708         ]:
4709       if result.payload is not None:
4710         errs.append("%s is expected to not return payload but got '%s'" %
4711                     (self.op.command, result.payload))
4712
4713     if errs:
4714       raise errors.OpExecError("Check of out-of-band payload failed due to %s" %
4715                                utils.CommaJoin(errs))
4716
4717
4718 class _OsQuery(_QueryBase):
4719   FIELDS = query.OS_FIELDS
4720
4721   def ExpandNames(self, lu):
4722     # Lock all nodes in shared mode
4723     # Temporary removal of locks, should be reverted later
4724     # TODO: reintroduce locks when they are lighter-weight
4725     lu.needed_locks = {}
4726     #self.share_locks[locking.LEVEL_NODE] = 1
4727     #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
4728
4729     # The following variables interact with _QueryBase._GetNames
4730     if self.names:
4731       self.wanted = self.names
4732     else:
4733       self.wanted = locking.ALL_SET
4734
4735     self.do_locking = self.use_locking
4736
4737   def DeclareLocks(self, lu, level):
4738     pass
4739
4740   @staticmethod
4741   def _DiagnoseByOS(rlist):
4742     """Remaps a per-node return list into an a per-os per-node dictionary
4743
4744     @param rlist: a map with node names as keys and OS objects as values
4745
4746     @rtype: dict
4747     @return: a dictionary with osnames as keys and as value another
4748         map, with nodes as keys and tuples of (path, status, diagnose,
4749         variants, parameters, api_versions) as values, eg::
4750
4751           {"debian-etch": {"node1": [(/usr/lib/..., True, "", [], []),
4752                                      (/srv/..., False, "invalid api")],
4753                            "node2": [(/srv/..., True, "", [], [])]}
4754           }
4755
4756     """
4757     all_os = {}
4758     # we build here the list of nodes that didn't fail the RPC (at RPC
4759     # level), so that nodes with a non-responding node daemon don't
4760     # make all OSes invalid
4761     good_nodes = [node_name for node_name in rlist
4762                   if not rlist[node_name].fail_msg]
4763     for node_name, nr in rlist.items():
4764       if nr.fail_msg or not nr.payload:
4765         continue
4766       for (name, path, status, diagnose, variants,
4767            params, api_versions) in nr.payload:
4768         if name not in all_os:
4769           # build a list of nodes for this os containing empty lists
4770           # for each node in node_list
4771           all_os[name] = {}
4772           for nname in good_nodes:
4773             all_os[name][nname] = []
4774         # convert params from [name, help] to (name, help)
4775         params = [tuple(v) for v in params]
4776         all_os[name][node_name].append((path, status, diagnose,
4777                                         variants, params, api_versions))
4778     return all_os
4779
4780   def _GetQueryData(self, lu):
4781     """Computes the list of nodes and their attributes.
4782
4783     """
4784     # Locking is not used
4785     assert not (compat.any(lu.glm.is_owned(level)
4786                            for level in locking.LEVELS
4787                            if level != locking.LEVEL_CLUSTER) or
4788                 self.do_locking or self.use_locking)
4789
4790     valid_nodes = [node.name
4791                    for node in lu.cfg.GetAllNodesInfo().values()
4792                    if not node.offline and node.vm_capable]
4793     pol = self._DiagnoseByOS(lu.rpc.call_os_diagnose(valid_nodes))
4794     cluster = lu.cfg.GetClusterInfo()
4795
4796     data = {}
4797
4798     for (os_name, os_data) in pol.items():
4799       info = query.OsInfo(name=os_name, valid=True, node_status=os_data,
4800                           hidden=(os_name in cluster.hidden_os),
4801                           blacklisted=(os_name in cluster.blacklisted_os))
4802
4803       variants = set()
4804       parameters = set()
4805       api_versions = set()
4806
4807       for idx, osl in enumerate(os_data.values()):
4808         info.valid = bool(info.valid and osl and osl[0][1])
4809         if not info.valid:
4810           break
4811
4812         (node_variants, node_params, node_api) = osl[0][3:6]
4813         if idx == 0:
4814           # First entry
4815           variants.update(node_variants)
4816           parameters.update(node_params)
4817           api_versions.update(node_api)
4818         else:
4819           # Filter out inconsistent values
4820           variants.intersection_update(node_variants)
4821           parameters.intersection_update(node_params)
4822           api_versions.intersection_update(node_api)
4823
4824       info.variants = list(variants)
4825       info.parameters = list(parameters)
4826       info.api_versions = list(api_versions)
4827
4828       data[os_name] = info
4829
4830     # Prepare data in requested order
4831     return [data[name] for name in self._GetNames(lu, pol.keys(), None)
4832             if name in data]
4833
4834
4835 class LUOsDiagnose(NoHooksLU):
4836   """Logical unit for OS diagnose/query.
4837
4838   """
4839   REQ_BGL = False
4840
4841   @staticmethod
4842   def _BuildFilter(fields, names):
4843     """Builds a filter for querying OSes.
4844
4845     """
4846     name_filter = qlang.MakeSimpleFilter("name", names)
4847
4848     # Legacy behaviour: Hide hidden, blacklisted or invalid OSes if the
4849     # respective field is not requested
4850     status_filter = [[qlang.OP_NOT, [qlang.OP_TRUE, fname]]
4851                      for fname in ["hidden", "blacklisted"]
4852                      if fname not in fields]
4853     if "valid" not in fields:
4854       status_filter.append([qlang.OP_TRUE, "valid"])
4855
4856     if status_filter:
4857       status_filter.insert(0, qlang.OP_AND)
4858     else:
4859       status_filter = None
4860
4861     if name_filter and status_filter:
4862       return [qlang.OP_AND, name_filter, status_filter]
4863     elif name_filter:
4864       return name_filter
4865     else:
4866       return status_filter
4867
4868   def CheckArguments(self):
4869     self.oq = _OsQuery(self._BuildFilter(self.op.output_fields, self.op.names),
4870                        self.op.output_fields, False)
4871
4872   def ExpandNames(self):
4873     self.oq.ExpandNames(self)
4874
4875   def Exec(self, feedback_fn):
4876     return self.oq.OldStyleQuery(self)
4877
4878
4879 class LUNodeRemove(LogicalUnit):
4880   """Logical unit for removing a node.
4881
4882   """
4883   HPATH = "node-remove"
4884   HTYPE = constants.HTYPE_NODE
4885
4886   def BuildHooksEnv(self):
4887     """Build hooks env.
4888
4889     """
4890     return {
4891       "OP_TARGET": self.op.node_name,
4892       "NODE_NAME": self.op.node_name,
4893       }
4894
4895   def BuildHooksNodes(self):
4896     """Build hooks nodes.
4897
4898     This doesn't run on the target node in the pre phase as a failed
4899     node would then be impossible to remove.
4900
4901     """
4902     all_nodes = self.cfg.GetNodeList()
4903     try:
4904       all_nodes.remove(self.op.node_name)
4905     except ValueError:
4906       pass
4907     return (all_nodes, all_nodes)
4908
4909   def CheckPrereq(self):
4910     """Check prerequisites.
4911
4912     This checks:
4913      - the node exists in the configuration
4914      - it does not have primary or secondary instances
4915      - it's not the master
4916
4917     Any errors are signaled by raising errors.OpPrereqError.
4918
4919     """
4920     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
4921     node = self.cfg.GetNodeInfo(self.op.node_name)
4922     assert node is not None
4923
4924     masternode = self.cfg.GetMasterNode()
4925     if node.name == masternode:
4926       raise errors.OpPrereqError("Node is the master node, failover to another"
4927                                  " node is required", errors.ECODE_INVAL)
4928
4929     for instance_name, instance in self.cfg.GetAllInstancesInfo().items():
4930       if node.name in instance.all_nodes:
4931         raise errors.OpPrereqError("Instance %s is still running on the node,"
4932                                    " please remove first" % instance_name,
4933                                    errors.ECODE_INVAL)
4934     self.op.node_name = node.name
4935     self.node = node
4936
4937   def Exec(self, feedback_fn):
4938     """Removes the node from the cluster.
4939
4940     """
4941     node = self.node
4942     logging.info("Stopping the node daemon and removing configs from node %s",
4943                  node.name)
4944
4945     modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
4946
4947     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER), \
4948       "Not owning BGL"
4949
4950     # Promote nodes to master candidate as needed
4951     _AdjustCandidatePool(self, exceptions=[node.name])
4952     self.context.RemoveNode(node.name)
4953
4954     # Run post hooks on the node before it's removed
4955     _RunPostHook(self, node.name)
4956
4957     result = self.rpc.call_node_leave_cluster(node.name, modify_ssh_setup)
4958     msg = result.fail_msg
4959     if msg:
4960       self.LogWarning("Errors encountered on the remote node while leaving"
4961                       " the cluster: %s", msg)
4962
4963     # Remove node from our /etc/hosts
4964     if self.cfg.GetClusterInfo().modify_etc_hosts:
4965       master_node = self.cfg.GetMasterNode()
4966       result = self.rpc.call_etc_hosts_modify(master_node,
4967                                               constants.ETC_HOSTS_REMOVE,
4968                                               node.name, None)
4969       result.Raise("Can't update hosts file with new host data")
4970       _RedistributeAncillaryFiles(self)
4971
4972
4973 class _NodeQuery(_QueryBase):
4974   FIELDS = query.NODE_FIELDS
4975
4976   def ExpandNames(self, lu):
4977     lu.needed_locks = {}
4978     lu.share_locks = _ShareAll()
4979
4980     if self.names:
4981       self.wanted = _GetWantedNodes(lu, self.names)
4982     else:
4983       self.wanted = locking.ALL_SET
4984
4985     self.do_locking = (self.use_locking and
4986                        query.NQ_LIVE in self.requested_data)
4987
4988     if self.do_locking:
4989       # If any non-static field is requested we need to lock the nodes
4990       lu.needed_locks[locking.LEVEL_NODE] = self.wanted
4991
4992   def DeclareLocks(self, lu, level):
4993     pass
4994
4995   def _GetQueryData(self, lu):
4996     """Computes the list of nodes and their attributes.
4997
4998     """
4999     all_info = lu.cfg.GetAllNodesInfo()
5000
5001     nodenames = self._GetNames(lu, all_info.keys(), locking.LEVEL_NODE)
5002
5003     # Gather data as requested
5004     if query.NQ_LIVE in self.requested_data:
5005       # filter out non-vm_capable nodes
5006       toquery_nodes = [name for name in nodenames if all_info[name].vm_capable]
5007
5008       node_data = lu.rpc.call_node_info(toquery_nodes, [lu.cfg.GetVGName()],
5009                                         [lu.cfg.GetHypervisorType()])
5010       live_data = dict((name, _MakeLegacyNodeInfo(nresult.payload))
5011                        for (name, nresult) in node_data.items()
5012                        if not nresult.fail_msg and nresult.payload)
5013     else:
5014       live_data = None
5015
5016     if query.NQ_INST in self.requested_data:
5017       node_to_primary = dict([(name, set()) for name in nodenames])
5018       node_to_secondary = dict([(name, set()) for name in nodenames])
5019
5020       inst_data = lu.cfg.GetAllInstancesInfo()
5021
5022       for inst in inst_data.values():
5023         if inst.primary_node in node_to_primary:
5024           node_to_primary[inst.primary_node].add(inst.name)
5025         for secnode in inst.secondary_nodes:
5026           if secnode in node_to_secondary:
5027             node_to_secondary[secnode].add(inst.name)
5028     else:
5029       node_to_primary = None
5030       node_to_secondary = None
5031
5032     if query.NQ_OOB in self.requested_data:
5033       oob_support = dict((name, bool(_SupportsOob(lu.cfg, node)))
5034                          for name, node in all_info.iteritems())
5035     else:
5036       oob_support = None
5037
5038     if query.NQ_GROUP in self.requested_data:
5039       groups = lu.cfg.GetAllNodeGroupsInfo()
5040     else:
5041       groups = {}
5042
5043     return query.NodeQueryData([all_info[name] for name in nodenames],
5044                                live_data, lu.cfg.GetMasterNode(),
5045                                node_to_primary, node_to_secondary, groups,
5046                                oob_support, lu.cfg.GetClusterInfo())
5047
5048
5049 class LUNodeQuery(NoHooksLU):
5050   """Logical unit for querying nodes.
5051
5052   """
5053   # pylint: disable=W0142
5054   REQ_BGL = False
5055
5056   def CheckArguments(self):
5057     self.nq = _NodeQuery(qlang.MakeSimpleFilter("name", self.op.names),
5058                          self.op.output_fields, self.op.use_locking)
5059
5060   def ExpandNames(self):
5061     self.nq.ExpandNames(self)
5062
5063   def DeclareLocks(self, level):
5064     self.nq.DeclareLocks(self, level)
5065
5066   def Exec(self, feedback_fn):
5067     return self.nq.OldStyleQuery(self)
5068
5069
5070 class LUNodeQueryvols(NoHooksLU):
5071   """Logical unit for getting volumes on node(s).
5072
5073   """
5074   REQ_BGL = False
5075   _FIELDS_DYNAMIC = utils.FieldSet("phys", "vg", "name", "size", "instance")
5076   _FIELDS_STATIC = utils.FieldSet("node")
5077
5078   def CheckArguments(self):
5079     _CheckOutputFields(static=self._FIELDS_STATIC,
5080                        dynamic=self._FIELDS_DYNAMIC,
5081                        selected=self.op.output_fields)
5082
5083   def ExpandNames(self):
5084     self.share_locks = _ShareAll()
5085     self.needed_locks = {}
5086
5087     if not self.op.nodes:
5088       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5089     else:
5090       self.needed_locks[locking.LEVEL_NODE] = \
5091         _GetWantedNodes(self, self.op.nodes)
5092
5093   def Exec(self, feedback_fn):
5094     """Computes the list of nodes and their attributes.
5095
5096     """
5097     nodenames = self.owned_locks(locking.LEVEL_NODE)
5098     volumes = self.rpc.call_node_volumes(nodenames)
5099
5100     ilist = self.cfg.GetAllInstancesInfo()
5101     vol2inst = _MapInstanceDisksToNodes(ilist.values())
5102
5103     output = []
5104     for node in nodenames:
5105       nresult = volumes[node]
5106       if nresult.offline:
5107         continue
5108       msg = nresult.fail_msg
5109       if msg:
5110         self.LogWarning("Can't compute volume data on node %s: %s", node, msg)
5111         continue
5112
5113       node_vols = sorted(nresult.payload,
5114                          key=operator.itemgetter("dev"))
5115
5116       for vol in node_vols:
5117         node_output = []
5118         for field in self.op.output_fields:
5119           if field == "node":
5120             val = node
5121           elif field == "phys":
5122             val = vol["dev"]
5123           elif field == "vg":
5124             val = vol["vg"]
5125           elif field == "name":
5126             val = vol["name"]
5127           elif field == "size":
5128             val = int(float(vol["size"]))
5129           elif field == "instance":
5130             val = vol2inst.get((node, vol["vg"] + "/" + vol["name"]), "-")
5131           else:
5132             raise errors.ParameterError(field)
5133           node_output.append(str(val))
5134
5135         output.append(node_output)
5136
5137     return output
5138
5139
5140 class LUNodeQueryStorage(NoHooksLU):
5141   """Logical unit for getting information on storage units on node(s).
5142
5143   """
5144   _FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
5145   REQ_BGL = False
5146
5147   def CheckArguments(self):
5148     _CheckOutputFields(static=self._FIELDS_STATIC,
5149                        dynamic=utils.FieldSet(*constants.VALID_STORAGE_FIELDS),
5150                        selected=self.op.output_fields)
5151
5152   def ExpandNames(self):
5153     self.share_locks = _ShareAll()
5154     self.needed_locks = {}
5155
5156     if self.op.nodes:
5157       self.needed_locks[locking.LEVEL_NODE] = \
5158         _GetWantedNodes(self, self.op.nodes)
5159     else:
5160       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5161
5162   def Exec(self, feedback_fn):
5163     """Computes the list of nodes and their attributes.
5164
5165     """
5166     self.nodes = self.owned_locks(locking.LEVEL_NODE)
5167
5168     # Always get name to sort by
5169     if constants.SF_NAME in self.op.output_fields:
5170       fields = self.op.output_fields[:]
5171     else:
5172       fields = [constants.SF_NAME] + self.op.output_fields
5173
5174     # Never ask for node or type as it's only known to the LU
5175     for extra in [constants.SF_NODE, constants.SF_TYPE]:
5176       while extra in fields:
5177         fields.remove(extra)
5178
5179     field_idx = dict([(name, idx) for (idx, name) in enumerate(fields)])
5180     name_idx = field_idx[constants.SF_NAME]
5181
5182     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
5183     data = self.rpc.call_storage_list(self.nodes,
5184                                       self.op.storage_type, st_args,
5185                                       self.op.name, fields)
5186
5187     result = []
5188
5189     for node in utils.NiceSort(self.nodes):
5190       nresult = data[node]
5191       if nresult.offline:
5192         continue
5193
5194       msg = nresult.fail_msg
5195       if msg:
5196         self.LogWarning("Can't get storage data from node %s: %s", node, msg)
5197         continue
5198
5199       rows = dict([(row[name_idx], row) for row in nresult.payload])
5200
5201       for name in utils.NiceSort(rows.keys()):
5202         row = rows[name]
5203
5204         out = []
5205
5206         for field in self.op.output_fields:
5207           if field == constants.SF_NODE:
5208             val = node
5209           elif field == constants.SF_TYPE:
5210             val = self.op.storage_type
5211           elif field in field_idx:
5212             val = row[field_idx[field]]
5213           else:
5214             raise errors.ParameterError(field)
5215
5216           out.append(val)
5217
5218         result.append(out)
5219
5220     return result
5221
5222
5223 class _InstanceQuery(_QueryBase):
5224   FIELDS = query.INSTANCE_FIELDS
5225
5226   def ExpandNames(self, lu):
5227     lu.needed_locks = {}
5228     lu.share_locks = _ShareAll()
5229
5230     if self.names:
5231       self.wanted = _GetWantedInstances(lu, self.names)
5232     else:
5233       self.wanted = locking.ALL_SET
5234
5235     self.do_locking = (self.use_locking and
5236                        query.IQ_LIVE in self.requested_data)
5237     if self.do_locking:
5238       lu.needed_locks[locking.LEVEL_INSTANCE] = self.wanted
5239       lu.needed_locks[locking.LEVEL_NODEGROUP] = []
5240       lu.needed_locks[locking.LEVEL_NODE] = []
5241       lu.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
5242
5243     self.do_grouplocks = (self.do_locking and
5244                           query.IQ_NODES in self.requested_data)
5245
5246   def DeclareLocks(self, lu, level):
5247     if self.do_locking:
5248       if level == locking.LEVEL_NODEGROUP and self.do_grouplocks:
5249         assert not lu.needed_locks[locking.LEVEL_NODEGROUP]
5250
5251         # Lock all groups used by instances optimistically; this requires going
5252         # via the node before it's locked, requiring verification later on
5253         lu.needed_locks[locking.LEVEL_NODEGROUP] = \
5254           set(group_uuid
5255               for instance_name in lu.owned_locks(locking.LEVEL_INSTANCE)
5256               for group_uuid in lu.cfg.GetInstanceNodeGroups(instance_name))
5257       elif level == locking.LEVEL_NODE:
5258         lu._LockInstancesNodes() # pylint: disable=W0212
5259
5260   @staticmethod
5261   def _CheckGroupLocks(lu):
5262     owned_instances = frozenset(lu.owned_locks(locking.LEVEL_INSTANCE))
5263     owned_groups = frozenset(lu.owned_locks(locking.LEVEL_NODEGROUP))
5264
5265     # Check if node groups for locked instances are still correct
5266     for instance_name in owned_instances:
5267       _CheckInstanceNodeGroups(lu.cfg, instance_name, owned_groups)
5268
5269   def _GetQueryData(self, lu):
5270     """Computes the list of instances and their attributes.
5271
5272     """
5273     if self.do_grouplocks:
5274       self._CheckGroupLocks(lu)
5275
5276     cluster = lu.cfg.GetClusterInfo()
5277     all_info = lu.cfg.GetAllInstancesInfo()
5278
5279     instance_names = self._GetNames(lu, all_info.keys(), locking.LEVEL_INSTANCE)
5280
5281     instance_list = [all_info[name] for name in instance_names]
5282     nodes = frozenset(itertools.chain(*(inst.all_nodes
5283                                         for inst in instance_list)))
5284     hv_list = list(set([inst.hypervisor for inst in instance_list]))
5285     bad_nodes = []
5286     offline_nodes = []
5287     wrongnode_inst = set()
5288
5289     # Gather data as requested
5290     if self.requested_data & set([query.IQ_LIVE, query.IQ_CONSOLE]):
5291       live_data = {}
5292       node_data = lu.rpc.call_all_instances_info(nodes, hv_list)
5293       for name in nodes:
5294         result = node_data[name]
5295         if result.offline:
5296           # offline nodes will be in both lists
5297           assert result.fail_msg
5298           offline_nodes.append(name)
5299         if result.fail_msg:
5300           bad_nodes.append(name)
5301         elif result.payload:
5302           for inst in result.payload:
5303             if inst in all_info:
5304               if all_info[inst].primary_node == name:
5305                 live_data.update(result.payload)
5306               else:
5307                 wrongnode_inst.add(inst)
5308             else:
5309               # orphan instance; we don't list it here as we don't
5310               # handle this case yet in the output of instance listing
5311               logging.warning("Orphan instance '%s' found on node %s",
5312                               inst, name)
5313         # else no instance is alive
5314     else:
5315       live_data = {}
5316
5317     if query.IQ_DISKUSAGE in self.requested_data:
5318       disk_usage = dict((inst.name,
5319                          _ComputeDiskSize(inst.disk_template,
5320                                           [{constants.IDISK_SIZE: disk.size}
5321                                            for disk in inst.disks]))
5322                         for inst in instance_list)
5323     else:
5324       disk_usage = None
5325
5326     if query.IQ_CONSOLE in self.requested_data:
5327       consinfo = {}
5328       for inst in instance_list:
5329         if inst.name in live_data:
5330           # Instance is running
5331           consinfo[inst.name] = _GetInstanceConsole(cluster, inst)
5332         else:
5333           consinfo[inst.name] = None
5334       assert set(consinfo.keys()) == set(instance_names)
5335     else:
5336       consinfo = None
5337
5338     if query.IQ_NODES in self.requested_data:
5339       node_names = set(itertools.chain(*map(operator.attrgetter("all_nodes"),
5340                                             instance_list)))
5341       nodes = dict(lu.cfg.GetMultiNodeInfo(node_names))
5342       groups = dict((uuid, lu.cfg.GetNodeGroup(uuid))
5343                     for uuid in set(map(operator.attrgetter("group"),
5344                                         nodes.values())))
5345     else:
5346       nodes = None
5347       groups = None
5348
5349     return query.InstanceQueryData(instance_list, lu.cfg.GetClusterInfo(),
5350                                    disk_usage, offline_nodes, bad_nodes,
5351                                    live_data, wrongnode_inst, consinfo,
5352                                    nodes, groups)
5353
5354
5355 class LUQuery(NoHooksLU):
5356   """Query for resources/items of a certain kind.
5357
5358   """
5359   # pylint: disable=W0142
5360   REQ_BGL = False
5361
5362   def CheckArguments(self):
5363     qcls = _GetQueryImplementation(self.op.what)
5364
5365     self.impl = qcls(self.op.qfilter, self.op.fields, self.op.use_locking)
5366
5367   def ExpandNames(self):
5368     self.impl.ExpandNames(self)
5369
5370   def DeclareLocks(self, level):
5371     self.impl.DeclareLocks(self, level)
5372
5373   def Exec(self, feedback_fn):
5374     return self.impl.NewStyleQuery(self)
5375
5376
5377 class LUQueryFields(NoHooksLU):
5378   """Query for resources/items of a certain kind.
5379
5380   """
5381   # pylint: disable=W0142
5382   REQ_BGL = False
5383
5384   def CheckArguments(self):
5385     self.qcls = _GetQueryImplementation(self.op.what)
5386
5387   def ExpandNames(self):
5388     self.needed_locks = {}
5389
5390   def Exec(self, feedback_fn):
5391     return query.QueryFields(self.qcls.FIELDS, self.op.fields)
5392
5393
5394 class LUNodeModifyStorage(NoHooksLU):
5395   """Logical unit for modifying a storage volume on a node.
5396
5397   """
5398   REQ_BGL = False
5399
5400   def CheckArguments(self):
5401     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5402
5403     storage_type = self.op.storage_type
5404
5405     try:
5406       modifiable = constants.MODIFIABLE_STORAGE_FIELDS[storage_type]
5407     except KeyError:
5408       raise errors.OpPrereqError("Storage units of type '%s' can not be"
5409                                  " modified" % storage_type,
5410                                  errors.ECODE_INVAL)
5411
5412     diff = set(self.op.changes.keys()) - modifiable
5413     if diff:
5414       raise errors.OpPrereqError("The following fields can not be modified for"
5415                                  " storage units of type '%s': %r" %
5416                                  (storage_type, list(diff)),
5417                                  errors.ECODE_INVAL)
5418
5419   def ExpandNames(self):
5420     self.needed_locks = {
5421       locking.LEVEL_NODE: self.op.node_name,
5422       }
5423
5424   def Exec(self, feedback_fn):
5425     """Computes the list of nodes and their attributes.
5426
5427     """
5428     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
5429     result = self.rpc.call_storage_modify(self.op.node_name,
5430                                           self.op.storage_type, st_args,
5431                                           self.op.name, self.op.changes)
5432     result.Raise("Failed to modify storage unit '%s' on %s" %
5433                  (self.op.name, self.op.node_name))
5434
5435
5436 class LUNodeAdd(LogicalUnit):
5437   """Logical unit for adding node to the cluster.
5438
5439   """
5440   HPATH = "node-add"
5441   HTYPE = constants.HTYPE_NODE
5442   _NFLAGS = ["master_capable", "vm_capable"]
5443
5444   def CheckArguments(self):
5445     self.primary_ip_family = self.cfg.GetPrimaryIPFamily()
5446     # validate/normalize the node name
5447     self.hostname = netutils.GetHostname(name=self.op.node_name,
5448                                          family=self.primary_ip_family)
5449     self.op.node_name = self.hostname.name
5450
5451     if self.op.readd and self.op.node_name == self.cfg.GetMasterNode():
5452       raise errors.OpPrereqError("Cannot readd the master node",
5453                                  errors.ECODE_STATE)
5454
5455     if self.op.readd and self.op.group:
5456       raise errors.OpPrereqError("Cannot pass a node group when a node is"
5457                                  " being readded", errors.ECODE_INVAL)
5458
5459   def BuildHooksEnv(self):
5460     """Build hooks env.
5461
5462     This will run on all nodes before, and on all nodes + the new node after.
5463
5464     """
5465     return {
5466       "OP_TARGET": self.op.node_name,
5467       "NODE_NAME": self.op.node_name,
5468       "NODE_PIP": self.op.primary_ip,
5469       "NODE_SIP": self.op.secondary_ip,
5470       "MASTER_CAPABLE": str(self.op.master_capable),
5471       "VM_CAPABLE": str(self.op.vm_capable),
5472       }
5473
5474   def BuildHooksNodes(self):
5475     """Build hooks nodes.
5476
5477     """
5478     # Exclude added node
5479     pre_nodes = list(set(self.cfg.GetNodeList()) - set([self.op.node_name]))
5480     post_nodes = pre_nodes + [self.op.node_name, ]
5481
5482     return (pre_nodes, post_nodes)
5483
5484   def CheckPrereq(self):
5485     """Check prerequisites.
5486
5487     This checks:
5488      - the new node is not already in the config
5489      - it is resolvable
5490      - its parameters (single/dual homed) matches the cluster
5491
5492     Any errors are signaled by raising errors.OpPrereqError.
5493
5494     """
5495     cfg = self.cfg
5496     hostname = self.hostname
5497     node = hostname.name
5498     primary_ip = self.op.primary_ip = hostname.ip
5499     if self.op.secondary_ip is None:
5500       if self.primary_ip_family == netutils.IP6Address.family:
5501         raise errors.OpPrereqError("When using a IPv6 primary address, a valid"
5502                                    " IPv4 address must be given as secondary",
5503                                    errors.ECODE_INVAL)
5504       self.op.secondary_ip = primary_ip
5505
5506     secondary_ip = self.op.secondary_ip
5507     if not netutils.IP4Address.IsValid(secondary_ip):
5508       raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
5509                                  " address" % secondary_ip, errors.ECODE_INVAL)
5510
5511     node_list = cfg.GetNodeList()
5512     if not self.op.readd and node in node_list:
5513       raise errors.OpPrereqError("Node %s is already in the configuration" %
5514                                  node, errors.ECODE_EXISTS)
5515     elif self.op.readd and node not in node_list:
5516       raise errors.OpPrereqError("Node %s is not in the configuration" % node,
5517                                  errors.ECODE_NOENT)
5518
5519     self.changed_primary_ip = False
5520
5521     for existing_node_name, existing_node in cfg.GetMultiNodeInfo(node_list):
5522       if self.op.readd and node == existing_node_name:
5523         if existing_node.secondary_ip != secondary_ip:
5524           raise errors.OpPrereqError("Readded node doesn't have the same IP"
5525                                      " address configuration as before",
5526                                      errors.ECODE_INVAL)
5527         if existing_node.primary_ip != primary_ip:
5528           self.changed_primary_ip = True
5529
5530         continue
5531
5532       if (existing_node.primary_ip == primary_ip or
5533           existing_node.secondary_ip == primary_ip or
5534           existing_node.primary_ip == secondary_ip or
5535           existing_node.secondary_ip == secondary_ip):
5536         raise errors.OpPrereqError("New node ip address(es) conflict with"
5537                                    " existing node %s" % existing_node.name,
5538                                    errors.ECODE_NOTUNIQUE)
5539
5540     # After this 'if' block, None is no longer a valid value for the
5541     # _capable op attributes
5542     if self.op.readd:
5543       old_node = self.cfg.GetNodeInfo(node)
5544       assert old_node is not None, "Can't retrieve locked node %s" % node
5545       for attr in self._NFLAGS:
5546         if getattr(self.op, attr) is None:
5547           setattr(self.op, attr, getattr(old_node, attr))
5548     else:
5549       for attr in self._NFLAGS:
5550         if getattr(self.op, attr) is None:
5551           setattr(self.op, attr, True)
5552
5553     if self.op.readd and not self.op.vm_capable:
5554       pri, sec = cfg.GetNodeInstances(node)
5555       if pri or sec:
5556         raise errors.OpPrereqError("Node %s being re-added with vm_capable"
5557                                    " flag set to false, but it already holds"
5558                                    " instances" % node,
5559                                    errors.ECODE_STATE)
5560
5561     # check that the type of the node (single versus dual homed) is the
5562     # same as for the master
5563     myself = cfg.GetNodeInfo(self.cfg.GetMasterNode())
5564     master_singlehomed = myself.secondary_ip == myself.primary_ip
5565     newbie_singlehomed = secondary_ip == primary_ip
5566     if master_singlehomed != newbie_singlehomed:
5567       if master_singlehomed:
5568         raise errors.OpPrereqError("The master has no secondary ip but the"
5569                                    " new node has one",
5570                                    errors.ECODE_INVAL)
5571       else:
5572         raise errors.OpPrereqError("The master has a secondary ip but the"
5573                                    " new node doesn't have one",
5574                                    errors.ECODE_INVAL)
5575
5576     # checks reachability
5577     if not netutils.TcpPing(primary_ip, constants.DEFAULT_NODED_PORT):
5578       raise errors.OpPrereqError("Node not reachable by ping",
5579                                  errors.ECODE_ENVIRON)
5580
5581     if not newbie_singlehomed:
5582       # check reachability from my secondary ip to newbie's secondary ip
5583       if not netutils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
5584                            source=myself.secondary_ip):
5585         raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
5586                                    " based ping to node daemon port",
5587                                    errors.ECODE_ENVIRON)
5588
5589     if self.op.readd:
5590       exceptions = [node]
5591     else:
5592       exceptions = []
5593
5594     if self.op.master_capable:
5595       self.master_candidate = _DecideSelfPromotion(self, exceptions=exceptions)
5596     else:
5597       self.master_candidate = False
5598
5599     if self.op.readd:
5600       self.new_node = old_node
5601     else:
5602       node_group = cfg.LookupNodeGroup(self.op.group)
5603       self.new_node = objects.Node(name=node,
5604                                    primary_ip=primary_ip,
5605                                    secondary_ip=secondary_ip,
5606                                    master_candidate=self.master_candidate,
5607                                    offline=False, drained=False,
5608                                    group=node_group)
5609
5610     if self.op.ndparams:
5611       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
5612
5613     if self.op.hv_state:
5614       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None)
5615
5616     if self.op.disk_state:
5617       self.new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state, None)
5618
5619     # TODO: If we need to have multiple DnsOnlyRunner we probably should make
5620     #       it a property on the base class.
5621     result = rpc.DnsOnlyRunner().call_version([node])[node]
5622     result.Raise("Can't get version information from node %s" % node)
5623     if constants.PROTOCOL_VERSION == result.payload:
5624       logging.info("Communication to node %s fine, sw version %s match",
5625                    node, result.payload)
5626     else:
5627       raise errors.OpPrereqError("Version mismatch master version %s,"
5628                                  " node version %s" %
5629                                  (constants.PROTOCOL_VERSION, result.payload),
5630                                  errors.ECODE_ENVIRON)
5631
5632   def Exec(self, feedback_fn):
5633     """Adds the new node to the cluster.
5634
5635     """
5636     new_node = self.new_node
5637     node = new_node.name
5638
5639     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER), \
5640       "Not owning BGL"
5641
5642     # We adding a new node so we assume it's powered
5643     new_node.powered = True
5644
5645     # for re-adds, reset the offline/drained/master-candidate flags;
5646     # we need to reset here, otherwise offline would prevent RPC calls
5647     # later in the procedure; this also means that if the re-add
5648     # fails, we are left with a non-offlined, broken node
5649     if self.op.readd:
5650       new_node.drained = new_node.offline = False # pylint: disable=W0201
5651       self.LogInfo("Readding a node, the offline/drained flags were reset")
5652       # if we demote the node, we do cleanup later in the procedure
5653       new_node.master_candidate = self.master_candidate
5654       if self.changed_primary_ip:
5655         new_node.primary_ip = self.op.primary_ip
5656
5657     # copy the master/vm_capable flags
5658     for attr in self._NFLAGS:
5659       setattr(new_node, attr, getattr(self.op, attr))
5660
5661     # notify the user about any possible mc promotion
5662     if new_node.master_candidate:
5663       self.LogInfo("Node will be a master candidate")
5664
5665     if self.op.ndparams:
5666       new_node.ndparams = self.op.ndparams
5667     else:
5668       new_node.ndparams = {}
5669
5670     if self.op.hv_state:
5671       new_node.hv_state_static = self.new_hv_state
5672
5673     if self.op.disk_state:
5674       new_node.disk_state_static = self.new_disk_state
5675
5676     # Add node to our /etc/hosts, and add key to known_hosts
5677     if self.cfg.GetClusterInfo().modify_etc_hosts:
5678       master_node = self.cfg.GetMasterNode()
5679       result = self.rpc.call_etc_hosts_modify(master_node,
5680                                               constants.ETC_HOSTS_ADD,
5681                                               self.hostname.name,
5682                                               self.hostname.ip)
5683       result.Raise("Can't update hosts file with new host data")
5684
5685     if new_node.secondary_ip != new_node.primary_ip:
5686       _CheckNodeHasSecondaryIP(self, new_node.name, new_node.secondary_ip,
5687                                False)
5688
5689     node_verify_list = [self.cfg.GetMasterNode()]
5690     node_verify_param = {
5691       constants.NV_NODELIST: ([node], {}),
5692       # TODO: do a node-net-test as well?
5693     }
5694
5695     result = self.rpc.call_node_verify(node_verify_list, node_verify_param,
5696                                        self.cfg.GetClusterName())
5697     for verifier in node_verify_list:
5698       result[verifier].Raise("Cannot communicate with node %s" % verifier)
5699       nl_payload = result[verifier].payload[constants.NV_NODELIST]
5700       if nl_payload:
5701         for failed in nl_payload:
5702           feedback_fn("ssh/hostname verification failed"
5703                       " (checking from %s): %s" %
5704                       (verifier, nl_payload[failed]))
5705         raise errors.OpExecError("ssh/hostname verification failed")
5706
5707     if self.op.readd:
5708       _RedistributeAncillaryFiles(self)
5709       self.context.ReaddNode(new_node)
5710       # make sure we redistribute the config
5711       self.cfg.Update(new_node, feedback_fn)
5712       # and make sure the new node will not have old files around
5713       if not new_node.master_candidate:
5714         result = self.rpc.call_node_demote_from_mc(new_node.name)
5715         msg = result.fail_msg
5716         if msg:
5717           self.LogWarning("Node failed to demote itself from master"
5718                           " candidate status: %s" % msg)
5719     else:
5720       _RedistributeAncillaryFiles(self, additional_nodes=[node],
5721                                   additional_vm=self.op.vm_capable)
5722       self.context.AddNode(new_node, self.proc.GetECId())
5723
5724
5725 class LUNodeSetParams(LogicalUnit):
5726   """Modifies the parameters of a node.
5727
5728   @cvar _F2R: a dictionary from tuples of flags (mc, drained, offline)
5729       to the node role (as _ROLE_*)
5730   @cvar _R2F: a dictionary from node role to tuples of flags
5731   @cvar _FLAGS: a list of attribute names corresponding to the flags
5732
5733   """
5734   HPATH = "node-modify"
5735   HTYPE = constants.HTYPE_NODE
5736   REQ_BGL = False
5737   (_ROLE_CANDIDATE, _ROLE_DRAINED, _ROLE_OFFLINE, _ROLE_REGULAR) = range(4)
5738   _F2R = {
5739     (True, False, False): _ROLE_CANDIDATE,
5740     (False, True, False): _ROLE_DRAINED,
5741     (False, False, True): _ROLE_OFFLINE,
5742     (False, False, False): _ROLE_REGULAR,
5743     }
5744   _R2F = dict((v, k) for k, v in _F2R.items())
5745   _FLAGS = ["master_candidate", "drained", "offline"]
5746
5747   def CheckArguments(self):
5748     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5749     all_mods = [self.op.offline, self.op.master_candidate, self.op.drained,
5750                 self.op.master_capable, self.op.vm_capable,
5751                 self.op.secondary_ip, self.op.ndparams, self.op.hv_state,
5752                 self.op.disk_state]
5753     if all_mods.count(None) == len(all_mods):
5754       raise errors.OpPrereqError("Please pass at least one modification",
5755                                  errors.ECODE_INVAL)
5756     if all_mods.count(True) > 1:
5757       raise errors.OpPrereqError("Can't set the node into more than one"
5758                                  " state at the same time",
5759                                  errors.ECODE_INVAL)
5760
5761     # Boolean value that tells us whether we might be demoting from MC
5762     self.might_demote = (self.op.master_candidate == False or
5763                          self.op.offline == True or
5764                          self.op.drained == True or
5765                          self.op.master_capable == False)
5766
5767     if self.op.secondary_ip:
5768       if not netutils.IP4Address.IsValid(self.op.secondary_ip):
5769         raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
5770                                    " address" % self.op.secondary_ip,
5771                                    errors.ECODE_INVAL)
5772
5773     self.lock_all = self.op.auto_promote and self.might_demote
5774     self.lock_instances = self.op.secondary_ip is not None
5775
5776   def _InstanceFilter(self, instance):
5777     """Filter for getting affected instances.
5778
5779     """
5780     return (instance.disk_template in constants.DTS_INT_MIRROR and
5781             self.op.node_name in instance.all_nodes)
5782
5783   def ExpandNames(self):
5784     if self.lock_all:
5785       self.needed_locks = {locking.LEVEL_NODE: locking.ALL_SET}
5786     else:
5787       self.needed_locks = {locking.LEVEL_NODE: self.op.node_name}
5788
5789     # Since modifying a node can have severe effects on currently running
5790     # operations the resource lock is at least acquired in shared mode
5791     self.needed_locks[locking.LEVEL_NODE_RES] = \
5792       self.needed_locks[locking.LEVEL_NODE]
5793
5794     # Get node resource and instance locks in shared mode; they are not used
5795     # for anything but read-only access
5796     self.share_locks[locking.LEVEL_NODE_RES] = 1
5797     self.share_locks[locking.LEVEL_INSTANCE] = 1
5798
5799     if self.lock_instances:
5800       self.needed_locks[locking.LEVEL_INSTANCE] = \
5801         frozenset(self.cfg.GetInstancesInfoByFilter(self._InstanceFilter))
5802
5803   def BuildHooksEnv(self):
5804     """Build hooks env.
5805
5806     This runs on the master node.
5807
5808     """
5809     return {
5810       "OP_TARGET": self.op.node_name,
5811       "MASTER_CANDIDATE": str(self.op.master_candidate),
5812       "OFFLINE": str(self.op.offline),
5813       "DRAINED": str(self.op.drained),
5814       "MASTER_CAPABLE": str(self.op.master_capable),
5815       "VM_CAPABLE": str(self.op.vm_capable),
5816       }
5817
5818   def BuildHooksNodes(self):
5819     """Build hooks nodes.
5820
5821     """
5822     nl = [self.cfg.GetMasterNode(), self.op.node_name]
5823     return (nl, nl)
5824
5825   def CheckPrereq(self):
5826     """Check prerequisites.
5827
5828     This only checks the instance list against the existing names.
5829
5830     """
5831     node = self.node = self.cfg.GetNodeInfo(self.op.node_name)
5832
5833     if self.lock_instances:
5834       affected_instances = \
5835         self.cfg.GetInstancesInfoByFilter(self._InstanceFilter)
5836
5837       # Verify instance locks
5838       owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
5839       wanted_instances = frozenset(affected_instances.keys())
5840       if wanted_instances - owned_instances:
5841         raise errors.OpPrereqError("Instances affected by changing node %s's"
5842                                    " secondary IP address have changed since"
5843                                    " locks were acquired, wanted '%s', have"
5844                                    " '%s'; retry the operation" %
5845                                    (self.op.node_name,
5846                                     utils.CommaJoin(wanted_instances),
5847                                     utils.CommaJoin(owned_instances)),
5848                                    errors.ECODE_STATE)
5849     else:
5850       affected_instances = None
5851
5852     if (self.op.master_candidate is not None or
5853         self.op.drained is not None or
5854         self.op.offline is not None):
5855       # we can't change the master's node flags
5856       if self.op.node_name == self.cfg.GetMasterNode():
5857         raise errors.OpPrereqError("The master role can be changed"
5858                                    " only via master-failover",
5859                                    errors.ECODE_INVAL)
5860
5861     if self.op.master_candidate and not node.master_capable:
5862       raise errors.OpPrereqError("Node %s is not master capable, cannot make"
5863                                  " it a master candidate" % node.name,
5864                                  errors.ECODE_STATE)
5865
5866     if self.op.vm_capable == False:
5867       (ipri, isec) = self.cfg.GetNodeInstances(self.op.node_name)
5868       if ipri or isec:
5869         raise errors.OpPrereqError("Node %s hosts instances, cannot unset"
5870                                    " the vm_capable flag" % node.name,
5871                                    errors.ECODE_STATE)
5872
5873     if node.master_candidate and self.might_demote and not self.lock_all:
5874       assert not self.op.auto_promote, "auto_promote set but lock_all not"
5875       # check if after removing the current node, we're missing master
5876       # candidates
5877       (mc_remaining, mc_should, _) = \
5878           self.cfg.GetMasterCandidateStats(exceptions=[node.name])
5879       if mc_remaining < mc_should:
5880         raise errors.OpPrereqError("Not enough master candidates, please"
5881                                    " pass auto promote option to allow"
5882                                    " promotion", errors.ECODE_STATE)
5883
5884     self.old_flags = old_flags = (node.master_candidate,
5885                                   node.drained, node.offline)
5886     assert old_flags in self._F2R, "Un-handled old flags %s" % str(old_flags)
5887     self.old_role = old_role = self._F2R[old_flags]
5888
5889     # Check for ineffective changes
5890     for attr in self._FLAGS:
5891       if (getattr(self.op, attr) == False and getattr(node, attr) == False):
5892         self.LogInfo("Ignoring request to unset flag %s, already unset", attr)
5893         setattr(self.op, attr, None)
5894
5895     # Past this point, any flag change to False means a transition
5896     # away from the respective state, as only real changes are kept
5897
5898     # TODO: We might query the real power state if it supports OOB
5899     if _SupportsOob(self.cfg, node):
5900       if self.op.offline is False and not (node.powered or
5901                                            self.op.powered == True):
5902         raise errors.OpPrereqError(("Node %s needs to be turned on before its"
5903                                     " offline status can be reset") %
5904                                    self.op.node_name)
5905     elif self.op.powered is not None:
5906       raise errors.OpPrereqError(("Unable to change powered state for node %s"
5907                                   " as it does not support out-of-band"
5908                                   " handling") % self.op.node_name)
5909
5910     # If we're being deofflined/drained, we'll MC ourself if needed
5911     if (self.op.drained == False or self.op.offline == False or
5912         (self.op.master_capable and not node.master_capable)):
5913       if _DecideSelfPromotion(self):
5914         self.op.master_candidate = True
5915         self.LogInfo("Auto-promoting node to master candidate")
5916
5917     # If we're no longer master capable, we'll demote ourselves from MC
5918     if self.op.master_capable == False and node.master_candidate:
5919       self.LogInfo("Demoting from master candidate")
5920       self.op.master_candidate = False
5921
5922     # Compute new role
5923     assert [getattr(self.op, attr) for attr in self._FLAGS].count(True) <= 1
5924     if self.op.master_candidate:
5925       new_role = self._ROLE_CANDIDATE
5926     elif self.op.drained:
5927       new_role = self._ROLE_DRAINED
5928     elif self.op.offline:
5929       new_role = self._ROLE_OFFLINE
5930     elif False in [self.op.master_candidate, self.op.drained, self.op.offline]:
5931       # False is still in new flags, which means we're un-setting (the
5932       # only) True flag
5933       new_role = self._ROLE_REGULAR
5934     else: # no new flags, nothing, keep old role
5935       new_role = old_role
5936
5937     self.new_role = new_role
5938
5939     if old_role == self._ROLE_OFFLINE and new_role != old_role:
5940       # Trying to transition out of offline status
5941       result = self.rpc.call_version([node.name])[node.name]
5942       if result.fail_msg:
5943         raise errors.OpPrereqError("Node %s is being de-offlined but fails"
5944                                    " to report its version: %s" %
5945                                    (node.name, result.fail_msg),
5946                                    errors.ECODE_STATE)
5947       else:
5948         self.LogWarning("Transitioning node from offline to online state"
5949                         " without using re-add. Please make sure the node"
5950                         " is healthy!")
5951
5952     if self.op.secondary_ip:
5953       # Ok even without locking, because this can't be changed by any LU
5954       master = self.cfg.GetNodeInfo(self.cfg.GetMasterNode())
5955       master_singlehomed = master.secondary_ip == master.primary_ip
5956       if master_singlehomed and self.op.secondary_ip:
5957         raise errors.OpPrereqError("Cannot change the secondary ip on a single"
5958                                    " homed cluster", errors.ECODE_INVAL)
5959
5960       assert not (frozenset(affected_instances) -
5961                   self.owned_locks(locking.LEVEL_INSTANCE))
5962
5963       if node.offline:
5964         if affected_instances:
5965           raise errors.OpPrereqError("Cannot change secondary IP address:"
5966                                      " offline node has instances (%s)"
5967                                      " configured to use it" %
5968                                      utils.CommaJoin(affected_instances.keys()))
5969       else:
5970         # On online nodes, check that no instances are running, and that
5971         # the node has the new ip and we can reach it.
5972         for instance in affected_instances.values():
5973           _CheckInstanceState(self, instance, INSTANCE_DOWN,
5974                               msg="cannot change secondary ip")
5975
5976         _CheckNodeHasSecondaryIP(self, node.name, self.op.secondary_ip, True)
5977         if master.name != node.name:
5978           # check reachability from master secondary ip to new secondary ip
5979           if not netutils.TcpPing(self.op.secondary_ip,
5980                                   constants.DEFAULT_NODED_PORT,
5981                                   source=master.secondary_ip):
5982             raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
5983                                        " based ping to node daemon port",
5984                                        errors.ECODE_ENVIRON)
5985
5986     if self.op.ndparams:
5987       new_ndparams = _GetUpdatedParams(self.node.ndparams, self.op.ndparams)
5988       utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES)
5989       self.new_ndparams = new_ndparams
5990
5991     if self.op.hv_state:
5992       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
5993                                                  self.node.hv_state_static)
5994
5995     if self.op.disk_state:
5996       self.new_disk_state = \
5997         _MergeAndVerifyDiskState(self.op.disk_state,
5998                                  self.node.disk_state_static)
5999
6000   def Exec(self, feedback_fn):
6001     """Modifies a node.
6002
6003     """
6004     node = self.node
6005     old_role = self.old_role
6006     new_role = self.new_role
6007
6008     result = []
6009
6010     if self.op.ndparams:
6011       node.ndparams = self.new_ndparams
6012
6013     if self.op.powered is not None:
6014       node.powered = self.op.powered
6015
6016     if self.op.hv_state:
6017       node.hv_state_static = self.new_hv_state
6018
6019     if self.op.disk_state:
6020       node.disk_state_static = self.new_disk_state
6021
6022     for attr in ["master_capable", "vm_capable"]:
6023       val = getattr(self.op, attr)
6024       if val is not None:
6025         setattr(node, attr, val)
6026         result.append((attr, str(val)))
6027
6028     if new_role != old_role:
6029       # Tell the node to demote itself, if no longer MC and not offline
6030       if old_role == self._ROLE_CANDIDATE and new_role != self._ROLE_OFFLINE:
6031         msg = self.rpc.call_node_demote_from_mc(node.name).fail_msg
6032         if msg:
6033           self.LogWarning("Node failed to demote itself: %s", msg)
6034
6035       new_flags = self._R2F[new_role]
6036       for of, nf, desc in zip(self.old_flags, new_flags, self._FLAGS):
6037         if of != nf:
6038           result.append((desc, str(nf)))
6039       (node.master_candidate, node.drained, node.offline) = new_flags
6040
6041       # we locked all nodes, we adjust the CP before updating this node
6042       if self.lock_all:
6043         _AdjustCandidatePool(self, [node.name])
6044
6045     if self.op.secondary_ip:
6046       node.secondary_ip = self.op.secondary_ip
6047       result.append(("secondary_ip", self.op.secondary_ip))
6048
6049     # this will trigger configuration file update, if needed
6050     self.cfg.Update(node, feedback_fn)
6051
6052     # this will trigger job queue propagation or cleanup if the mc
6053     # flag changed
6054     if [old_role, new_role].count(self._ROLE_CANDIDATE) == 1:
6055       self.context.ReaddNode(node)
6056
6057     return result
6058
6059
6060 class LUNodePowercycle(NoHooksLU):
6061   """Powercycles a node.
6062
6063   """
6064   REQ_BGL = False
6065
6066   def CheckArguments(self):
6067     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
6068     if self.op.node_name == self.cfg.GetMasterNode() and not self.op.force:
6069       raise errors.OpPrereqError("The node is the master and the force"
6070                                  " parameter was not set",
6071                                  errors.ECODE_INVAL)
6072
6073   def ExpandNames(self):
6074     """Locking for PowercycleNode.
6075
6076     This is a last-resort option and shouldn't block on other
6077     jobs. Therefore, we grab no locks.
6078
6079     """
6080     self.needed_locks = {}
6081
6082   def Exec(self, feedback_fn):
6083     """Reboots a node.
6084
6085     """
6086     result = self.rpc.call_node_powercycle(self.op.node_name,
6087                                            self.cfg.GetHypervisorType())
6088     result.Raise("Failed to schedule the reboot")
6089     return result.payload
6090
6091
6092 class LUClusterQuery(NoHooksLU):
6093   """Query cluster configuration.
6094
6095   """
6096   REQ_BGL = False
6097
6098   def ExpandNames(self):
6099     self.needed_locks = {}
6100
6101   def Exec(self, feedback_fn):
6102     """Return cluster config.
6103
6104     """
6105     cluster = self.cfg.GetClusterInfo()
6106     os_hvp = {}
6107
6108     # Filter just for enabled hypervisors
6109     for os_name, hv_dict in cluster.os_hvp.items():
6110       os_hvp[os_name] = {}
6111       for hv_name, hv_params in hv_dict.items():
6112         if hv_name in cluster.enabled_hypervisors:
6113           os_hvp[os_name][hv_name] = hv_params
6114
6115     # Convert ip_family to ip_version
6116     primary_ip_version = constants.IP4_VERSION
6117     if cluster.primary_ip_family == netutils.IP6Address.family:
6118       primary_ip_version = constants.IP6_VERSION
6119
6120     result = {
6121       "software_version": constants.RELEASE_VERSION,
6122       "protocol_version": constants.PROTOCOL_VERSION,
6123       "config_version": constants.CONFIG_VERSION,
6124       "os_api_version": max(constants.OS_API_VERSIONS),
6125       "export_version": constants.EXPORT_VERSION,
6126       "architecture": runtime.GetArchInfo(),
6127       "name": cluster.cluster_name,
6128       "master": cluster.master_node,
6129       "default_hypervisor": cluster.primary_hypervisor,
6130       "enabled_hypervisors": cluster.enabled_hypervisors,
6131       "hvparams": dict([(hypervisor_name, cluster.hvparams[hypervisor_name])
6132                         for hypervisor_name in cluster.enabled_hypervisors]),
6133       "os_hvp": os_hvp,
6134       "beparams": cluster.beparams,
6135       "osparams": cluster.osparams,
6136       "ipolicy": cluster.ipolicy,
6137       "nicparams": cluster.nicparams,
6138       "ndparams": cluster.ndparams,
6139       "candidate_pool_size": cluster.candidate_pool_size,
6140       "master_netdev": cluster.master_netdev,
6141       "master_netmask": cluster.master_netmask,
6142       "use_external_mip_script": cluster.use_external_mip_script,
6143       "volume_group_name": cluster.volume_group_name,
6144       "drbd_usermode_helper": cluster.drbd_usermode_helper,
6145       "file_storage_dir": cluster.file_storage_dir,
6146       "shared_file_storage_dir": cluster.shared_file_storage_dir,
6147       "maintain_node_health": cluster.maintain_node_health,
6148       "ctime": cluster.ctime,
6149       "mtime": cluster.mtime,
6150       "uuid": cluster.uuid,
6151       "tags": list(cluster.GetTags()),
6152       "uid_pool": cluster.uid_pool,
6153       "default_iallocator": cluster.default_iallocator,
6154       "reserved_lvs": cluster.reserved_lvs,
6155       "primary_ip_version": primary_ip_version,
6156       "prealloc_wipe_disks": cluster.prealloc_wipe_disks,
6157       "hidden_os": cluster.hidden_os,
6158       "blacklisted_os": cluster.blacklisted_os,
6159       }
6160
6161     return result
6162
6163
6164 class LUClusterConfigQuery(NoHooksLU):
6165   """Return configuration values.
6166
6167   """
6168   REQ_BGL = False
6169
6170   def CheckArguments(self):
6171     self.cq = _ClusterQuery(None, self.op.output_fields, False)
6172
6173   def ExpandNames(self):
6174     self.cq.ExpandNames(self)
6175
6176   def DeclareLocks(self, level):
6177     self.cq.DeclareLocks(self, level)
6178
6179   def Exec(self, feedback_fn):
6180     result = self.cq.OldStyleQuery(self)
6181
6182     assert len(result) == 1
6183
6184     return result[0]
6185
6186
6187 class _ClusterQuery(_QueryBase):
6188   FIELDS = query.CLUSTER_FIELDS
6189
6190   #: Do not sort (there is only one item)
6191   SORT_FIELD = None
6192
6193   def ExpandNames(self, lu):
6194     lu.needed_locks = {}
6195
6196     # The following variables interact with _QueryBase._GetNames
6197     self.wanted = locking.ALL_SET
6198     self.do_locking = self.use_locking
6199
6200     if self.do_locking:
6201       raise errors.OpPrereqError("Can not use locking for cluster queries",
6202                                  errors.ECODE_INVAL)
6203
6204   def DeclareLocks(self, lu, level):
6205     pass
6206
6207   def _GetQueryData(self, lu):
6208     """Computes the list of nodes and their attributes.
6209
6210     """
6211     # Locking is not used
6212     assert not (compat.any(lu.glm.is_owned(level)
6213                            for level in locking.LEVELS
6214                            if level != locking.LEVEL_CLUSTER) or
6215                 self.do_locking or self.use_locking)
6216
6217     if query.CQ_CONFIG in self.requested_data:
6218       cluster = lu.cfg.GetClusterInfo()
6219     else:
6220       cluster = NotImplemented
6221
6222     if query.CQ_QUEUE_DRAINED in self.requested_data:
6223       drain_flag = os.path.exists(constants.JOB_QUEUE_DRAIN_FILE)
6224     else:
6225       drain_flag = NotImplemented
6226
6227     if query.CQ_WATCHER_PAUSE in self.requested_data:
6228       watcher_pause = utils.ReadWatcherPauseFile(constants.WATCHER_PAUSEFILE)
6229     else:
6230       watcher_pause = NotImplemented
6231
6232     return query.ClusterQueryData(cluster, drain_flag, watcher_pause)
6233
6234
6235 class LUInstanceActivateDisks(NoHooksLU):
6236   """Bring up an instance's disks.
6237
6238   """
6239   REQ_BGL = False
6240
6241   def ExpandNames(self):
6242     self._ExpandAndLockInstance()
6243     self.needed_locks[locking.LEVEL_NODE] = []
6244     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6245
6246   def DeclareLocks(self, level):
6247     if level == locking.LEVEL_NODE:
6248       self._LockInstancesNodes()
6249
6250   def CheckPrereq(self):
6251     """Check prerequisites.
6252
6253     This checks that the instance is in the cluster.
6254
6255     """
6256     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6257     assert self.instance is not None, \
6258       "Cannot retrieve locked instance %s" % self.op.instance_name
6259     _CheckNodeOnline(self, self.instance.primary_node)
6260
6261   def Exec(self, feedback_fn):
6262     """Activate the disks.
6263
6264     """
6265     disks_ok, disks_info = \
6266               _AssembleInstanceDisks(self, self.instance,
6267                                      ignore_size=self.op.ignore_size)
6268     if not disks_ok:
6269       raise errors.OpExecError("Cannot activate block devices")
6270
6271     return disks_info
6272
6273
6274 def _AssembleInstanceDisks(lu, instance, disks=None, ignore_secondaries=False,
6275                            ignore_size=False):
6276   """Prepare the block devices for an instance.
6277
6278   This sets up the block devices on all nodes.
6279
6280   @type lu: L{LogicalUnit}
6281   @param lu: the logical unit on whose behalf we execute
6282   @type instance: L{objects.Instance}
6283   @param instance: the instance for whose disks we assemble
6284   @type disks: list of L{objects.Disk} or None
6285   @param disks: which disks to assemble (or all, if None)
6286   @type ignore_secondaries: boolean
6287   @param ignore_secondaries: if true, errors on secondary nodes
6288       won't result in an error return from the function
6289   @type ignore_size: boolean
6290   @param ignore_size: if true, the current known size of the disk
6291       will not be used during the disk activation, useful for cases
6292       when the size is wrong
6293   @return: False if the operation failed, otherwise a list of
6294       (host, instance_visible_name, node_visible_name)
6295       with the mapping from node devices to instance devices
6296
6297   """
6298   device_info = []
6299   disks_ok = True
6300   iname = instance.name
6301   disks = _ExpandCheckDisks(instance, disks)
6302
6303   # With the two passes mechanism we try to reduce the window of
6304   # opportunity for the race condition of switching DRBD to primary
6305   # before handshaking occured, but we do not eliminate it
6306
6307   # The proper fix would be to wait (with some limits) until the
6308   # connection has been made and drbd transitions from WFConnection
6309   # into any other network-connected state (Connected, SyncTarget,
6310   # SyncSource, etc.)
6311
6312   # 1st pass, assemble on all nodes in secondary mode
6313   for idx, inst_disk in enumerate(disks):
6314     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
6315       if ignore_size:
6316         node_disk = node_disk.Copy()
6317         node_disk.UnsetSize()
6318       lu.cfg.SetDiskID(node_disk, node)
6319       result = lu.rpc.call_blockdev_assemble(node, (node_disk, instance), iname,
6320                                              False, idx)
6321       msg = result.fail_msg
6322       if msg:
6323         lu.proc.LogWarning("Could not prepare block device %s on node %s"
6324                            " (is_primary=False, pass=1): %s",
6325                            inst_disk.iv_name, node, msg)
6326         if not ignore_secondaries:
6327           disks_ok = False
6328
6329   # FIXME: race condition on drbd migration to primary
6330
6331   # 2nd pass, do only the primary node
6332   for idx, inst_disk in enumerate(disks):
6333     dev_path = None
6334
6335     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
6336       if node != instance.primary_node:
6337         continue
6338       if ignore_size:
6339         node_disk = node_disk.Copy()
6340         node_disk.UnsetSize()
6341       lu.cfg.SetDiskID(node_disk, node)
6342       result = lu.rpc.call_blockdev_assemble(node, (node_disk, instance), iname,
6343                                              True, idx)
6344       msg = result.fail_msg
6345       if msg:
6346         lu.proc.LogWarning("Could not prepare block device %s on node %s"
6347                            " (is_primary=True, pass=2): %s",
6348                            inst_disk.iv_name, node, msg)
6349         disks_ok = False
6350       else:
6351         dev_path = result.payload
6352
6353     device_info.append((instance.primary_node, inst_disk.iv_name, dev_path))
6354
6355   # leave the disks configured for the primary node
6356   # this is a workaround that would be fixed better by
6357   # improving the logical/physical id handling
6358   for disk in disks:
6359     lu.cfg.SetDiskID(disk, instance.primary_node)
6360
6361   return disks_ok, device_info
6362
6363
6364 def _StartInstanceDisks(lu, instance, force):
6365   """Start the disks of an instance.
6366
6367   """
6368   disks_ok, _ = _AssembleInstanceDisks(lu, instance,
6369                                            ignore_secondaries=force)
6370   if not disks_ok:
6371     _ShutdownInstanceDisks(lu, instance)
6372     if force is not None and not force:
6373       lu.proc.LogWarning("", hint="If the message above refers to a"
6374                          " secondary node,"
6375                          " you can retry the operation using '--force'.")
6376     raise errors.OpExecError("Disk consistency error")
6377
6378
6379 class LUInstanceDeactivateDisks(NoHooksLU):
6380   """Shutdown an instance's disks.
6381
6382   """
6383   REQ_BGL = False
6384
6385   def ExpandNames(self):
6386     self._ExpandAndLockInstance()
6387     self.needed_locks[locking.LEVEL_NODE] = []
6388     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6389
6390   def DeclareLocks(self, level):
6391     if level == locking.LEVEL_NODE:
6392       self._LockInstancesNodes()
6393
6394   def CheckPrereq(self):
6395     """Check prerequisites.
6396
6397     This checks that the instance is in the cluster.
6398
6399     """
6400     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6401     assert self.instance is not None, \
6402       "Cannot retrieve locked instance %s" % self.op.instance_name
6403
6404   def Exec(self, feedback_fn):
6405     """Deactivate the disks
6406
6407     """
6408     instance = self.instance
6409     if self.op.force:
6410       _ShutdownInstanceDisks(self, instance)
6411     else:
6412       _SafeShutdownInstanceDisks(self, instance)
6413
6414
6415 def _SafeShutdownInstanceDisks(lu, instance, disks=None):
6416   """Shutdown block devices of an instance.
6417
6418   This function checks if an instance is running, before calling
6419   _ShutdownInstanceDisks.
6420
6421   """
6422   _CheckInstanceState(lu, instance, INSTANCE_DOWN, msg="cannot shutdown disks")
6423   _ShutdownInstanceDisks(lu, instance, disks=disks)
6424
6425
6426 def _ExpandCheckDisks(instance, disks):
6427   """Return the instance disks selected by the disks list
6428
6429   @type disks: list of L{objects.Disk} or None
6430   @param disks: selected disks
6431   @rtype: list of L{objects.Disk}
6432   @return: selected instance disks to act on
6433
6434   """
6435   if disks is None:
6436     return instance.disks
6437   else:
6438     if not set(disks).issubset(instance.disks):
6439       raise errors.ProgrammerError("Can only act on disks belonging to the"
6440                                    " target instance")
6441     return disks
6442
6443
6444 def _ShutdownInstanceDisks(lu, instance, disks=None, ignore_primary=False):
6445   """Shutdown block devices of an instance.
6446
6447   This does the shutdown on all nodes of the instance.
6448
6449   If the ignore_primary is false, errors on the primary node are
6450   ignored.
6451
6452   """
6453   all_result = True
6454   disks = _ExpandCheckDisks(instance, disks)
6455
6456   for disk in disks:
6457     for node, top_disk in disk.ComputeNodeTree(instance.primary_node):
6458       lu.cfg.SetDiskID(top_disk, node)
6459       result = lu.rpc.call_blockdev_shutdown(node, top_disk)
6460       msg = result.fail_msg
6461       if msg:
6462         lu.LogWarning("Could not shutdown block device %s on node %s: %s",
6463                       disk.iv_name, node, msg)
6464         if ((node == instance.primary_node and not ignore_primary) or
6465             (node != instance.primary_node and not result.offline)):
6466           all_result = False
6467   return all_result
6468
6469
6470 def _CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name):
6471   """Checks if a node has enough free memory.
6472
6473   This function check if a given node has the needed amount of free
6474   memory. In case the node has less memory or we cannot get the
6475   information from the node, this function raise an OpPrereqError
6476   exception.
6477
6478   @type lu: C{LogicalUnit}
6479   @param lu: a logical unit from which we get configuration data
6480   @type node: C{str}
6481   @param node: the node to check
6482   @type reason: C{str}
6483   @param reason: string to use in the error message
6484   @type requested: C{int}
6485   @param requested: the amount of memory in MiB to check for
6486   @type hypervisor_name: C{str}
6487   @param hypervisor_name: the hypervisor to ask for memory stats
6488   @rtype: integer
6489   @return: node current free memory
6490   @raise errors.OpPrereqError: if the node doesn't have enough memory, or
6491       we cannot check the node
6492
6493   """
6494   nodeinfo = lu.rpc.call_node_info([node], None, [hypervisor_name])
6495   nodeinfo[node].Raise("Can't get data from node %s" % node,
6496                        prereq=True, ecode=errors.ECODE_ENVIRON)
6497   (_, _, (hv_info, )) = nodeinfo[node].payload
6498
6499   free_mem = hv_info.get("memory_free", None)
6500   if not isinstance(free_mem, int):
6501     raise errors.OpPrereqError("Can't compute free memory on node %s, result"
6502                                " was '%s'" % (node, free_mem),
6503                                errors.ECODE_ENVIRON)
6504   if requested > free_mem:
6505     raise errors.OpPrereqError("Not enough memory on node %s for %s:"
6506                                " needed %s MiB, available %s MiB" %
6507                                (node, reason, requested, free_mem),
6508                                errors.ECODE_NORES)
6509   return free_mem
6510
6511
6512 def _CheckNodesFreeDiskPerVG(lu, nodenames, req_sizes):
6513   """Checks if nodes have enough free disk space in the all VGs.
6514
6515   This function check if all given nodes have the needed amount of
6516   free disk. In case any node has less disk or we cannot get the
6517   information from the node, this function raise an OpPrereqError
6518   exception.
6519
6520   @type lu: C{LogicalUnit}
6521   @param lu: a logical unit from which we get configuration data
6522   @type nodenames: C{list}
6523   @param nodenames: the list of node names to check
6524   @type req_sizes: C{dict}
6525   @param req_sizes: the hash of vg and corresponding amount of disk in
6526       MiB to check for
6527   @raise errors.OpPrereqError: if the node doesn't have enough disk,
6528       or we cannot check the node
6529
6530   """
6531   for vg, req_size in req_sizes.items():
6532     _CheckNodesFreeDiskOnVG(lu, nodenames, vg, req_size)
6533
6534
6535 def _CheckNodesFreeDiskOnVG(lu, nodenames, vg, requested):
6536   """Checks if nodes have enough free disk space in the specified VG.
6537
6538   This function check if all given nodes have the needed amount of
6539   free disk. In case any node has less disk or we cannot get the
6540   information from the node, this function raise an OpPrereqError
6541   exception.
6542
6543   @type lu: C{LogicalUnit}
6544   @param lu: a logical unit from which we get configuration data
6545   @type nodenames: C{list}
6546   @param nodenames: the list of node names to check
6547   @type vg: C{str}
6548   @param vg: the volume group to check
6549   @type requested: C{int}
6550   @param requested: the amount of disk in MiB to check for
6551   @raise errors.OpPrereqError: if the node doesn't have enough disk,
6552       or we cannot check the node
6553
6554   """
6555   nodeinfo = lu.rpc.call_node_info(nodenames, [vg], None)
6556   for node in nodenames:
6557     info = nodeinfo[node]
6558     info.Raise("Cannot get current information from node %s" % node,
6559                prereq=True, ecode=errors.ECODE_ENVIRON)
6560     (_, (vg_info, ), _) = info.payload
6561     vg_free = vg_info.get("vg_free", None)
6562     if not isinstance(vg_free, int):
6563       raise errors.OpPrereqError("Can't compute free disk space on node"
6564                                  " %s for vg %s, result was '%s'" %
6565                                  (node, vg, vg_free), errors.ECODE_ENVIRON)
6566     if requested > vg_free:
6567       raise errors.OpPrereqError("Not enough disk space on target node %s"
6568                                  " vg %s: required %d MiB, available %d MiB" %
6569                                  (node, vg, requested, vg_free),
6570                                  errors.ECODE_NORES)
6571
6572
6573 def _CheckNodesPhysicalCPUs(lu, nodenames, requested, hypervisor_name):
6574   """Checks if nodes have enough physical CPUs
6575
6576   This function checks if all given nodes have the needed number of
6577   physical CPUs. In case any node has less CPUs or we cannot get the
6578   information from the node, this function raises an OpPrereqError
6579   exception.
6580
6581   @type lu: C{LogicalUnit}
6582   @param lu: a logical unit from which we get configuration data
6583   @type nodenames: C{list}
6584   @param nodenames: the list of node names to check
6585   @type requested: C{int}
6586   @param requested: the minimum acceptable number of physical CPUs
6587   @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
6588       or we cannot check the node
6589
6590   """
6591   nodeinfo = lu.rpc.call_node_info(nodenames, None, [hypervisor_name])
6592   for node in nodenames:
6593     info = nodeinfo[node]
6594     info.Raise("Cannot get current information from node %s" % node,
6595                prereq=True, ecode=errors.ECODE_ENVIRON)
6596     (_, _, (hv_info, )) = info.payload
6597     num_cpus = hv_info.get("cpu_total", None)
6598     if not isinstance(num_cpus, int):
6599       raise errors.OpPrereqError("Can't compute the number of physical CPUs"
6600                                  " on node %s, result was '%s'" %
6601                                  (node, num_cpus), errors.ECODE_ENVIRON)
6602     if requested > num_cpus:
6603       raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
6604                                  "required" % (node, num_cpus, requested),
6605                                  errors.ECODE_NORES)
6606
6607
6608 class LUInstanceStartup(LogicalUnit):
6609   """Starts an instance.
6610
6611   """
6612   HPATH = "instance-start"
6613   HTYPE = constants.HTYPE_INSTANCE
6614   REQ_BGL = False
6615
6616   def CheckArguments(self):
6617     # extra beparams
6618     if self.op.beparams:
6619       # fill the beparams dict
6620       objects.UpgradeBeParams(self.op.beparams)
6621       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
6622
6623   def ExpandNames(self):
6624     self._ExpandAndLockInstance()
6625     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
6626
6627   def DeclareLocks(self, level):
6628     if level == locking.LEVEL_NODE_RES:
6629       self._LockInstancesNodes(primary_only=True, level=locking.LEVEL_NODE_RES)
6630
6631   def BuildHooksEnv(self):
6632     """Build hooks env.
6633
6634     This runs on master, primary and secondary nodes of the instance.
6635
6636     """
6637     env = {
6638       "FORCE": self.op.force,
6639       }
6640
6641     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
6642
6643     return env
6644
6645   def BuildHooksNodes(self):
6646     """Build hooks nodes.
6647
6648     """
6649     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6650     return (nl, nl)
6651
6652   def CheckPrereq(self):
6653     """Check prerequisites.
6654
6655     This checks that the instance is in the cluster.
6656
6657     """
6658     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6659     assert self.instance is not None, \
6660       "Cannot retrieve locked instance %s" % self.op.instance_name
6661
6662     # extra hvparams
6663     if self.op.hvparams:
6664       # check hypervisor parameter syntax (locally)
6665       cluster = self.cfg.GetClusterInfo()
6666       utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
6667       filled_hvp = cluster.FillHV(instance)
6668       filled_hvp.update(self.op.hvparams)
6669       hv_type = hypervisor.GetHypervisor(instance.hypervisor)
6670       hv_type.CheckParameterSyntax(filled_hvp)
6671       _CheckHVParams(self, instance.all_nodes, instance.hypervisor, filled_hvp)
6672
6673     _CheckInstanceState(self, instance, INSTANCE_ONLINE)
6674
6675     self.primary_offline = self.cfg.GetNodeInfo(instance.primary_node).offline
6676
6677     if self.primary_offline and self.op.ignore_offline_nodes:
6678       self.proc.LogWarning("Ignoring offline primary node")
6679
6680       if self.op.hvparams or self.op.beparams:
6681         self.proc.LogWarning("Overridden parameters are ignored")
6682     else:
6683       _CheckNodeOnline(self, instance.primary_node)
6684
6685       bep = self.cfg.GetClusterInfo().FillBE(instance)
6686       bep.update(self.op.beparams)
6687
6688       # check bridges existence
6689       _CheckInstanceBridgesExist(self, instance)
6690
6691       remote_info = self.rpc.call_instance_info(instance.primary_node,
6692                                                 instance.name,
6693                                                 instance.hypervisor)
6694       remote_info.Raise("Error checking node %s" % instance.primary_node,
6695                         prereq=True, ecode=errors.ECODE_ENVIRON)
6696       if not remote_info.payload: # not running already
6697         _CheckNodeFreeMemory(self, instance.primary_node,
6698                              "starting instance %s" % instance.name,
6699                              bep[constants.BE_MINMEM], instance.hypervisor)
6700
6701   def Exec(self, feedback_fn):
6702     """Start the instance.
6703
6704     """
6705     instance = self.instance
6706     force = self.op.force
6707
6708     if not self.op.no_remember:
6709       self.cfg.MarkInstanceUp(instance.name)
6710
6711     if self.primary_offline:
6712       assert self.op.ignore_offline_nodes
6713       self.proc.LogInfo("Primary node offline, marked instance as started")
6714     else:
6715       node_current = instance.primary_node
6716
6717       _StartInstanceDisks(self, instance, force)
6718
6719       result = \
6720         self.rpc.call_instance_start(node_current,
6721                                      (instance, self.op.hvparams,
6722                                       self.op.beparams),
6723                                      self.op.startup_paused)
6724       msg = result.fail_msg
6725       if msg:
6726         _ShutdownInstanceDisks(self, instance)
6727         raise errors.OpExecError("Could not start instance: %s" % msg)
6728
6729
6730 class LUInstanceReboot(LogicalUnit):
6731   """Reboot an instance.
6732
6733   """
6734   HPATH = "instance-reboot"
6735   HTYPE = constants.HTYPE_INSTANCE
6736   REQ_BGL = False
6737
6738   def ExpandNames(self):
6739     self._ExpandAndLockInstance()
6740
6741   def BuildHooksEnv(self):
6742     """Build hooks env.
6743
6744     This runs on master, primary and secondary nodes of the instance.
6745
6746     """
6747     env = {
6748       "IGNORE_SECONDARIES": self.op.ignore_secondaries,
6749       "REBOOT_TYPE": self.op.reboot_type,
6750       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
6751       }
6752
6753     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
6754
6755     return env
6756
6757   def BuildHooksNodes(self):
6758     """Build hooks nodes.
6759
6760     """
6761     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6762     return (nl, nl)
6763
6764   def CheckPrereq(self):
6765     """Check prerequisites.
6766
6767     This checks that the instance is in the cluster.
6768
6769     """
6770     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6771     assert self.instance is not None, \
6772       "Cannot retrieve locked instance %s" % self.op.instance_name
6773     _CheckInstanceState(self, instance, INSTANCE_ONLINE)
6774     _CheckNodeOnline(self, instance.primary_node)
6775
6776     # check bridges existence
6777     _CheckInstanceBridgesExist(self, instance)
6778
6779   def Exec(self, feedback_fn):
6780     """Reboot the instance.
6781
6782     """
6783     instance = self.instance
6784     ignore_secondaries = self.op.ignore_secondaries
6785     reboot_type = self.op.reboot_type
6786
6787     remote_info = self.rpc.call_instance_info(instance.primary_node,
6788                                               instance.name,
6789                                               instance.hypervisor)
6790     remote_info.Raise("Error checking node %s" % instance.primary_node)
6791     instance_running = bool(remote_info.payload)
6792
6793     node_current = instance.primary_node
6794
6795     if instance_running and reboot_type in [constants.INSTANCE_REBOOT_SOFT,
6796                                             constants.INSTANCE_REBOOT_HARD]:
6797       for disk in instance.disks:
6798         self.cfg.SetDiskID(disk, node_current)
6799       result = self.rpc.call_instance_reboot(node_current, instance,
6800                                              reboot_type,
6801                                              self.op.shutdown_timeout)
6802       result.Raise("Could not reboot instance")
6803     else:
6804       if instance_running:
6805         result = self.rpc.call_instance_shutdown(node_current, instance,
6806                                                  self.op.shutdown_timeout)
6807         result.Raise("Could not shutdown instance for full reboot")
6808         _ShutdownInstanceDisks(self, instance)
6809       else:
6810         self.LogInfo("Instance %s was already stopped, starting now",
6811                      instance.name)
6812       _StartInstanceDisks(self, instance, ignore_secondaries)
6813       result = self.rpc.call_instance_start(node_current,
6814                                             (instance, None, None), False)
6815       msg = result.fail_msg
6816       if msg:
6817         _ShutdownInstanceDisks(self, instance)
6818         raise errors.OpExecError("Could not start instance for"
6819                                  " full reboot: %s" % msg)
6820
6821     self.cfg.MarkInstanceUp(instance.name)
6822
6823
6824 class LUInstanceShutdown(LogicalUnit):
6825   """Shutdown an instance.
6826
6827   """
6828   HPATH = "instance-stop"
6829   HTYPE = constants.HTYPE_INSTANCE
6830   REQ_BGL = False
6831
6832   def ExpandNames(self):
6833     self._ExpandAndLockInstance()
6834
6835   def BuildHooksEnv(self):
6836     """Build hooks env.
6837
6838     This runs on master, primary and secondary nodes of the instance.
6839
6840     """
6841     env = _BuildInstanceHookEnvByObject(self, self.instance)
6842     env["TIMEOUT"] = self.op.timeout
6843     return env
6844
6845   def BuildHooksNodes(self):
6846     """Build hooks nodes.
6847
6848     """
6849     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6850     return (nl, nl)
6851
6852   def CheckPrereq(self):
6853     """Check prerequisites.
6854
6855     This checks that the instance is in the cluster.
6856
6857     """
6858     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6859     assert self.instance is not None, \
6860       "Cannot retrieve locked instance %s" % self.op.instance_name
6861
6862     _CheckInstanceState(self, self.instance, INSTANCE_ONLINE)
6863
6864     self.primary_offline = \
6865       self.cfg.GetNodeInfo(self.instance.primary_node).offline
6866
6867     if self.primary_offline and self.op.ignore_offline_nodes:
6868       self.proc.LogWarning("Ignoring offline primary node")
6869     else:
6870       _CheckNodeOnline(self, self.instance.primary_node)
6871
6872   def Exec(self, feedback_fn):
6873     """Shutdown the instance.
6874
6875     """
6876     instance = self.instance
6877     node_current = instance.primary_node
6878     timeout = self.op.timeout
6879
6880     if not self.op.no_remember:
6881       self.cfg.MarkInstanceDown(instance.name)
6882
6883     if self.primary_offline:
6884       assert self.op.ignore_offline_nodes
6885       self.proc.LogInfo("Primary node offline, marked instance as stopped")
6886     else:
6887       result = self.rpc.call_instance_shutdown(node_current, instance, timeout)
6888       msg = result.fail_msg
6889       if msg:
6890         self.proc.LogWarning("Could not shutdown instance: %s" % msg)
6891
6892       _ShutdownInstanceDisks(self, instance)
6893
6894
6895 class LUInstanceReinstall(LogicalUnit):
6896   """Reinstall an instance.
6897
6898   """
6899   HPATH = "instance-reinstall"
6900   HTYPE = constants.HTYPE_INSTANCE
6901   REQ_BGL = False
6902
6903   def ExpandNames(self):
6904     self._ExpandAndLockInstance()
6905
6906   def BuildHooksEnv(self):
6907     """Build hooks env.
6908
6909     This runs on master, primary and secondary nodes of the instance.
6910
6911     """
6912     return _BuildInstanceHookEnvByObject(self, self.instance)
6913
6914   def BuildHooksNodes(self):
6915     """Build hooks nodes.
6916
6917     """
6918     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6919     return (nl, nl)
6920
6921   def CheckPrereq(self):
6922     """Check prerequisites.
6923
6924     This checks that the instance is in the cluster and is not running.
6925
6926     """
6927     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6928     assert instance is not None, \
6929       "Cannot retrieve locked instance %s" % self.op.instance_name
6930     _CheckNodeOnline(self, instance.primary_node, "Instance primary node"
6931                      " offline, cannot reinstall")
6932     for node in instance.secondary_nodes:
6933       _CheckNodeOnline(self, node, "Instance secondary node offline,"
6934                        " cannot reinstall")
6935
6936     if instance.disk_template == constants.DT_DISKLESS:
6937       raise errors.OpPrereqError("Instance '%s' has no disks" %
6938                                  self.op.instance_name,
6939                                  errors.ECODE_INVAL)
6940     _CheckInstanceState(self, instance, INSTANCE_DOWN, msg="cannot reinstall")
6941
6942     if self.op.os_type is not None:
6943       # OS verification
6944       pnode = _ExpandNodeName(self.cfg, instance.primary_node)
6945       _CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant)
6946       instance_os = self.op.os_type
6947     else:
6948       instance_os = instance.os
6949
6950     nodelist = list(instance.all_nodes)
6951
6952     if self.op.osparams:
6953       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
6954       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
6955       self.os_inst = i_osdict # the new dict (without defaults)
6956     else:
6957       self.os_inst = None
6958
6959     self.instance = instance
6960
6961   def Exec(self, feedback_fn):
6962     """Reinstall the instance.
6963
6964     """
6965     inst = self.instance
6966
6967     if self.op.os_type is not None:
6968       feedback_fn("Changing OS to '%s'..." % self.op.os_type)
6969       inst.os = self.op.os_type
6970       # Write to configuration
6971       self.cfg.Update(inst, feedback_fn)
6972
6973     _StartInstanceDisks(self, inst, None)
6974     try:
6975       feedback_fn("Running the instance OS create scripts...")
6976       # FIXME: pass debug option from opcode to backend
6977       result = self.rpc.call_instance_os_add(inst.primary_node,
6978                                              (inst, self.os_inst), True,
6979                                              self.op.debug_level)
6980       result.Raise("Could not install OS for instance %s on node %s" %
6981                    (inst.name, inst.primary_node))
6982     finally:
6983       _ShutdownInstanceDisks(self, inst)
6984
6985
6986 class LUInstanceRecreateDisks(LogicalUnit):
6987   """Recreate an instance's missing disks.
6988
6989   """
6990   HPATH = "instance-recreate-disks"
6991   HTYPE = constants.HTYPE_INSTANCE
6992   REQ_BGL = False
6993
6994   _MODIFYABLE = frozenset([
6995     constants.IDISK_SIZE,
6996     constants.IDISK_MODE,
6997     ])
6998
6999   # New or changed disk parameters may have different semantics
7000   assert constants.IDISK_PARAMS == (_MODIFYABLE | frozenset([
7001     constants.IDISK_ADOPT,
7002
7003     # TODO: Implement support changing VG while recreating
7004     constants.IDISK_VG,
7005     constants.IDISK_METAVG,
7006     ]))
7007
7008   def CheckArguments(self):
7009     if self.op.disks and ht.TPositiveInt(self.op.disks[0]):
7010       # Normalize and convert deprecated list of disk indices
7011       self.op.disks = [(idx, {}) for idx in sorted(frozenset(self.op.disks))]
7012
7013     duplicates = utils.FindDuplicates(map(compat.fst, self.op.disks))
7014     if duplicates:
7015       raise errors.OpPrereqError("Some disks have been specified more than"
7016                                  " once: %s" % utils.CommaJoin(duplicates),
7017                                  errors.ECODE_INVAL)
7018
7019     for (idx, params) in self.op.disks:
7020       utils.ForceDictType(params, constants.IDISK_PARAMS_TYPES)
7021       unsupported = frozenset(params.keys()) - self._MODIFYABLE
7022       if unsupported:
7023         raise errors.OpPrereqError("Parameters for disk %s try to change"
7024                                    " unmodifyable parameter(s): %s" %
7025                                    (idx, utils.CommaJoin(unsupported)),
7026                                    errors.ECODE_INVAL)
7027
7028   def ExpandNames(self):
7029     self._ExpandAndLockInstance()
7030     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
7031     if self.op.nodes:
7032       self.op.nodes = [_ExpandNodeName(self.cfg, n) for n in self.op.nodes]
7033       self.needed_locks[locking.LEVEL_NODE] = list(self.op.nodes)
7034     else:
7035       self.needed_locks[locking.LEVEL_NODE] = []
7036     self.needed_locks[locking.LEVEL_NODE_RES] = []
7037
7038   def DeclareLocks(self, level):
7039     if level == locking.LEVEL_NODE:
7040       # if we replace the nodes, we only need to lock the old primary,
7041       # otherwise we need to lock all nodes for disk re-creation
7042       primary_only = bool(self.op.nodes)
7043       self._LockInstancesNodes(primary_only=primary_only)
7044     elif level == locking.LEVEL_NODE_RES:
7045       # Copy node locks
7046       self.needed_locks[locking.LEVEL_NODE_RES] = \
7047         self.needed_locks[locking.LEVEL_NODE][:]
7048
7049   def BuildHooksEnv(self):
7050     """Build hooks env.
7051
7052     This runs on master, primary and secondary nodes of the instance.
7053
7054     """
7055     return _BuildInstanceHookEnvByObject(self, self.instance)
7056
7057   def BuildHooksNodes(self):
7058     """Build hooks nodes.
7059
7060     """
7061     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7062     return (nl, nl)
7063
7064   def CheckPrereq(self):
7065     """Check prerequisites.
7066
7067     This checks that the instance is in the cluster and is not running.
7068
7069     """
7070     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7071     assert instance is not None, \
7072       "Cannot retrieve locked instance %s" % self.op.instance_name
7073     if self.op.nodes:
7074       if len(self.op.nodes) != len(instance.all_nodes):
7075         raise errors.OpPrereqError("Instance %s currently has %d nodes, but"
7076                                    " %d replacement nodes were specified" %
7077                                    (instance.name, len(instance.all_nodes),
7078                                     len(self.op.nodes)),
7079                                    errors.ECODE_INVAL)
7080       assert instance.disk_template != constants.DT_DRBD8 or \
7081           len(self.op.nodes) == 2
7082       assert instance.disk_template != constants.DT_PLAIN or \
7083           len(self.op.nodes) == 1
7084       primary_node = self.op.nodes[0]
7085     else:
7086       primary_node = instance.primary_node
7087     _CheckNodeOnline(self, primary_node)
7088
7089     if instance.disk_template == constants.DT_DISKLESS:
7090       raise errors.OpPrereqError("Instance '%s' has no disks" %
7091                                  self.op.instance_name, errors.ECODE_INVAL)
7092
7093     # if we replace nodes *and* the old primary is offline, we don't
7094     # check
7095     assert instance.primary_node in self.owned_locks(locking.LEVEL_NODE)
7096     assert instance.primary_node in self.owned_locks(locking.LEVEL_NODE_RES)
7097     old_pnode = self.cfg.GetNodeInfo(instance.primary_node)
7098     if not (self.op.nodes and old_pnode.offline):
7099       _CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
7100                           msg="cannot recreate disks")
7101
7102     if self.op.disks:
7103       self.disks = dict(self.op.disks)
7104     else:
7105       self.disks = dict((idx, {}) for idx in range(len(instance.disks)))
7106
7107     maxidx = max(self.disks.keys())
7108     if maxidx >= len(instance.disks):
7109       raise errors.OpPrereqError("Invalid disk index '%s'" % maxidx,
7110                                  errors.ECODE_INVAL)
7111
7112     if (self.op.nodes and
7113         sorted(self.disks.keys()) != range(len(instance.disks))):
7114       raise errors.OpPrereqError("Can't recreate disks partially and"
7115                                  " change the nodes at the same time",
7116                                  errors.ECODE_INVAL)
7117
7118     self.instance = instance
7119
7120   def Exec(self, feedback_fn):
7121     """Recreate the disks.
7122
7123     """
7124     instance = self.instance
7125
7126     assert (self.owned_locks(locking.LEVEL_NODE) ==
7127             self.owned_locks(locking.LEVEL_NODE_RES))
7128
7129     to_skip = []
7130     mods = [] # keeps track of needed changes
7131
7132     for idx, disk in enumerate(instance.disks):
7133       try:
7134         changes = self.disks[idx]
7135       except KeyError:
7136         # Disk should not be recreated
7137         to_skip.append(idx)
7138         continue
7139
7140       # update secondaries for disks, if needed
7141       if self.op.nodes and disk.dev_type == constants.LD_DRBD8:
7142         # need to update the nodes and minors
7143         assert len(self.op.nodes) == 2
7144         assert len(disk.logical_id) == 6 # otherwise disk internals
7145                                          # have changed
7146         (_, _, old_port, _, _, old_secret) = disk.logical_id
7147         new_minors = self.cfg.AllocateDRBDMinor(self.op.nodes, instance.name)
7148         new_id = (self.op.nodes[0], self.op.nodes[1], old_port,
7149                   new_minors[0], new_minors[1], old_secret)
7150         assert len(disk.logical_id) == len(new_id)
7151       else:
7152         new_id = None
7153
7154       mods.append((idx, new_id, changes))
7155
7156     # now that we have passed all asserts above, we can apply the mods
7157     # in a single run (to avoid partial changes)
7158     for idx, new_id, changes in mods:
7159       disk = instance.disks[idx]
7160       if new_id is not None:
7161         assert disk.dev_type == constants.LD_DRBD8
7162         disk.logical_id = new_id
7163       if changes:
7164         disk.Update(size=changes.get(constants.IDISK_SIZE, None),
7165                     mode=changes.get(constants.IDISK_MODE, None))
7166
7167     # change primary node, if needed
7168     if self.op.nodes:
7169       instance.primary_node = self.op.nodes[0]
7170       self.LogWarning("Changing the instance's nodes, you will have to"
7171                       " remove any disks left on the older nodes manually")
7172
7173     if self.op.nodes:
7174       self.cfg.Update(instance, feedback_fn)
7175
7176     _CreateDisks(self, instance, to_skip=to_skip)
7177
7178
7179 class LUInstanceRename(LogicalUnit):
7180   """Rename an instance.
7181
7182   """
7183   HPATH = "instance-rename"
7184   HTYPE = constants.HTYPE_INSTANCE
7185
7186   def CheckArguments(self):
7187     """Check arguments.
7188
7189     """
7190     if self.op.ip_check and not self.op.name_check:
7191       # TODO: make the ip check more flexible and not depend on the name check
7192       raise errors.OpPrereqError("IP address check requires a name check",
7193                                  errors.ECODE_INVAL)
7194
7195   def BuildHooksEnv(self):
7196     """Build hooks env.
7197
7198     This runs on master, primary and secondary nodes of the instance.
7199
7200     """
7201     env = _BuildInstanceHookEnvByObject(self, self.instance)
7202     env["INSTANCE_NEW_NAME"] = self.op.new_name
7203     return env
7204
7205   def BuildHooksNodes(self):
7206     """Build hooks nodes.
7207
7208     """
7209     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7210     return (nl, nl)
7211
7212   def CheckPrereq(self):
7213     """Check prerequisites.
7214
7215     This checks that the instance is in the cluster and is not running.
7216
7217     """
7218     self.op.instance_name = _ExpandInstanceName(self.cfg,
7219                                                 self.op.instance_name)
7220     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7221     assert instance is not None
7222     _CheckNodeOnline(self, instance.primary_node)
7223     _CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
7224                         msg="cannot rename")
7225     self.instance = instance
7226
7227     new_name = self.op.new_name
7228     if self.op.name_check:
7229       hostname = netutils.GetHostname(name=new_name)
7230       if hostname.name != new_name:
7231         self.LogInfo("Resolved given name '%s' to '%s'", new_name,
7232                      hostname.name)
7233       if not utils.MatchNameComponent(self.op.new_name, [hostname.name]):
7234         raise errors.OpPrereqError(("Resolved hostname '%s' does not look the"
7235                                     " same as given hostname '%s'") %
7236                                     (hostname.name, self.op.new_name),
7237                                     errors.ECODE_INVAL)
7238       new_name = self.op.new_name = hostname.name
7239       if (self.op.ip_check and
7240           netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
7241         raise errors.OpPrereqError("IP %s of instance %s already in use" %
7242                                    (hostname.ip, new_name),
7243                                    errors.ECODE_NOTUNIQUE)
7244
7245     instance_list = self.cfg.GetInstanceList()
7246     if new_name in instance_list and new_name != instance.name:
7247       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
7248                                  new_name, errors.ECODE_EXISTS)
7249
7250   def Exec(self, feedback_fn):
7251     """Rename the instance.
7252
7253     """
7254     inst = self.instance
7255     old_name = inst.name
7256
7257     rename_file_storage = False
7258     if (inst.disk_template in constants.DTS_FILEBASED and
7259         self.op.new_name != inst.name):
7260       old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
7261       rename_file_storage = True
7262
7263     self.cfg.RenameInstance(inst.name, self.op.new_name)
7264     # Change the instance lock. This is definitely safe while we hold the BGL.
7265     # Otherwise the new lock would have to be added in acquired mode.
7266     assert self.REQ_BGL
7267     self.glm.remove(locking.LEVEL_INSTANCE, old_name)
7268     self.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
7269
7270     # re-read the instance from the configuration after rename
7271     inst = self.cfg.GetInstanceInfo(self.op.new_name)
7272
7273     if rename_file_storage:
7274       new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
7275       result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
7276                                                      old_file_storage_dir,
7277                                                      new_file_storage_dir)
7278       result.Raise("Could not rename on node %s directory '%s' to '%s'"
7279                    " (but the instance has been renamed in Ganeti)" %
7280                    (inst.primary_node, old_file_storage_dir,
7281                     new_file_storage_dir))
7282
7283     _StartInstanceDisks(self, inst, None)
7284     try:
7285       result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
7286                                                  old_name, self.op.debug_level)
7287       msg = result.fail_msg
7288       if msg:
7289         msg = ("Could not run OS rename script for instance %s on node %s"
7290                " (but the instance has been renamed in Ganeti): %s" %
7291                (inst.name, inst.primary_node, msg))
7292         self.proc.LogWarning(msg)
7293     finally:
7294       _ShutdownInstanceDisks(self, inst)
7295
7296     return inst.name
7297
7298
7299 class LUInstanceRemove(LogicalUnit):
7300   """Remove an instance.
7301
7302   """
7303   HPATH = "instance-remove"
7304   HTYPE = constants.HTYPE_INSTANCE
7305   REQ_BGL = False
7306
7307   def ExpandNames(self):
7308     self._ExpandAndLockInstance()
7309     self.needed_locks[locking.LEVEL_NODE] = []
7310     self.needed_locks[locking.LEVEL_NODE_RES] = []
7311     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7312
7313   def DeclareLocks(self, level):
7314     if level == locking.LEVEL_NODE:
7315       self._LockInstancesNodes()
7316     elif level == locking.LEVEL_NODE_RES:
7317       # Copy node locks
7318       self.needed_locks[locking.LEVEL_NODE_RES] = \
7319         self.needed_locks[locking.LEVEL_NODE][:]
7320
7321   def BuildHooksEnv(self):
7322     """Build hooks env.
7323
7324     This runs on master, primary and secondary nodes of the instance.
7325
7326     """
7327     env = _BuildInstanceHookEnvByObject(self, self.instance)
7328     env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
7329     return env
7330
7331   def BuildHooksNodes(self):
7332     """Build hooks nodes.
7333
7334     """
7335     nl = [self.cfg.GetMasterNode()]
7336     nl_post = list(self.instance.all_nodes) + nl
7337     return (nl, nl_post)
7338
7339   def CheckPrereq(self):
7340     """Check prerequisites.
7341
7342     This checks that the instance is in the cluster.
7343
7344     """
7345     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7346     assert self.instance is not None, \
7347       "Cannot retrieve locked instance %s" % self.op.instance_name
7348
7349   def Exec(self, feedback_fn):
7350     """Remove the instance.
7351
7352     """
7353     instance = self.instance
7354     logging.info("Shutting down instance %s on node %s",
7355                  instance.name, instance.primary_node)
7356
7357     result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
7358                                              self.op.shutdown_timeout)
7359     msg = result.fail_msg
7360     if msg:
7361       if self.op.ignore_failures:
7362         feedback_fn("Warning: can't shutdown instance: %s" % msg)
7363       else:
7364         raise errors.OpExecError("Could not shutdown instance %s on"
7365                                  " node %s: %s" %
7366                                  (instance.name, instance.primary_node, msg))
7367
7368     assert (self.owned_locks(locking.LEVEL_NODE) ==
7369             self.owned_locks(locking.LEVEL_NODE_RES))
7370     assert not (set(instance.all_nodes) -
7371                 self.owned_locks(locking.LEVEL_NODE)), \
7372       "Not owning correct locks"
7373
7374     _RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
7375
7376
7377 def _RemoveInstance(lu, feedback_fn, instance, ignore_failures):
7378   """Utility function to remove an instance.
7379
7380   """
7381   logging.info("Removing block devices for instance %s", instance.name)
7382
7383   if not _RemoveDisks(lu, instance, ignore_failures=ignore_failures):
7384     if not ignore_failures:
7385       raise errors.OpExecError("Can't remove instance's disks")
7386     feedback_fn("Warning: can't remove instance's disks")
7387
7388   logging.info("Removing instance %s out of cluster config", instance.name)
7389
7390   lu.cfg.RemoveInstance(instance.name)
7391
7392   assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \
7393     "Instance lock removal conflict"
7394
7395   # Remove lock for the instance
7396   lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
7397
7398
7399 class LUInstanceQuery(NoHooksLU):
7400   """Logical unit for querying instances.
7401
7402   """
7403   # pylint: disable=W0142
7404   REQ_BGL = False
7405
7406   def CheckArguments(self):
7407     self.iq = _InstanceQuery(qlang.MakeSimpleFilter("name", self.op.names),
7408                              self.op.output_fields, self.op.use_locking)
7409
7410   def ExpandNames(self):
7411     self.iq.ExpandNames(self)
7412
7413   def DeclareLocks(self, level):
7414     self.iq.DeclareLocks(self, level)
7415
7416   def Exec(self, feedback_fn):
7417     return self.iq.OldStyleQuery(self)
7418
7419
7420 class LUInstanceFailover(LogicalUnit):
7421   """Failover an instance.
7422
7423   """
7424   HPATH = "instance-failover"
7425   HTYPE = constants.HTYPE_INSTANCE
7426   REQ_BGL = False
7427
7428   def CheckArguments(self):
7429     """Check the arguments.
7430
7431     """
7432     self.iallocator = getattr(self.op, "iallocator", None)
7433     self.target_node = getattr(self.op, "target_node", None)
7434
7435   def ExpandNames(self):
7436     self._ExpandAndLockInstance()
7437
7438     if self.op.target_node is not None:
7439       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7440
7441     self.needed_locks[locking.LEVEL_NODE] = []
7442     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7443
7444     self.needed_locks[locking.LEVEL_NODE_RES] = []
7445     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
7446
7447     ignore_consistency = self.op.ignore_consistency
7448     shutdown_timeout = self.op.shutdown_timeout
7449     self._migrater = TLMigrateInstance(self, self.op.instance_name,
7450                                        cleanup=False,
7451                                        failover=True,
7452                                        ignore_consistency=ignore_consistency,
7453                                        shutdown_timeout=shutdown_timeout,
7454                                        ignore_ipolicy=self.op.ignore_ipolicy)
7455     self.tasklets = [self._migrater]
7456
7457   def DeclareLocks(self, level):
7458     if level == locking.LEVEL_NODE:
7459       instance = self.context.cfg.GetInstanceInfo(self.op.instance_name)
7460       if instance.disk_template in constants.DTS_EXT_MIRROR:
7461         if self.op.target_node is None:
7462           self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7463         else:
7464           self.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
7465                                                    self.op.target_node]
7466         del self.recalculate_locks[locking.LEVEL_NODE]
7467       else:
7468         self._LockInstancesNodes()
7469     elif level == locking.LEVEL_NODE_RES:
7470       # Copy node locks
7471       self.needed_locks[locking.LEVEL_NODE_RES] = \
7472         self.needed_locks[locking.LEVEL_NODE][:]
7473
7474   def BuildHooksEnv(self):
7475     """Build hooks env.
7476
7477     This runs on master, primary and secondary nodes of the instance.
7478
7479     """
7480     instance = self._migrater.instance
7481     source_node = instance.primary_node
7482     target_node = self.op.target_node
7483     env = {
7484       "IGNORE_CONSISTENCY": self.op.ignore_consistency,
7485       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
7486       "OLD_PRIMARY": source_node,
7487       "NEW_PRIMARY": target_node,
7488       }
7489
7490     if instance.disk_template in constants.DTS_INT_MIRROR:
7491       env["OLD_SECONDARY"] = instance.secondary_nodes[0]
7492       env["NEW_SECONDARY"] = source_node
7493     else:
7494       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = ""
7495
7496     env.update(_BuildInstanceHookEnvByObject(self, instance))
7497
7498     return env
7499
7500   def BuildHooksNodes(self):
7501     """Build hooks nodes.
7502
7503     """
7504     instance = self._migrater.instance
7505     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
7506     return (nl, nl + [instance.primary_node])
7507
7508
7509 class LUInstanceMigrate(LogicalUnit):
7510   """Migrate an instance.
7511
7512   This is migration without shutting down, compared to the failover,
7513   which is done with shutdown.
7514
7515   """
7516   HPATH = "instance-migrate"
7517   HTYPE = constants.HTYPE_INSTANCE
7518   REQ_BGL = False
7519
7520   def ExpandNames(self):
7521     self._ExpandAndLockInstance()
7522
7523     if self.op.target_node is not None:
7524       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7525
7526     self.needed_locks[locking.LEVEL_NODE] = []
7527     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7528
7529     self.needed_locks[locking.LEVEL_NODE] = []
7530     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7531
7532     self._migrater = \
7533       TLMigrateInstance(self, self.op.instance_name,
7534                         cleanup=self.op.cleanup,
7535                         failover=False,
7536                         fallback=self.op.allow_failover,
7537                         allow_runtime_changes=self.op.allow_runtime_changes,
7538                         ignore_ipolicy=self.op.ignore_ipolicy)
7539     self.tasklets = [self._migrater]
7540
7541   def DeclareLocks(self, level):
7542     if level == locking.LEVEL_NODE:
7543       instance = self.context.cfg.GetInstanceInfo(self.op.instance_name)
7544       if instance.disk_template in constants.DTS_EXT_MIRROR:
7545         if self.op.target_node is None:
7546           self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7547         else:
7548           self.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
7549                                                    self.op.target_node]
7550         del self.recalculate_locks[locking.LEVEL_NODE]
7551       else:
7552         self._LockInstancesNodes()
7553     elif level == locking.LEVEL_NODE_RES:
7554       # Copy node locks
7555       self.needed_locks[locking.LEVEL_NODE_RES] = \
7556         self.needed_locks[locking.LEVEL_NODE][:]
7557
7558   def BuildHooksEnv(self):
7559     """Build hooks env.
7560
7561     This runs on master, primary and secondary nodes of the instance.
7562
7563     """
7564     instance = self._migrater.instance
7565     source_node = instance.primary_node
7566     target_node = self.op.target_node
7567     env = _BuildInstanceHookEnvByObject(self, instance)
7568     env.update({
7569       "MIGRATE_LIVE": self._migrater.live,
7570       "MIGRATE_CLEANUP": self.op.cleanup,
7571       "OLD_PRIMARY": source_node,
7572       "NEW_PRIMARY": target_node,
7573       "ALLOW_RUNTIME_CHANGES": self.op.allow_runtime_changes,
7574       })
7575
7576     if instance.disk_template in constants.DTS_INT_MIRROR:
7577       env["OLD_SECONDARY"] = target_node
7578       env["NEW_SECONDARY"] = source_node
7579     else:
7580       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = None
7581
7582     return env
7583
7584   def BuildHooksNodes(self):
7585     """Build hooks nodes.
7586
7587     """
7588     instance = self._migrater.instance
7589     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
7590     return (nl, nl + [instance.primary_node])
7591
7592
7593 class LUInstanceMove(LogicalUnit):
7594   """Move an instance by data-copying.
7595
7596   """
7597   HPATH = "instance-move"
7598   HTYPE = constants.HTYPE_INSTANCE
7599   REQ_BGL = False
7600
7601   def ExpandNames(self):
7602     self._ExpandAndLockInstance()
7603     target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7604     self.op.target_node = target_node
7605     self.needed_locks[locking.LEVEL_NODE] = [target_node]
7606     self.needed_locks[locking.LEVEL_NODE_RES] = []
7607     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
7608
7609   def DeclareLocks(self, level):
7610     if level == locking.LEVEL_NODE:
7611       self._LockInstancesNodes(primary_only=True)
7612     elif level == locking.LEVEL_NODE_RES:
7613       # Copy node locks
7614       self.needed_locks[locking.LEVEL_NODE_RES] = \
7615         self.needed_locks[locking.LEVEL_NODE][:]
7616
7617   def BuildHooksEnv(self):
7618     """Build hooks env.
7619
7620     This runs on master, primary and secondary nodes of the instance.
7621
7622     """
7623     env = {
7624       "TARGET_NODE": self.op.target_node,
7625       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
7626       }
7627     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
7628     return env
7629
7630   def BuildHooksNodes(self):
7631     """Build hooks nodes.
7632
7633     """
7634     nl = [
7635       self.cfg.GetMasterNode(),
7636       self.instance.primary_node,
7637       self.op.target_node,
7638       ]
7639     return (nl, nl)
7640
7641   def CheckPrereq(self):
7642     """Check prerequisites.
7643
7644     This checks that the instance is in the cluster.
7645
7646     """
7647     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7648     assert self.instance is not None, \
7649       "Cannot retrieve locked instance %s" % self.op.instance_name
7650
7651     node = self.cfg.GetNodeInfo(self.op.target_node)
7652     assert node is not None, \
7653       "Cannot retrieve locked node %s" % self.op.target_node
7654
7655     self.target_node = target_node = node.name
7656
7657     if target_node == instance.primary_node:
7658       raise errors.OpPrereqError("Instance %s is already on the node %s" %
7659                                  (instance.name, target_node),
7660                                  errors.ECODE_STATE)
7661
7662     bep = self.cfg.GetClusterInfo().FillBE(instance)
7663
7664     for idx, dsk in enumerate(instance.disks):
7665       if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
7666         raise errors.OpPrereqError("Instance disk %d has a complex layout,"
7667                                    " cannot copy" % idx, errors.ECODE_STATE)
7668
7669     _CheckNodeOnline(self, target_node)
7670     _CheckNodeNotDrained(self, target_node)
7671     _CheckNodeVmCapable(self, target_node)
7672     ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(),
7673                                      self.cfg.GetNodeGroup(node.group))
7674     _CheckTargetNodeIPolicy(self, ipolicy, instance, node,
7675                             ignore=self.op.ignore_ipolicy)
7676
7677     if instance.admin_state == constants.ADMINST_UP:
7678       # check memory requirements on the secondary node
7679       _CheckNodeFreeMemory(self, target_node, "failing over instance %s" %
7680                            instance.name, bep[constants.BE_MAXMEM],
7681                            instance.hypervisor)
7682     else:
7683       self.LogInfo("Not checking memory on the secondary node as"
7684                    " instance will not be started")
7685
7686     # check bridge existance
7687     _CheckInstanceBridgesExist(self, instance, node=target_node)
7688
7689   def Exec(self, feedback_fn):
7690     """Move an instance.
7691
7692     The move is done by shutting it down on its present node, copying
7693     the data over (slow) and starting it on the new node.
7694
7695     """
7696     instance = self.instance
7697
7698     source_node = instance.primary_node
7699     target_node = self.target_node
7700
7701     self.LogInfo("Shutting down instance %s on source node %s",
7702                  instance.name, source_node)
7703
7704     assert (self.owned_locks(locking.LEVEL_NODE) ==
7705             self.owned_locks(locking.LEVEL_NODE_RES))
7706
7707     result = self.rpc.call_instance_shutdown(source_node, instance,
7708                                              self.op.shutdown_timeout)
7709     msg = result.fail_msg
7710     if msg:
7711       if self.op.ignore_consistency:
7712         self.proc.LogWarning("Could not shutdown instance %s on node %s."
7713                              " Proceeding anyway. Please make sure node"
7714                              " %s is down. Error details: %s",
7715                              instance.name, source_node, source_node, msg)
7716       else:
7717         raise errors.OpExecError("Could not shutdown instance %s on"
7718                                  " node %s: %s" %
7719                                  (instance.name, source_node, msg))
7720
7721     # create the target disks
7722     try:
7723       _CreateDisks(self, instance, target_node=target_node)
7724     except errors.OpExecError:
7725       self.LogWarning("Device creation failed, reverting...")
7726       try:
7727         _RemoveDisks(self, instance, target_node=target_node)
7728       finally:
7729         self.cfg.ReleaseDRBDMinors(instance.name)
7730         raise
7731
7732     cluster_name = self.cfg.GetClusterInfo().cluster_name
7733
7734     errs = []
7735     # activate, get path, copy the data over
7736     for idx, disk in enumerate(instance.disks):
7737       self.LogInfo("Copying data for disk %d", idx)
7738       result = self.rpc.call_blockdev_assemble(target_node, (disk, instance),
7739                                                instance.name, True, idx)
7740       if result.fail_msg:
7741         self.LogWarning("Can't assemble newly created disk %d: %s",
7742                         idx, result.fail_msg)
7743         errs.append(result.fail_msg)
7744         break
7745       dev_path = result.payload
7746       result = self.rpc.call_blockdev_export(source_node, (disk, instance),
7747                                              target_node, dev_path,
7748                                              cluster_name)
7749       if result.fail_msg:
7750         self.LogWarning("Can't copy data over for disk %d: %s",
7751                         idx, result.fail_msg)
7752         errs.append(result.fail_msg)
7753         break
7754
7755     if errs:
7756       self.LogWarning("Some disks failed to copy, aborting")
7757       try:
7758         _RemoveDisks(self, instance, target_node=target_node)
7759       finally:
7760         self.cfg.ReleaseDRBDMinors(instance.name)
7761         raise errors.OpExecError("Errors during disk copy: %s" %
7762                                  (",".join(errs),))
7763
7764     instance.primary_node = target_node
7765     self.cfg.Update(instance, feedback_fn)
7766
7767     self.LogInfo("Removing the disks on the original node")
7768     _RemoveDisks(self, instance, target_node=source_node)
7769
7770     # Only start the instance if it's marked as up
7771     if instance.admin_state == constants.ADMINST_UP:
7772       self.LogInfo("Starting instance %s on node %s",
7773                    instance.name, target_node)
7774
7775       disks_ok, _ = _AssembleInstanceDisks(self, instance,
7776                                            ignore_secondaries=True)
7777       if not disks_ok:
7778         _ShutdownInstanceDisks(self, instance)
7779         raise errors.OpExecError("Can't activate the instance's disks")
7780
7781       result = self.rpc.call_instance_start(target_node,
7782                                             (instance, None, None), False)
7783       msg = result.fail_msg
7784       if msg:
7785         _ShutdownInstanceDisks(self, instance)
7786         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
7787                                  (instance.name, target_node, msg))
7788
7789
7790 class LUNodeMigrate(LogicalUnit):
7791   """Migrate all instances from a node.
7792
7793   """
7794   HPATH = "node-migrate"
7795   HTYPE = constants.HTYPE_NODE
7796   REQ_BGL = False
7797
7798   def CheckArguments(self):
7799     pass
7800
7801   def ExpandNames(self):
7802     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
7803
7804     self.share_locks = _ShareAll()
7805     self.needed_locks = {
7806       locking.LEVEL_NODE: [self.op.node_name],
7807       }
7808
7809   def BuildHooksEnv(self):
7810     """Build hooks env.
7811
7812     This runs on the master, the primary and all the secondaries.
7813
7814     """
7815     return {
7816       "NODE_NAME": self.op.node_name,
7817       "ALLOW_RUNTIME_CHANGES": self.op.allow_runtime_changes,
7818       }
7819
7820   def BuildHooksNodes(self):
7821     """Build hooks nodes.
7822
7823     """
7824     nl = [self.cfg.GetMasterNode()]
7825     return (nl, nl)
7826
7827   def CheckPrereq(self):
7828     pass
7829
7830   def Exec(self, feedback_fn):
7831     # Prepare jobs for migration instances
7832     allow_runtime_changes = self.op.allow_runtime_changes
7833     jobs = [
7834       [opcodes.OpInstanceMigrate(instance_name=inst.name,
7835                                  mode=self.op.mode,
7836                                  live=self.op.live,
7837                                  iallocator=self.op.iallocator,
7838                                  target_node=self.op.target_node,
7839                                  allow_runtime_changes=allow_runtime_changes,
7840                                  ignore_ipolicy=self.op.ignore_ipolicy)]
7841       for inst in _GetNodePrimaryInstances(self.cfg, self.op.node_name)
7842       ]
7843
7844     # TODO: Run iallocator in this opcode and pass correct placement options to
7845     # OpInstanceMigrate. Since other jobs can modify the cluster between
7846     # running the iallocator and the actual migration, a good consistency model
7847     # will have to be found.
7848
7849     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
7850             frozenset([self.op.node_name]))
7851
7852     return ResultWithJobs(jobs)
7853
7854
7855 class TLMigrateInstance(Tasklet):
7856   """Tasklet class for instance migration.
7857
7858   @type live: boolean
7859   @ivar live: whether the migration will be done live or non-live;
7860       this variable is initalized only after CheckPrereq has run
7861   @type cleanup: boolean
7862   @ivar cleanup: Wheater we cleanup from a failed migration
7863   @type iallocator: string
7864   @ivar iallocator: The iallocator used to determine target_node
7865   @type target_node: string
7866   @ivar target_node: If given, the target_node to reallocate the instance to
7867   @type failover: boolean
7868   @ivar failover: Whether operation results in failover or migration
7869   @type fallback: boolean
7870   @ivar fallback: Whether fallback to failover is allowed if migration not
7871                   possible
7872   @type ignore_consistency: boolean
7873   @ivar ignore_consistency: Wheter we should ignore consistency between source
7874                             and target node
7875   @type shutdown_timeout: int
7876   @ivar shutdown_timeout: In case of failover timeout of the shutdown
7877   @type ignore_ipolicy: bool
7878   @ivar ignore_ipolicy: If true, we can ignore instance policy when migrating
7879
7880   """
7881
7882   # Constants
7883   _MIGRATION_POLL_INTERVAL = 1      # seconds
7884   _MIGRATION_FEEDBACK_INTERVAL = 10 # seconds
7885
7886   def __init__(self, lu, instance_name, cleanup=False,
7887                failover=False, fallback=False,
7888                ignore_consistency=False,
7889                allow_runtime_changes=True,
7890                shutdown_timeout=constants.DEFAULT_SHUTDOWN_TIMEOUT,
7891                ignore_ipolicy=False):
7892     """Initializes this class.
7893
7894     """
7895     Tasklet.__init__(self, lu)
7896
7897     # Parameters
7898     self.instance_name = instance_name
7899     self.cleanup = cleanup
7900     self.live = False # will be overridden later
7901     self.failover = failover
7902     self.fallback = fallback
7903     self.ignore_consistency = ignore_consistency
7904     self.shutdown_timeout = shutdown_timeout
7905     self.ignore_ipolicy = ignore_ipolicy
7906     self.allow_runtime_changes = allow_runtime_changes
7907
7908   def CheckPrereq(self):
7909     """Check prerequisites.
7910
7911     This checks that the instance is in the cluster.
7912
7913     """
7914     instance_name = _ExpandInstanceName(self.lu.cfg, self.instance_name)
7915     instance = self.cfg.GetInstanceInfo(instance_name)
7916     assert instance is not None
7917     self.instance = instance
7918     cluster = self.cfg.GetClusterInfo()
7919
7920     if (not self.cleanup and
7921         not instance.admin_state == constants.ADMINST_UP and
7922         not self.failover and self.fallback):
7923       self.lu.LogInfo("Instance is marked down or offline, fallback allowed,"
7924                       " switching to failover")
7925       self.failover = True
7926
7927     if instance.disk_template not in constants.DTS_MIRRORED:
7928       if self.failover:
7929         text = "failovers"
7930       else:
7931         text = "migrations"
7932       raise errors.OpPrereqError("Instance's disk layout '%s' does not allow"
7933                                  " %s" % (instance.disk_template, text),
7934                                  errors.ECODE_STATE)
7935
7936     if instance.disk_template in constants.DTS_EXT_MIRROR:
7937       _CheckIAllocatorOrNode(self.lu, "iallocator", "target_node")
7938
7939       if self.lu.op.iallocator:
7940         self._RunAllocator()
7941       else:
7942         # We set set self.target_node as it is required by
7943         # BuildHooksEnv
7944         self.target_node = self.lu.op.target_node
7945
7946       # Check that the target node is correct in terms of instance policy
7947       nodeinfo = self.cfg.GetNodeInfo(self.target_node)
7948       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
7949       ipolicy = _CalculateGroupIPolicy(cluster, group_info)
7950       _CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo,
7951                               ignore=self.ignore_ipolicy)
7952
7953       # self.target_node is already populated, either directly or by the
7954       # iallocator run
7955       target_node = self.target_node
7956       if self.target_node == instance.primary_node:
7957         raise errors.OpPrereqError("Cannot migrate instance %s"
7958                                    " to its primary (%s)" %
7959                                    (instance.name, instance.primary_node))
7960
7961       if len(self.lu.tasklets) == 1:
7962         # It is safe to release locks only when we're the only tasklet
7963         # in the LU
7964         _ReleaseLocks(self.lu, locking.LEVEL_NODE,
7965                       keep=[instance.primary_node, self.target_node])
7966
7967     else:
7968       secondary_nodes = instance.secondary_nodes
7969       if not secondary_nodes:
7970         raise errors.ConfigurationError("No secondary node but using"
7971                                         " %s disk template" %
7972                                         instance.disk_template)
7973       target_node = secondary_nodes[0]
7974       if self.lu.op.iallocator or (self.lu.op.target_node and
7975                                    self.lu.op.target_node != target_node):
7976         if self.failover:
7977           text = "failed over"
7978         else:
7979           text = "migrated"
7980         raise errors.OpPrereqError("Instances with disk template %s cannot"
7981                                    " be %s to arbitrary nodes"
7982                                    " (neither an iallocator nor a target"
7983                                    " node can be passed)" %
7984                                    (instance.disk_template, text),
7985                                    errors.ECODE_INVAL)
7986       nodeinfo = self.cfg.GetNodeInfo(target_node)
7987       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
7988       ipolicy = _CalculateGroupIPolicy(cluster, group_info)
7989       _CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo,
7990                               ignore=self.ignore_ipolicy)
7991
7992     i_be = cluster.FillBE(instance)
7993
7994     # check memory requirements on the secondary node
7995     if (not self.cleanup and
7996          (not self.failover or instance.admin_state == constants.ADMINST_UP)):
7997       self.tgt_free_mem = _CheckNodeFreeMemory(self.lu, target_node,
7998                                                "migrating instance %s" %
7999                                                instance.name,
8000                                                i_be[constants.BE_MINMEM],
8001                                                instance.hypervisor)
8002     else:
8003       self.lu.LogInfo("Not checking memory on the secondary node as"
8004                       " instance will not be started")
8005
8006     # check if failover must be forced instead of migration
8007     if (not self.cleanup and not self.failover and
8008         i_be[constants.BE_ALWAYS_FAILOVER]):
8009       if self.fallback:
8010         self.lu.LogInfo("Instance configured to always failover; fallback"
8011                         " to failover")
8012         self.failover = True
8013       else:
8014         raise errors.OpPrereqError("This instance has been configured to"
8015                                    " always failover, please allow failover",
8016                                    errors.ECODE_STATE)
8017
8018     # check bridge existance
8019     _CheckInstanceBridgesExist(self.lu, instance, node=target_node)
8020
8021     if not self.cleanup:
8022       _CheckNodeNotDrained(self.lu, target_node)
8023       if not self.failover:
8024         result = self.rpc.call_instance_migratable(instance.primary_node,
8025                                                    instance)
8026         if result.fail_msg and self.fallback:
8027           self.lu.LogInfo("Can't migrate, instance offline, fallback to"
8028                           " failover")
8029           self.failover = True
8030         else:
8031           result.Raise("Can't migrate, please use failover",
8032                        prereq=True, ecode=errors.ECODE_STATE)
8033
8034     assert not (self.failover and self.cleanup)
8035
8036     if not self.failover:
8037       if self.lu.op.live is not None and self.lu.op.mode is not None:
8038         raise errors.OpPrereqError("Only one of the 'live' and 'mode'"
8039                                    " parameters are accepted",
8040                                    errors.ECODE_INVAL)
8041       if self.lu.op.live is not None:
8042         if self.lu.op.live:
8043           self.lu.op.mode = constants.HT_MIGRATION_LIVE
8044         else:
8045           self.lu.op.mode = constants.HT_MIGRATION_NONLIVE
8046         # reset the 'live' parameter to None so that repeated
8047         # invocations of CheckPrereq do not raise an exception
8048         self.lu.op.live = None
8049       elif self.lu.op.mode is None:
8050         # read the default value from the hypervisor
8051         i_hv = cluster.FillHV(self.instance, skip_globals=False)
8052         self.lu.op.mode = i_hv[constants.HV_MIGRATION_MODE]
8053
8054       self.live = self.lu.op.mode == constants.HT_MIGRATION_LIVE
8055     else:
8056       # Failover is never live
8057       self.live = False
8058
8059     if not (self.failover or self.cleanup):
8060       remote_info = self.rpc.call_instance_info(instance.primary_node,
8061                                                 instance.name,
8062                                                 instance.hypervisor)
8063       remote_info.Raise("Error checking instance on node %s" %
8064                         instance.primary_node)
8065       instance_running = bool(remote_info.payload)
8066       if instance_running:
8067         self.current_mem = int(remote_info.payload["memory"])
8068
8069   def _RunAllocator(self):
8070     """Run the allocator based on input opcode.
8071
8072     """
8073     # FIXME: add a self.ignore_ipolicy option
8074     ial = IAllocator(self.cfg, self.rpc,
8075                      mode=constants.IALLOCATOR_MODE_RELOC,
8076                      name=self.instance_name,
8077                      relocate_from=[self.instance.primary_node],
8078                      )
8079
8080     ial.Run(self.lu.op.iallocator)
8081
8082     if not ial.success:
8083       raise errors.OpPrereqError("Can't compute nodes using"
8084                                  " iallocator '%s': %s" %
8085                                  (self.lu.op.iallocator, ial.info),
8086                                  errors.ECODE_NORES)
8087     if len(ial.result) != ial.required_nodes:
8088       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
8089                                  " of nodes (%s), required %s" %
8090                                  (self.lu.op.iallocator, len(ial.result),
8091                                   ial.required_nodes), errors.ECODE_FAULT)
8092     self.target_node = ial.result[0]
8093     self.lu.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
8094                  self.instance_name, self.lu.op.iallocator,
8095                  utils.CommaJoin(ial.result))
8096
8097   def _WaitUntilSync(self):
8098     """Poll with custom rpc for disk sync.
8099
8100     This uses our own step-based rpc call.
8101
8102     """
8103     self.feedback_fn("* wait until resync is done")
8104     all_done = False
8105     while not all_done:
8106       all_done = True
8107       result = self.rpc.call_drbd_wait_sync(self.all_nodes,
8108                                             self.nodes_ip,
8109                                             (self.instance.disks,
8110                                              self.instance))
8111       min_percent = 100
8112       for node, nres in result.items():
8113         nres.Raise("Cannot resync disks on node %s" % node)
8114         node_done, node_percent = nres.payload
8115         all_done = all_done and node_done
8116         if node_percent is not None:
8117           min_percent = min(min_percent, node_percent)
8118       if not all_done:
8119         if min_percent < 100:
8120           self.feedback_fn("   - progress: %.1f%%" % min_percent)
8121         time.sleep(2)
8122
8123   def _EnsureSecondary(self, node):
8124     """Demote a node to secondary.
8125
8126     """
8127     self.feedback_fn("* switching node %s to secondary mode" % node)
8128
8129     for dev in self.instance.disks:
8130       self.cfg.SetDiskID(dev, node)
8131
8132     result = self.rpc.call_blockdev_close(node, self.instance.name,
8133                                           self.instance.disks)
8134     result.Raise("Cannot change disk to secondary on node %s" % node)
8135
8136   def _GoStandalone(self):
8137     """Disconnect from the network.
8138
8139     """
8140     self.feedback_fn("* changing into standalone mode")
8141     result = self.rpc.call_drbd_disconnect_net(self.all_nodes, self.nodes_ip,
8142                                                self.instance.disks)
8143     for node, nres in result.items():
8144       nres.Raise("Cannot disconnect disks node %s" % node)
8145
8146   def _GoReconnect(self, multimaster):
8147     """Reconnect to the network.
8148
8149     """
8150     if multimaster:
8151       msg = "dual-master"
8152     else:
8153       msg = "single-master"
8154     self.feedback_fn("* changing disks into %s mode" % msg)
8155     result = self.rpc.call_drbd_attach_net(self.all_nodes, self.nodes_ip,
8156                                            (self.instance.disks, self.instance),
8157                                            self.instance.name, multimaster)
8158     for node, nres in result.items():
8159       nres.Raise("Cannot change disks config on node %s" % node)
8160
8161   def _ExecCleanup(self):
8162     """Try to cleanup after a failed migration.
8163
8164     The cleanup is done by:
8165       - check that the instance is running only on one node
8166         (and update the config if needed)
8167       - change disks on its secondary node to secondary
8168       - wait until disks are fully synchronized
8169       - disconnect from the network
8170       - change disks into single-master mode
8171       - wait again until disks are fully synchronized
8172
8173     """
8174     instance = self.instance
8175     target_node = self.target_node
8176     source_node = self.source_node
8177
8178     # check running on only one node
8179     self.feedback_fn("* checking where the instance actually runs"
8180                      " (if this hangs, the hypervisor might be in"
8181                      " a bad state)")
8182     ins_l = self.rpc.call_instance_list(self.all_nodes, [instance.hypervisor])
8183     for node, result in ins_l.items():
8184       result.Raise("Can't contact node %s" % node)
8185
8186     runningon_source = instance.name in ins_l[source_node].payload
8187     runningon_target = instance.name in ins_l[target_node].payload
8188
8189     if runningon_source and runningon_target:
8190       raise errors.OpExecError("Instance seems to be running on two nodes,"
8191                                " or the hypervisor is confused; you will have"
8192                                " to ensure manually that it runs only on one"
8193                                " and restart this operation")
8194
8195     if not (runningon_source or runningon_target):
8196       raise errors.OpExecError("Instance does not seem to be running at all;"
8197                                " in this case it's safer to repair by"
8198                                " running 'gnt-instance stop' to ensure disk"
8199                                " shutdown, and then restarting it")
8200
8201     if runningon_target:
8202       # the migration has actually succeeded, we need to update the config
8203       self.feedback_fn("* instance running on secondary node (%s),"
8204                        " updating config" % target_node)
8205       instance.primary_node = target_node
8206       self.cfg.Update(instance, self.feedback_fn)
8207       demoted_node = source_node
8208     else:
8209       self.feedback_fn("* instance confirmed to be running on its"
8210                        " primary node (%s)" % source_node)
8211       demoted_node = target_node
8212
8213     if instance.disk_template in constants.DTS_INT_MIRROR:
8214       self._EnsureSecondary(demoted_node)
8215       try:
8216         self._WaitUntilSync()
8217       except errors.OpExecError:
8218         # we ignore here errors, since if the device is standalone, it
8219         # won't be able to sync
8220         pass
8221       self._GoStandalone()
8222       self._GoReconnect(False)
8223       self._WaitUntilSync()
8224
8225     self.feedback_fn("* done")
8226
8227   def _RevertDiskStatus(self):
8228     """Try to revert the disk status after a failed migration.
8229
8230     """
8231     target_node = self.target_node
8232     if self.instance.disk_template in constants.DTS_EXT_MIRROR:
8233       return
8234
8235     try:
8236       self._EnsureSecondary(target_node)
8237       self._GoStandalone()
8238       self._GoReconnect(False)
8239       self._WaitUntilSync()
8240     except errors.OpExecError, err:
8241       self.lu.LogWarning("Migration failed and I can't reconnect the drives,"
8242                          " please try to recover the instance manually;"
8243                          " error '%s'" % str(err))
8244
8245   def _AbortMigration(self):
8246     """Call the hypervisor code to abort a started migration.
8247
8248     """
8249     instance = self.instance
8250     target_node = self.target_node
8251     source_node = self.source_node
8252     migration_info = self.migration_info
8253
8254     abort_result = self.rpc.call_instance_finalize_migration_dst(target_node,
8255                                                                  instance,
8256                                                                  migration_info,
8257                                                                  False)
8258     abort_msg = abort_result.fail_msg
8259     if abort_msg:
8260       logging.error("Aborting migration failed on target node %s: %s",
8261                     target_node, abort_msg)
8262       # Don't raise an exception here, as we stil have to try to revert the
8263       # disk status, even if this step failed.
8264
8265     abort_result = self.rpc.call_instance_finalize_migration_src(source_node,
8266         instance, False, self.live)
8267     abort_msg = abort_result.fail_msg
8268     if abort_msg:
8269       logging.error("Aborting migration failed on source node %s: %s",
8270                     source_node, abort_msg)
8271
8272   def _ExecMigration(self):
8273     """Migrate an instance.
8274
8275     The migrate is done by:
8276       - change the disks into dual-master mode
8277       - wait until disks are fully synchronized again
8278       - migrate the instance
8279       - change disks on the new secondary node (the old primary) to secondary
8280       - wait until disks are fully synchronized
8281       - change disks into single-master mode
8282
8283     """
8284     instance = self.instance
8285     target_node = self.target_node
8286     source_node = self.source_node
8287
8288     # Check for hypervisor version mismatch and warn the user.
8289     nodeinfo = self.rpc.call_node_info([source_node, target_node],
8290                                        None, [self.instance.hypervisor])
8291     for ninfo in nodeinfo.values():
8292       ninfo.Raise("Unable to retrieve node information from node '%s'" %
8293                   ninfo.node)
8294     (_, _, (src_info, )) = nodeinfo[source_node].payload
8295     (_, _, (dst_info, )) = nodeinfo[target_node].payload
8296
8297     if ((constants.HV_NODEINFO_KEY_VERSION in src_info) and
8298         (constants.HV_NODEINFO_KEY_VERSION in dst_info)):
8299       src_version = src_info[constants.HV_NODEINFO_KEY_VERSION]
8300       dst_version = dst_info[constants.HV_NODEINFO_KEY_VERSION]
8301       if src_version != dst_version:
8302         self.feedback_fn("* warning: hypervisor version mismatch between"
8303                          " source (%s) and target (%s) node" %
8304                          (src_version, dst_version))
8305
8306     self.feedback_fn("* checking disk consistency between source and target")
8307     for (idx, dev) in enumerate(instance.disks):
8308       if not _CheckDiskConsistency(self.lu, instance, dev, target_node, False):
8309         raise errors.OpExecError("Disk %s is degraded or not fully"
8310                                  " synchronized on target node,"
8311                                  " aborting migration" % idx)
8312
8313     if self.current_mem > self.tgt_free_mem:
8314       if not self.allow_runtime_changes:
8315         raise errors.OpExecError("Memory ballooning not allowed and not enough"
8316                                  " free memory to fit instance %s on target"
8317                                  " node %s (have %dMB, need %dMB)" %
8318                                  (instance.name, target_node,
8319                                   self.tgt_free_mem, self.current_mem))
8320       self.feedback_fn("* setting instance memory to %s" % self.tgt_free_mem)
8321       rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
8322                                                      instance,
8323                                                      self.tgt_free_mem)
8324       rpcres.Raise("Cannot modify instance runtime memory")
8325
8326     # First get the migration information from the remote node
8327     result = self.rpc.call_migration_info(source_node, instance)
8328     msg = result.fail_msg
8329     if msg:
8330       log_err = ("Failed fetching source migration information from %s: %s" %
8331                  (source_node, msg))
8332       logging.error(log_err)
8333       raise errors.OpExecError(log_err)
8334
8335     self.migration_info = migration_info = result.payload
8336
8337     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
8338       # Then switch the disks to master/master mode
8339       self._EnsureSecondary(target_node)
8340       self._GoStandalone()
8341       self._GoReconnect(True)
8342       self._WaitUntilSync()
8343
8344     self.feedback_fn("* preparing %s to accept the instance" % target_node)
8345     result = self.rpc.call_accept_instance(target_node,
8346                                            instance,
8347                                            migration_info,
8348                                            self.nodes_ip[target_node])
8349
8350     msg = result.fail_msg
8351     if msg:
8352       logging.error("Instance pre-migration failed, trying to revert"
8353                     " disk status: %s", msg)
8354       self.feedback_fn("Pre-migration failed, aborting")
8355       self._AbortMigration()
8356       self._RevertDiskStatus()
8357       raise errors.OpExecError("Could not pre-migrate instance %s: %s" %
8358                                (instance.name, msg))
8359
8360     self.feedback_fn("* migrating instance to %s" % target_node)
8361     result = self.rpc.call_instance_migrate(source_node, instance,
8362                                             self.nodes_ip[target_node],
8363                                             self.live)
8364     msg = result.fail_msg
8365     if msg:
8366       logging.error("Instance migration failed, trying to revert"
8367                     " disk status: %s", msg)
8368       self.feedback_fn("Migration failed, aborting")
8369       self._AbortMigration()
8370       self._RevertDiskStatus()
8371       raise errors.OpExecError("Could not migrate instance %s: %s" %
8372                                (instance.name, msg))
8373
8374     self.feedback_fn("* starting memory transfer")
8375     last_feedback = time.time()
8376     while True:
8377       result = self.rpc.call_instance_get_migration_status(source_node,
8378                                                            instance)
8379       msg = result.fail_msg
8380       ms = result.payload   # MigrationStatus instance
8381       if msg or (ms.status in constants.HV_MIGRATION_FAILED_STATUSES):
8382         logging.error("Instance migration failed, trying to revert"
8383                       " disk status: %s", msg)
8384         self.feedback_fn("Migration failed, aborting")
8385         self._AbortMigration()
8386         self._RevertDiskStatus()
8387         raise errors.OpExecError("Could not migrate instance %s: %s" %
8388                                  (instance.name, msg))
8389
8390       if result.payload.status != constants.HV_MIGRATION_ACTIVE:
8391         self.feedback_fn("* memory transfer complete")
8392         break
8393
8394       if (utils.TimeoutExpired(last_feedback,
8395                                self._MIGRATION_FEEDBACK_INTERVAL) and
8396           ms.transferred_ram is not None):
8397         mem_progress = 100 * float(ms.transferred_ram) / float(ms.total_ram)
8398         self.feedback_fn("* memory transfer progress: %.2f %%" % mem_progress)
8399         last_feedback = time.time()
8400
8401       time.sleep(self._MIGRATION_POLL_INTERVAL)
8402
8403     result = self.rpc.call_instance_finalize_migration_src(source_node,
8404                                                            instance,
8405                                                            True,
8406                                                            self.live)
8407     msg = result.fail_msg
8408     if msg:
8409       logging.error("Instance migration succeeded, but finalization failed"
8410                     " on the source node: %s", msg)
8411       raise errors.OpExecError("Could not finalize instance migration: %s" %
8412                                msg)
8413
8414     instance.primary_node = target_node
8415
8416     # distribute new instance config to the other nodes
8417     self.cfg.Update(instance, self.feedback_fn)
8418
8419     result = self.rpc.call_instance_finalize_migration_dst(target_node,
8420                                                            instance,
8421                                                            migration_info,
8422                                                            True)
8423     msg = result.fail_msg
8424     if msg:
8425       logging.error("Instance migration succeeded, but finalization failed"
8426                     " on the target node: %s", msg)
8427       raise errors.OpExecError("Could not finalize instance migration: %s" %
8428                                msg)
8429
8430     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
8431       self._EnsureSecondary(source_node)
8432       self._WaitUntilSync()
8433       self._GoStandalone()
8434       self._GoReconnect(False)
8435       self._WaitUntilSync()
8436
8437     # If the instance's disk template is `rbd' and there was a successful
8438     # migration, unmap the device from the source node.
8439     if self.instance.disk_template == constants.DT_RBD:
8440       disks = _ExpandCheckDisks(instance, instance.disks)
8441       self.feedback_fn("* unmapping instance's disks from %s" % source_node)
8442       for disk in disks:
8443         result = self.rpc.call_blockdev_shutdown(source_node, disk)
8444         msg = result.fail_msg
8445         if msg:
8446           logging.error("Migration was successful, but couldn't unmap the"
8447                         " block device %s on source node %s: %s",
8448                         disk.iv_name, source_node, msg)
8449           logging.error("You need to unmap the device %s manually on %s",
8450                         disk.iv_name, source_node)
8451
8452     self.feedback_fn("* done")
8453
8454   def _ExecFailover(self):
8455     """Failover an instance.
8456
8457     The failover is done by shutting it down on its present node and
8458     starting it on the secondary.
8459
8460     """
8461     instance = self.instance
8462     primary_node = self.cfg.GetNodeInfo(instance.primary_node)
8463
8464     source_node = instance.primary_node
8465     target_node = self.target_node
8466
8467     if instance.admin_state == constants.ADMINST_UP:
8468       self.feedback_fn("* checking disk consistency between source and target")
8469       for (idx, dev) in enumerate(instance.disks):
8470         # for drbd, these are drbd over lvm
8471         if not _CheckDiskConsistency(self.lu, instance, dev, target_node,
8472                                      False):
8473           if primary_node.offline:
8474             self.feedback_fn("Node %s is offline, ignoring degraded disk %s on"
8475                              " target node %s" %
8476                              (primary_node.name, idx, target_node))
8477           elif not self.ignore_consistency:
8478             raise errors.OpExecError("Disk %s is degraded on target node,"
8479                                      " aborting failover" % idx)
8480     else:
8481       self.feedback_fn("* not checking disk consistency as instance is not"
8482                        " running")
8483
8484     self.feedback_fn("* shutting down instance on source node")
8485     logging.info("Shutting down instance %s on node %s",
8486                  instance.name, source_node)
8487
8488     result = self.rpc.call_instance_shutdown(source_node, instance,
8489                                              self.shutdown_timeout)
8490     msg = result.fail_msg
8491     if msg:
8492       if self.ignore_consistency or primary_node.offline:
8493         self.lu.LogWarning("Could not shutdown instance %s on node %s,"
8494                            " proceeding anyway; please make sure node"
8495                            " %s is down; error details: %s",
8496                            instance.name, source_node, source_node, msg)
8497       else:
8498         raise errors.OpExecError("Could not shutdown instance %s on"
8499                                  " node %s: %s" %
8500                                  (instance.name, source_node, msg))
8501
8502     self.feedback_fn("* deactivating the instance's disks on source node")
8503     if not _ShutdownInstanceDisks(self.lu, instance, ignore_primary=True):
8504       raise errors.OpExecError("Can't shut down the instance's disks")
8505
8506     instance.primary_node = target_node
8507     # distribute new instance config to the other nodes
8508     self.cfg.Update(instance, self.feedback_fn)
8509
8510     # Only start the instance if it's marked as up
8511     if instance.admin_state == constants.ADMINST_UP:
8512       self.feedback_fn("* activating the instance's disks on target node %s" %
8513                        target_node)
8514       logging.info("Starting instance %s on node %s",
8515                    instance.name, target_node)
8516
8517       disks_ok, _ = _AssembleInstanceDisks(self.lu, instance,
8518                                            ignore_secondaries=True)
8519       if not disks_ok:
8520         _ShutdownInstanceDisks(self.lu, instance)
8521         raise errors.OpExecError("Can't activate the instance's disks")
8522
8523       self.feedback_fn("* starting the instance on the target node %s" %
8524                        target_node)
8525       result = self.rpc.call_instance_start(target_node, (instance, None, None),
8526                                             False)
8527       msg = result.fail_msg
8528       if msg:
8529         _ShutdownInstanceDisks(self.lu, instance)
8530         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
8531                                  (instance.name, target_node, msg))
8532
8533   def Exec(self, feedback_fn):
8534     """Perform the migration.
8535
8536     """
8537     self.feedback_fn = feedback_fn
8538     self.source_node = self.instance.primary_node
8539
8540     # FIXME: if we implement migrate-to-any in DRBD, this needs fixing
8541     if self.instance.disk_template in constants.DTS_INT_MIRROR:
8542       self.target_node = self.instance.secondary_nodes[0]
8543       # Otherwise self.target_node has been populated either
8544       # directly, or through an iallocator.
8545
8546     self.all_nodes = [self.source_node, self.target_node]
8547     self.nodes_ip = dict((name, node.secondary_ip) for (name, node)
8548                          in self.cfg.GetMultiNodeInfo(self.all_nodes))
8549
8550     if self.failover:
8551       feedback_fn("Failover instance %s" % self.instance.name)
8552       self._ExecFailover()
8553     else:
8554       feedback_fn("Migrating instance %s" % self.instance.name)
8555
8556       if self.cleanup:
8557         return self._ExecCleanup()
8558       else:
8559         return self._ExecMigration()
8560
8561
8562 def _CreateBlockDev(lu, node, instance, device, force_create,
8563                     info, force_open):
8564   """Create a tree of block devices on a given node.
8565
8566   If this device type has to be created on secondaries, create it and
8567   all its children.
8568
8569   If not, just recurse to children keeping the same 'force' value.
8570
8571   @param lu: the lu on whose behalf we execute
8572   @param node: the node on which to create the device
8573   @type instance: L{objects.Instance}
8574   @param instance: the instance which owns the device
8575   @type device: L{objects.Disk}
8576   @param device: the device to create
8577   @type force_create: boolean
8578   @param force_create: whether to force creation of this device; this
8579       will be change to True whenever we find a device which has
8580       CreateOnSecondary() attribute
8581   @param info: the extra 'metadata' we should attach to the device
8582       (this will be represented as a LVM tag)
8583   @type force_open: boolean
8584   @param force_open: this parameter will be passes to the
8585       L{backend.BlockdevCreate} function where it specifies
8586       whether we run on primary or not, and it affects both
8587       the child assembly and the device own Open() execution
8588
8589   """
8590   if device.CreateOnSecondary():
8591     force_create = True
8592
8593   if device.children:
8594     for child in device.children:
8595       _CreateBlockDev(lu, node, instance, child, force_create,
8596                       info, force_open)
8597
8598   if not force_create:
8599     return
8600
8601   _CreateSingleBlockDev(lu, node, instance, device, info, force_open)
8602
8603
8604 def _CreateSingleBlockDev(lu, node, instance, device, info, force_open):
8605   """Create a single block device on a given node.
8606
8607   This will not recurse over children of the device, so they must be
8608   created in advance.
8609
8610   @param lu: the lu on whose behalf we execute
8611   @param node: the node on which to create the device
8612   @type instance: L{objects.Instance}
8613   @param instance: the instance which owns the device
8614   @type device: L{objects.Disk}
8615   @param device: the device to create
8616   @param info: the extra 'metadata' we should attach to the device
8617       (this will be represented as a LVM tag)
8618   @type force_open: boolean
8619   @param force_open: this parameter will be passes to the
8620       L{backend.BlockdevCreate} function where it specifies
8621       whether we run on primary or not, and it affects both
8622       the child assembly and the device own Open() execution
8623
8624   """
8625   lu.cfg.SetDiskID(device, node)
8626   result = lu.rpc.call_blockdev_create(node, device, device.size,
8627                                        instance.name, force_open, info)
8628   result.Raise("Can't create block device %s on"
8629                " node %s for instance %s" % (device, node, instance.name))
8630   if device.physical_id is None:
8631     device.physical_id = result.payload
8632
8633
8634 def _GenerateUniqueNames(lu, exts):
8635   """Generate a suitable LV name.
8636
8637   This will generate a logical volume name for the given instance.
8638
8639   """
8640   results = []
8641   for val in exts:
8642     new_id = lu.cfg.GenerateUniqueID(lu.proc.GetECId())
8643     results.append("%s%s" % (new_id, val))
8644   return results
8645
8646
8647 def _GenerateDRBD8Branch(lu, primary, secondary, size, vgnames, names,
8648                          iv_name, p_minor, s_minor, drbd_params, data_params,
8649                          meta_params):
8650   """Generate a drbd8 device complete with its children.
8651
8652   """
8653   assert len(vgnames) == len(names) == 2
8654   port = lu.cfg.AllocatePort()
8655   shared_secret = lu.cfg.GenerateDRBDSecret(lu.proc.GetECId())
8656
8657   dev_data = objects.Disk(dev_type=constants.LD_LV, size=size,
8658                           logical_id=(vgnames[0], names[0]),
8659                           params=data_params)
8660   dev_meta = objects.Disk(dev_type=constants.LD_LV, size=DRBD_META_SIZE,
8661                           logical_id=(vgnames[1], names[1]),
8662                           params=meta_params)
8663   drbd_dev = objects.Disk(dev_type=constants.LD_DRBD8, size=size,
8664                           logical_id=(primary, secondary, port,
8665                                       p_minor, s_minor,
8666                                       shared_secret),
8667                           children=[dev_data, dev_meta],
8668                           iv_name=iv_name, params=drbd_params)
8669   return drbd_dev
8670
8671
8672 _DISK_TEMPLATE_NAME_PREFIX = {
8673   constants.DT_PLAIN: "",
8674   constants.DT_RBD: ".rbd",
8675   }
8676
8677
8678 _DISK_TEMPLATE_DEVICE_TYPE = {
8679   constants.DT_PLAIN: constants.LD_LV,
8680   constants.DT_FILE: constants.LD_FILE,
8681   constants.DT_SHARED_FILE: constants.LD_FILE,
8682   constants.DT_BLOCK: constants.LD_BLOCKDEV,
8683   constants.DT_RBD: constants.LD_RBD,
8684   }
8685
8686
8687 def _GenerateDiskTemplate(lu, template_name, instance_name, primary_node,
8688     secondary_nodes, disk_info, file_storage_dir, file_driver, base_index,
8689     feedback_fn, disk_params,
8690     _req_file_storage=opcodes.RequireFileStorage,
8691     _req_shr_file_storage=opcodes.RequireSharedFileStorage):
8692   """Generate the entire disk layout for a given template type.
8693
8694   """
8695   #TODO: compute space requirements
8696
8697   vgname = lu.cfg.GetVGName()
8698   disk_count = len(disk_info)
8699   disks = []
8700   ld_params = _ComputeLDParams(template_name, disk_params)
8701
8702   if template_name == constants.DT_DISKLESS:
8703     pass
8704   elif template_name == constants.DT_DRBD8:
8705     drbd_params, data_params, meta_params = ld_params
8706     if len(secondary_nodes) != 1:
8707       raise errors.ProgrammerError("Wrong template configuration")
8708     remote_node = secondary_nodes[0]
8709     minors = lu.cfg.AllocateDRBDMinor(
8710       [primary_node, remote_node] * len(disk_info), instance_name)
8711
8712     names = []
8713     for lv_prefix in _GenerateUniqueNames(lu, [".disk%d" % (base_index + i)
8714                                                for i in range(disk_count)]):
8715       names.append(lv_prefix + "_data")
8716       names.append(lv_prefix + "_meta")
8717     for idx, disk in enumerate(disk_info):
8718       disk_index = idx + base_index
8719       drbd_default_metavg = drbd_params[constants.LDP_DEFAULT_METAVG]
8720       data_vg = disk.get(constants.IDISK_VG, vgname)
8721       meta_vg = disk.get(constants.IDISK_METAVG, drbd_default_metavg)
8722       disk_dev = _GenerateDRBD8Branch(lu, primary_node, remote_node,
8723                                       disk[constants.IDISK_SIZE],
8724                                       [data_vg, meta_vg],
8725                                       names[idx * 2:idx * 2 + 2],
8726                                       "disk/%d" % disk_index,
8727                                       minors[idx * 2], minors[idx * 2 + 1],
8728                                       drbd_params, data_params, meta_params)
8729       disk_dev.mode = disk[constants.IDISK_MODE]
8730       disks.append(disk_dev)
8731   else:
8732     if secondary_nodes:
8733       raise errors.ProgrammerError("Wrong template configuration")
8734
8735     if template_name == constants.DT_FILE:
8736       _req_file_storage()
8737     elif template_name == constants.DT_SHARED_FILE:
8738       _req_shr_file_storage()
8739
8740     name_prefix = _DISK_TEMPLATE_NAME_PREFIX.get(template_name, None)
8741     if name_prefix is None:
8742       names = None
8743     else:
8744       names = _GenerateUniqueNames(lu, ["%s.disk%s" %
8745                                         (name_prefix, base_index + i)
8746                                         for i in range(disk_count)])
8747
8748     dev_type = _DISK_TEMPLATE_DEVICE_TYPE[template_name]
8749
8750     if template_name == constants.DT_PLAIN:
8751       def logical_id_fn(idx, _, disk):
8752         vg = disk.get(constants.IDISK_VG, vgname)
8753         return (vg, names[idx])
8754     elif template_name in (constants.DT_FILE, constants.DT_SHARED_FILE):
8755       logical_id_fn = \
8756         lambda _, disk_index, disk: (file_driver,
8757                                      "%s/disk%d" % (file_storage_dir,
8758                                                     disk_index))
8759     elif template_name == constants.DT_BLOCK:
8760       logical_id_fn = \
8761         lambda idx, disk_index, disk: (constants.BLOCKDEV_DRIVER_MANUAL,
8762                                        disk[constants.IDISK_ADOPT])
8763     elif template_name == constants.DT_RBD:
8764       logical_id_fn = lambda idx, _, disk: ("rbd", names[idx])
8765     else:
8766       raise errors.ProgrammerError("Unknown disk template '%s'" % template_name)
8767
8768     for idx, disk in enumerate(disk_info):
8769       disk_index = idx + base_index
8770       size = disk[constants.IDISK_SIZE]
8771       feedback_fn("* disk %s, size %s" %
8772                   (disk_index, utils.FormatUnit(size, "h")))
8773       disks.append(objects.Disk(dev_type=dev_type, size=size,
8774                                 logical_id=logical_id_fn(idx, disk_index, disk),
8775                                 iv_name="disk/%d" % disk_index,
8776                                 mode=disk[constants.IDISK_MODE],
8777                                 params=ld_params[0]))
8778
8779   return disks
8780
8781
8782 def _GetInstanceInfoText(instance):
8783   """Compute that text that should be added to the disk's metadata.
8784
8785   """
8786   return "originstname+%s" % instance.name
8787
8788
8789 def _CalcEta(time_taken, written, total_size):
8790   """Calculates the ETA based on size written and total size.
8791
8792   @param time_taken: The time taken so far
8793   @param written: amount written so far
8794   @param total_size: The total size of data to be written
8795   @return: The remaining time in seconds
8796
8797   """
8798   avg_time = time_taken / float(written)
8799   return (total_size - written) * avg_time
8800
8801
8802 def _WipeDisks(lu, instance):
8803   """Wipes instance disks.
8804
8805   @type lu: L{LogicalUnit}
8806   @param lu: the logical unit on whose behalf we execute
8807   @type instance: L{objects.Instance}
8808   @param instance: the instance whose disks we should create
8809   @return: the success of the wipe
8810
8811   """
8812   node = instance.primary_node
8813
8814   for device in instance.disks:
8815     lu.cfg.SetDiskID(device, node)
8816
8817   logging.info("Pause sync of instance %s disks", instance.name)
8818   result = lu.rpc.call_blockdev_pause_resume_sync(node,
8819                                                   (instance.disks, instance),
8820                                                   True)
8821
8822   for idx, success in enumerate(result.payload):
8823     if not success:
8824       logging.warn("pause-sync of instance %s for disks %d failed",
8825                    instance.name, idx)
8826
8827   try:
8828     for idx, device in enumerate(instance.disks):
8829       # The wipe size is MIN_WIPE_CHUNK_PERCENT % of the instance disk but
8830       # MAX_WIPE_CHUNK at max
8831       wipe_chunk_size = min(constants.MAX_WIPE_CHUNK, device.size / 100.0 *
8832                             constants.MIN_WIPE_CHUNK_PERCENT)
8833       # we _must_ make this an int, otherwise rounding errors will
8834       # occur
8835       wipe_chunk_size = int(wipe_chunk_size)
8836
8837       lu.LogInfo("* Wiping disk %d", idx)
8838       logging.info("Wiping disk %d for instance %s, node %s using"
8839                    " chunk size %s", idx, instance.name, node, wipe_chunk_size)
8840
8841       offset = 0
8842       size = device.size
8843       last_output = 0
8844       start_time = time.time()
8845
8846       while offset < size:
8847         wipe_size = min(wipe_chunk_size, size - offset)
8848         logging.debug("Wiping disk %d, offset %s, chunk %s",
8849                       idx, offset, wipe_size)
8850         result = lu.rpc.call_blockdev_wipe(node, (device, instance), offset,
8851                                            wipe_size)
8852         result.Raise("Could not wipe disk %d at offset %d for size %d" %
8853                      (idx, offset, wipe_size))
8854         now = time.time()
8855         offset += wipe_size
8856         if now - last_output >= 60:
8857           eta = _CalcEta(now - start_time, offset, size)
8858           lu.LogInfo(" - done: %.1f%% ETA: %s" %
8859                      (offset / float(size) * 100, utils.FormatSeconds(eta)))
8860           last_output = now
8861   finally:
8862     logging.info("Resume sync of instance %s disks", instance.name)
8863
8864     result = lu.rpc.call_blockdev_pause_resume_sync(node,
8865                                                     (instance.disks, instance),
8866                                                     False)
8867
8868     for idx, success in enumerate(result.payload):
8869       if not success:
8870         lu.LogWarning("Resume sync of disk %d failed, please have a"
8871                       " look at the status and troubleshoot the issue", idx)
8872         logging.warn("resume-sync of instance %s for disks %d failed",
8873                      instance.name, idx)
8874
8875
8876 def _CreateDisks(lu, instance, to_skip=None, target_node=None):
8877   """Create all disks for an instance.
8878
8879   This abstracts away some work from AddInstance.
8880
8881   @type lu: L{LogicalUnit}
8882   @param lu: the logical unit on whose behalf we execute
8883   @type instance: L{objects.Instance}
8884   @param instance: the instance whose disks we should create
8885   @type to_skip: list
8886   @param to_skip: list of indices to skip
8887   @type target_node: string
8888   @param target_node: if passed, overrides the target node for creation
8889   @rtype: boolean
8890   @return: the success of the creation
8891
8892   """
8893   info = _GetInstanceInfoText(instance)
8894   if target_node is None:
8895     pnode = instance.primary_node
8896     all_nodes = instance.all_nodes
8897   else:
8898     pnode = target_node
8899     all_nodes = [pnode]
8900
8901   if instance.disk_template in constants.DTS_FILEBASED:
8902     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
8903     result = lu.rpc.call_file_storage_dir_create(pnode, file_storage_dir)
8904
8905     result.Raise("Failed to create directory '%s' on"
8906                  " node %s" % (file_storage_dir, pnode))
8907
8908   # Note: this needs to be kept in sync with adding of disks in
8909   # LUInstanceSetParams
8910   for idx, device in enumerate(instance.disks):
8911     if to_skip and idx in to_skip:
8912       continue
8913     logging.info("Creating disk %s for instance '%s'", idx, instance.name)
8914     #HARDCODE
8915     for node in all_nodes:
8916       f_create = node == pnode
8917       _CreateBlockDev(lu, node, instance, device, f_create, info, f_create)
8918
8919
8920 def _RemoveDisks(lu, instance, target_node=None, ignore_failures=False):
8921   """Remove all disks for an instance.
8922
8923   This abstracts away some work from `AddInstance()` and
8924   `RemoveInstance()`. Note that in case some of the devices couldn't
8925   be removed, the removal will continue with the other ones (compare
8926   with `_CreateDisks()`).
8927
8928   @type lu: L{LogicalUnit}
8929   @param lu: the logical unit on whose behalf we execute
8930   @type instance: L{objects.Instance}
8931   @param instance: the instance whose disks we should remove
8932   @type target_node: string
8933   @param target_node: used to override the node on which to remove the disks
8934   @rtype: boolean
8935   @return: the success of the removal
8936
8937   """
8938   logging.info("Removing block devices for instance %s", instance.name)
8939
8940   all_result = True
8941   ports_to_release = set()
8942   for (idx, device) in enumerate(instance.disks):
8943     if target_node:
8944       edata = [(target_node, device)]
8945     else:
8946       edata = device.ComputeNodeTree(instance.primary_node)
8947     for node, disk in edata:
8948       lu.cfg.SetDiskID(disk, node)
8949       msg = lu.rpc.call_blockdev_remove(node, disk).fail_msg
8950       if msg:
8951         lu.LogWarning("Could not remove disk %s on node %s,"
8952                       " continuing anyway: %s", idx, node, msg)
8953         all_result = False
8954
8955     # if this is a DRBD disk, return its port to the pool
8956     if device.dev_type in constants.LDS_DRBD:
8957       ports_to_release.add(device.logical_id[2])
8958
8959   if all_result or ignore_failures:
8960     for port in ports_to_release:
8961       lu.cfg.AddTcpUdpPort(port)
8962
8963   if instance.disk_template == constants.DT_FILE:
8964     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
8965     if target_node:
8966       tgt = target_node
8967     else:
8968       tgt = instance.primary_node
8969     result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
8970     if result.fail_msg:
8971       lu.LogWarning("Could not remove directory '%s' on node %s: %s",
8972                     file_storage_dir, instance.primary_node, result.fail_msg)
8973       all_result = False
8974
8975   return all_result
8976
8977
8978 def _ComputeDiskSizePerVG(disk_template, disks):
8979   """Compute disk size requirements in the volume group
8980
8981   """
8982   def _compute(disks, payload):
8983     """Universal algorithm.
8984
8985     """
8986     vgs = {}
8987     for disk in disks:
8988       vgs[disk[constants.IDISK_VG]] = \
8989         vgs.get(constants.IDISK_VG, 0) + disk[constants.IDISK_SIZE] + payload
8990
8991     return vgs
8992
8993   # Required free disk space as a function of disk and swap space
8994   req_size_dict = {
8995     constants.DT_DISKLESS: {},
8996     constants.DT_PLAIN: _compute(disks, 0),
8997     # 128 MB are added for drbd metadata for each disk
8998     constants.DT_DRBD8: _compute(disks, DRBD_META_SIZE),
8999     constants.DT_FILE: {},
9000     constants.DT_SHARED_FILE: {},
9001   }
9002
9003   if disk_template not in req_size_dict:
9004     raise errors.ProgrammerError("Disk template '%s' size requirement"
9005                                  " is unknown" % disk_template)
9006
9007   return req_size_dict[disk_template]
9008
9009
9010 def _ComputeDiskSize(disk_template, disks):
9011   """Compute disk size requirements in the volume group
9012
9013   """
9014   # Required free disk space as a function of disk and swap space
9015   req_size_dict = {
9016     constants.DT_DISKLESS: None,
9017     constants.DT_PLAIN: sum(d[constants.IDISK_SIZE] for d in disks),
9018     # 128 MB are added for drbd metadata for each disk
9019     constants.DT_DRBD8:
9020       sum(d[constants.IDISK_SIZE] + DRBD_META_SIZE for d in disks),
9021     constants.DT_FILE: None,
9022     constants.DT_SHARED_FILE: 0,
9023     constants.DT_BLOCK: 0,
9024     constants.DT_RBD: 0,
9025   }
9026
9027   if disk_template not in req_size_dict:
9028     raise errors.ProgrammerError("Disk template '%s' size requirement"
9029                                  " is unknown" % disk_template)
9030
9031   return req_size_dict[disk_template]
9032
9033
9034 def _FilterVmNodes(lu, nodenames):
9035   """Filters out non-vm_capable nodes from a list.
9036
9037   @type lu: L{LogicalUnit}
9038   @param lu: the logical unit for which we check
9039   @type nodenames: list
9040   @param nodenames: the list of nodes on which we should check
9041   @rtype: list
9042   @return: the list of vm-capable nodes
9043
9044   """
9045   vm_nodes = frozenset(lu.cfg.GetNonVmCapableNodeList())
9046   return [name for name in nodenames if name not in vm_nodes]
9047
9048
9049 def _CheckHVParams(lu, nodenames, hvname, hvparams):
9050   """Hypervisor parameter validation.
9051
9052   This function abstract the hypervisor parameter validation to be
9053   used in both instance create and instance modify.
9054
9055   @type lu: L{LogicalUnit}
9056   @param lu: the logical unit for which we check
9057   @type nodenames: list
9058   @param nodenames: the list of nodes on which we should check
9059   @type hvname: string
9060   @param hvname: the name of the hypervisor we should use
9061   @type hvparams: dict
9062   @param hvparams: the parameters which we need to check
9063   @raise errors.OpPrereqError: if the parameters are not valid
9064
9065   """
9066   nodenames = _FilterVmNodes(lu, nodenames)
9067
9068   cluster = lu.cfg.GetClusterInfo()
9069   hvfull = objects.FillDict(cluster.hvparams.get(hvname, {}), hvparams)
9070
9071   hvinfo = lu.rpc.call_hypervisor_validate_params(nodenames, hvname, hvfull)
9072   for node in nodenames:
9073     info = hvinfo[node]
9074     if info.offline:
9075       continue
9076     info.Raise("Hypervisor parameter validation failed on node %s" % node)
9077
9078
9079 def _CheckOSParams(lu, required, nodenames, osname, osparams):
9080   """OS parameters validation.
9081
9082   @type lu: L{LogicalUnit}
9083   @param lu: the logical unit for which we check
9084   @type required: boolean
9085   @param required: whether the validation should fail if the OS is not
9086       found
9087   @type nodenames: list
9088   @param nodenames: the list of nodes on which we should check
9089   @type osname: string
9090   @param osname: the name of the hypervisor we should use
9091   @type osparams: dict
9092   @param osparams: the parameters which we need to check
9093   @raise errors.OpPrereqError: if the parameters are not valid
9094
9095   """
9096   nodenames = _FilterVmNodes(lu, nodenames)
9097   result = lu.rpc.call_os_validate(nodenames, required, osname,
9098                                    [constants.OS_VALIDATE_PARAMETERS],
9099                                    osparams)
9100   for node, nres in result.items():
9101     # we don't check for offline cases since this should be run only
9102     # against the master node and/or an instance's nodes
9103     nres.Raise("OS Parameters validation failed on node %s" % node)
9104     if not nres.payload:
9105       lu.LogInfo("OS %s not found on node %s, validation skipped",
9106                  osname, node)
9107
9108
9109 class LUInstanceCreate(LogicalUnit):
9110   """Create an instance.
9111
9112   """
9113   HPATH = "instance-add"
9114   HTYPE = constants.HTYPE_INSTANCE
9115   REQ_BGL = False
9116
9117   def CheckArguments(self):
9118     """Check arguments.
9119
9120     """
9121     # do not require name_check to ease forward/backward compatibility
9122     # for tools
9123     if self.op.no_install and self.op.start:
9124       self.LogInfo("No-installation mode selected, disabling startup")
9125       self.op.start = False
9126     # validate/normalize the instance name
9127     self.op.instance_name = \
9128       netutils.Hostname.GetNormalizedName(self.op.instance_name)
9129
9130     if self.op.ip_check and not self.op.name_check:
9131       # TODO: make the ip check more flexible and not depend on the name check
9132       raise errors.OpPrereqError("Cannot do IP address check without a name"
9133                                  " check", errors.ECODE_INVAL)
9134
9135     # check nics' parameter names
9136     for nic in self.op.nics:
9137       utils.ForceDictType(nic, constants.INIC_PARAMS_TYPES)
9138
9139     # check disks. parameter names and consistent adopt/no-adopt strategy
9140     has_adopt = has_no_adopt = False
9141     for disk in self.op.disks:
9142       utils.ForceDictType(disk, constants.IDISK_PARAMS_TYPES)
9143       if constants.IDISK_ADOPT in disk:
9144         has_adopt = True
9145       else:
9146         has_no_adopt = True
9147     if has_adopt and has_no_adopt:
9148       raise errors.OpPrereqError("Either all disks are adopted or none is",
9149                                  errors.ECODE_INVAL)
9150     if has_adopt:
9151       if self.op.disk_template not in constants.DTS_MAY_ADOPT:
9152         raise errors.OpPrereqError("Disk adoption is not supported for the"
9153                                    " '%s' disk template" %
9154                                    self.op.disk_template,
9155                                    errors.ECODE_INVAL)
9156       if self.op.iallocator is not None:
9157         raise errors.OpPrereqError("Disk adoption not allowed with an"
9158                                    " iallocator script", errors.ECODE_INVAL)
9159       if self.op.mode == constants.INSTANCE_IMPORT:
9160         raise errors.OpPrereqError("Disk adoption not allowed for"
9161                                    " instance import", errors.ECODE_INVAL)
9162     else:
9163       if self.op.disk_template in constants.DTS_MUST_ADOPT:
9164         raise errors.OpPrereqError("Disk template %s requires disk adoption,"
9165                                    " but no 'adopt' parameter given" %
9166                                    self.op.disk_template,
9167                                    errors.ECODE_INVAL)
9168
9169     self.adopt_disks = has_adopt
9170
9171     # instance name verification
9172     if self.op.name_check:
9173       self.hostname1 = netutils.GetHostname(name=self.op.instance_name)
9174       self.op.instance_name = self.hostname1.name
9175       # used in CheckPrereq for ip ping check
9176       self.check_ip = self.hostname1.ip
9177     else:
9178       self.check_ip = None
9179
9180     # file storage checks
9181     if (self.op.file_driver and
9182         not self.op.file_driver in constants.FILE_DRIVER):
9183       raise errors.OpPrereqError("Invalid file driver name '%s'" %
9184                                  self.op.file_driver, errors.ECODE_INVAL)
9185
9186     if self.op.disk_template == constants.DT_FILE:
9187       opcodes.RequireFileStorage()
9188     elif self.op.disk_template == constants.DT_SHARED_FILE:
9189       opcodes.RequireSharedFileStorage()
9190
9191     ### Node/iallocator related checks
9192     _CheckIAllocatorOrNode(self, "iallocator", "pnode")
9193
9194     if self.op.pnode is not None:
9195       if self.op.disk_template in constants.DTS_INT_MIRROR:
9196         if self.op.snode is None:
9197           raise errors.OpPrereqError("The networked disk templates need"
9198                                      " a mirror node", errors.ECODE_INVAL)
9199       elif self.op.snode:
9200         self.LogWarning("Secondary node will be ignored on non-mirrored disk"
9201                         " template")
9202         self.op.snode = None
9203
9204     self._cds = _GetClusterDomainSecret()
9205
9206     if self.op.mode == constants.INSTANCE_IMPORT:
9207       # On import force_variant must be True, because if we forced it at
9208       # initial install, our only chance when importing it back is that it
9209       # works again!
9210       self.op.force_variant = True
9211
9212       if self.op.no_install:
9213         self.LogInfo("No-installation mode has no effect during import")
9214
9215     elif self.op.mode == constants.INSTANCE_CREATE:
9216       if self.op.os_type is None:
9217         raise errors.OpPrereqError("No guest OS specified",
9218                                    errors.ECODE_INVAL)
9219       if self.op.os_type in self.cfg.GetClusterInfo().blacklisted_os:
9220         raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
9221                                    " installation" % self.op.os_type,
9222                                    errors.ECODE_STATE)
9223       if self.op.disk_template is None:
9224         raise errors.OpPrereqError("No disk template specified",
9225                                    errors.ECODE_INVAL)
9226
9227     elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
9228       # Check handshake to ensure both clusters have the same domain secret
9229       src_handshake = self.op.source_handshake
9230       if not src_handshake:
9231         raise errors.OpPrereqError("Missing source handshake",
9232                                    errors.ECODE_INVAL)
9233
9234       errmsg = masterd.instance.CheckRemoteExportHandshake(self._cds,
9235                                                            src_handshake)
9236       if errmsg:
9237         raise errors.OpPrereqError("Invalid handshake: %s" % errmsg,
9238                                    errors.ECODE_INVAL)
9239
9240       # Load and check source CA
9241       self.source_x509_ca_pem = self.op.source_x509_ca
9242       if not self.source_x509_ca_pem:
9243         raise errors.OpPrereqError("Missing source X509 CA",
9244                                    errors.ECODE_INVAL)
9245
9246       try:
9247         (cert, _) = utils.LoadSignedX509Certificate(self.source_x509_ca_pem,
9248                                                     self._cds)
9249       except OpenSSL.crypto.Error, err:
9250         raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
9251                                    (err, ), errors.ECODE_INVAL)
9252
9253       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
9254       if errcode is not None:
9255         raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
9256                                    errors.ECODE_INVAL)
9257
9258       self.source_x509_ca = cert
9259
9260       src_instance_name = self.op.source_instance_name
9261       if not src_instance_name:
9262         raise errors.OpPrereqError("Missing source instance name",
9263                                    errors.ECODE_INVAL)
9264
9265       self.source_instance_name = \
9266           netutils.GetHostname(name=src_instance_name).name
9267
9268     else:
9269       raise errors.OpPrereqError("Invalid instance creation mode %r" %
9270                                  self.op.mode, errors.ECODE_INVAL)
9271
9272   def ExpandNames(self):
9273     """ExpandNames for CreateInstance.
9274
9275     Figure out the right locks for instance creation.
9276
9277     """
9278     self.needed_locks = {}
9279
9280     instance_name = self.op.instance_name
9281     # this is just a preventive check, but someone might still add this
9282     # instance in the meantime, and creation will fail at lock-add time
9283     if instance_name in self.cfg.GetInstanceList():
9284       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
9285                                  instance_name, errors.ECODE_EXISTS)
9286
9287     self.add_locks[locking.LEVEL_INSTANCE] = instance_name
9288
9289     if self.op.iallocator:
9290       # TODO: Find a solution to not lock all nodes in the cluster, e.g. by
9291       # specifying a group on instance creation and then selecting nodes from
9292       # that group
9293       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9294       self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
9295     else:
9296       self.op.pnode = _ExpandNodeName(self.cfg, self.op.pnode)
9297       nodelist = [self.op.pnode]
9298       if self.op.snode is not None:
9299         self.op.snode = _ExpandNodeName(self.cfg, self.op.snode)
9300         nodelist.append(self.op.snode)
9301       self.needed_locks[locking.LEVEL_NODE] = nodelist
9302       # Lock resources of instance's primary and secondary nodes (copy to
9303       # prevent accidential modification)
9304       self.needed_locks[locking.LEVEL_NODE_RES] = list(nodelist)
9305
9306     # in case of import lock the source node too
9307     if self.op.mode == constants.INSTANCE_IMPORT:
9308       src_node = self.op.src_node
9309       src_path = self.op.src_path
9310
9311       if src_path is None:
9312         self.op.src_path = src_path = self.op.instance_name
9313
9314       if src_node is None:
9315         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9316         self.op.src_node = None
9317         if os.path.isabs(src_path):
9318           raise errors.OpPrereqError("Importing an instance from a path"
9319                                      " requires a source node option",
9320                                      errors.ECODE_INVAL)
9321       else:
9322         self.op.src_node = src_node = _ExpandNodeName(self.cfg, src_node)
9323         if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
9324           self.needed_locks[locking.LEVEL_NODE].append(src_node)
9325         if not os.path.isabs(src_path):
9326           self.op.src_path = src_path = \
9327             utils.PathJoin(constants.EXPORT_DIR, src_path)
9328
9329   def _RunAllocator(self):
9330     """Run the allocator based on input opcode.
9331
9332     """
9333     nics = [n.ToDict() for n in self.nics]
9334     ial = IAllocator(self.cfg, self.rpc,
9335                      mode=constants.IALLOCATOR_MODE_ALLOC,
9336                      name=self.op.instance_name,
9337                      disk_template=self.op.disk_template,
9338                      tags=self.op.tags,
9339                      os=self.op.os_type,
9340                      vcpus=self.be_full[constants.BE_VCPUS],
9341                      memory=self.be_full[constants.BE_MAXMEM],
9342                      spindle_use=self.be_full[constants.BE_SPINDLE_USE],
9343                      disks=self.disks,
9344                      nics=nics,
9345                      hypervisor=self.op.hypervisor,
9346                      )
9347
9348     ial.Run(self.op.iallocator)
9349
9350     if not ial.success:
9351       raise errors.OpPrereqError("Can't compute nodes using"
9352                                  " iallocator '%s': %s" %
9353                                  (self.op.iallocator, ial.info),
9354                                  errors.ECODE_NORES)
9355     if len(ial.result) != ial.required_nodes:
9356       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
9357                                  " of nodes (%s), required %s" %
9358                                  (self.op.iallocator, len(ial.result),
9359                                   ial.required_nodes), errors.ECODE_FAULT)
9360     self.op.pnode = ial.result[0]
9361     self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
9362                  self.op.instance_name, self.op.iallocator,
9363                  utils.CommaJoin(ial.result))
9364     if ial.required_nodes == 2:
9365       self.op.snode = ial.result[1]
9366
9367   def BuildHooksEnv(self):
9368     """Build hooks env.
9369
9370     This runs on master, primary and secondary nodes of the instance.
9371
9372     """
9373     env = {
9374       "ADD_MODE": self.op.mode,
9375       }
9376     if self.op.mode == constants.INSTANCE_IMPORT:
9377       env["SRC_NODE"] = self.op.src_node
9378       env["SRC_PATH"] = self.op.src_path
9379       env["SRC_IMAGES"] = self.src_images
9380
9381     env.update(_BuildInstanceHookEnv(
9382       name=self.op.instance_name,
9383       primary_node=self.op.pnode,
9384       secondary_nodes=self.secondaries,
9385       status=self.op.start,
9386       os_type=self.op.os_type,
9387       minmem=self.be_full[constants.BE_MINMEM],
9388       maxmem=self.be_full[constants.BE_MAXMEM],
9389       vcpus=self.be_full[constants.BE_VCPUS],
9390       nics=_NICListToTuple(self, self.nics),
9391       disk_template=self.op.disk_template,
9392       disks=[(d[constants.IDISK_SIZE], d[constants.IDISK_MODE])
9393              for d in self.disks],
9394       bep=self.be_full,
9395       hvp=self.hv_full,
9396       hypervisor_name=self.op.hypervisor,
9397       tags=self.op.tags,
9398     ))
9399
9400     return env
9401
9402   def BuildHooksNodes(self):
9403     """Build hooks nodes.
9404
9405     """
9406     nl = [self.cfg.GetMasterNode(), self.op.pnode] + self.secondaries
9407     return nl, nl
9408
9409   def _ReadExportInfo(self):
9410     """Reads the export information from disk.
9411
9412     It will override the opcode source node and path with the actual
9413     information, if these two were not specified before.
9414
9415     @return: the export information
9416
9417     """
9418     assert self.op.mode == constants.INSTANCE_IMPORT
9419
9420     src_node = self.op.src_node
9421     src_path = self.op.src_path
9422
9423     if src_node is None:
9424       locked_nodes = self.owned_locks(locking.LEVEL_NODE)
9425       exp_list = self.rpc.call_export_list(locked_nodes)
9426       found = False
9427       for node in exp_list:
9428         if exp_list[node].fail_msg:
9429           continue
9430         if src_path in exp_list[node].payload:
9431           found = True
9432           self.op.src_node = src_node = node
9433           self.op.src_path = src_path = utils.PathJoin(constants.EXPORT_DIR,
9434                                                        src_path)
9435           break
9436       if not found:
9437         raise errors.OpPrereqError("No export found for relative path %s" %
9438                                     src_path, errors.ECODE_INVAL)
9439
9440     _CheckNodeOnline(self, src_node)
9441     result = self.rpc.call_export_info(src_node, src_path)
9442     result.Raise("No export or invalid export found in dir %s" % src_path)
9443
9444     export_info = objects.SerializableConfigParser.Loads(str(result.payload))
9445     if not export_info.has_section(constants.INISECT_EXP):
9446       raise errors.ProgrammerError("Corrupted export config",
9447                                    errors.ECODE_ENVIRON)
9448
9449     ei_version = export_info.get(constants.INISECT_EXP, "version")
9450     if (int(ei_version) != constants.EXPORT_VERSION):
9451       raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
9452                                  (ei_version, constants.EXPORT_VERSION),
9453                                  errors.ECODE_ENVIRON)
9454     return export_info
9455
9456   def _ReadExportParams(self, einfo):
9457     """Use export parameters as defaults.
9458
9459     In case the opcode doesn't specify (as in override) some instance
9460     parameters, then try to use them from the export information, if
9461     that declares them.
9462
9463     """
9464     self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
9465
9466     if self.op.disk_template is None:
9467       if einfo.has_option(constants.INISECT_INS, "disk_template"):
9468         self.op.disk_template = einfo.get(constants.INISECT_INS,
9469                                           "disk_template")
9470         if self.op.disk_template not in constants.DISK_TEMPLATES:
9471           raise errors.OpPrereqError("Disk template specified in configuration"
9472                                      " file is not one of the allowed values:"
9473                                      " %s" % " ".join(constants.DISK_TEMPLATES))
9474       else:
9475         raise errors.OpPrereqError("No disk template specified and the export"
9476                                    " is missing the disk_template information",
9477                                    errors.ECODE_INVAL)
9478
9479     if not self.op.disks:
9480       disks = []
9481       # TODO: import the disk iv_name too
9482       for idx in range(constants.MAX_DISKS):
9483         if einfo.has_option(constants.INISECT_INS, "disk%d_size" % idx):
9484           disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
9485           disks.append({constants.IDISK_SIZE: disk_sz})
9486       self.op.disks = disks
9487       if not disks and self.op.disk_template != constants.DT_DISKLESS:
9488         raise errors.OpPrereqError("No disk info specified and the export"
9489                                    " is missing the disk information",
9490                                    errors.ECODE_INVAL)
9491
9492     if not self.op.nics:
9493       nics = []
9494       for idx in range(constants.MAX_NICS):
9495         if einfo.has_option(constants.INISECT_INS, "nic%d_mac" % idx):
9496           ndict = {}
9497           for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
9498             v = einfo.get(constants.INISECT_INS, "nic%d_%s" % (idx, name))
9499             ndict[name] = v
9500           nics.append(ndict)
9501         else:
9502           break
9503       self.op.nics = nics
9504
9505     if not self.op.tags and einfo.has_option(constants.INISECT_INS, "tags"):
9506       self.op.tags = einfo.get(constants.INISECT_INS, "tags").split()
9507
9508     if (self.op.hypervisor is None and
9509         einfo.has_option(constants.INISECT_INS, "hypervisor")):
9510       self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
9511
9512     if einfo.has_section(constants.INISECT_HYP):
9513       # use the export parameters but do not override the ones
9514       # specified by the user
9515       for name, value in einfo.items(constants.INISECT_HYP):
9516         if name not in self.op.hvparams:
9517           self.op.hvparams[name] = value
9518
9519     if einfo.has_section(constants.INISECT_BEP):
9520       # use the parameters, without overriding
9521       for name, value in einfo.items(constants.INISECT_BEP):
9522         if name not in self.op.beparams:
9523           self.op.beparams[name] = value
9524         # Compatibility for the old "memory" be param
9525         if name == constants.BE_MEMORY:
9526           if constants.BE_MAXMEM not in self.op.beparams:
9527             self.op.beparams[constants.BE_MAXMEM] = value
9528           if constants.BE_MINMEM not in self.op.beparams:
9529             self.op.beparams[constants.BE_MINMEM] = value
9530     else:
9531       # try to read the parameters old style, from the main section
9532       for name in constants.BES_PARAMETERS:
9533         if (name not in self.op.beparams and
9534             einfo.has_option(constants.INISECT_INS, name)):
9535           self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
9536
9537     if einfo.has_section(constants.INISECT_OSP):
9538       # use the parameters, without overriding
9539       for name, value in einfo.items(constants.INISECT_OSP):
9540         if name not in self.op.osparams:
9541           self.op.osparams[name] = value
9542
9543   def _RevertToDefaults(self, cluster):
9544     """Revert the instance parameters to the default values.
9545
9546     """
9547     # hvparams
9548     hv_defs = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type, {})
9549     for name in self.op.hvparams.keys():
9550       if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
9551         del self.op.hvparams[name]
9552     # beparams
9553     be_defs = cluster.SimpleFillBE({})
9554     for name in self.op.beparams.keys():
9555       if name in be_defs and be_defs[name] == self.op.beparams[name]:
9556         del self.op.beparams[name]
9557     # nic params
9558     nic_defs = cluster.SimpleFillNIC({})
9559     for nic in self.op.nics:
9560       for name in constants.NICS_PARAMETERS:
9561         if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
9562           del nic[name]
9563     # osparams
9564     os_defs = cluster.SimpleFillOS(self.op.os_type, {})
9565     for name in self.op.osparams.keys():
9566       if name in os_defs and os_defs[name] == self.op.osparams[name]:
9567         del self.op.osparams[name]
9568
9569   def _CalculateFileStorageDir(self):
9570     """Calculate final instance file storage dir.
9571
9572     """
9573     # file storage dir calculation/check
9574     self.instance_file_storage_dir = None
9575     if self.op.disk_template in constants.DTS_FILEBASED:
9576       # build the full file storage dir path
9577       joinargs = []
9578
9579       if self.op.disk_template == constants.DT_SHARED_FILE:
9580         get_fsd_fn = self.cfg.GetSharedFileStorageDir
9581       else:
9582         get_fsd_fn = self.cfg.GetFileStorageDir
9583
9584       cfg_storagedir = get_fsd_fn()
9585       if not cfg_storagedir:
9586         raise errors.OpPrereqError("Cluster file storage dir not defined")
9587       joinargs.append(cfg_storagedir)
9588
9589       if self.op.file_storage_dir is not None:
9590         joinargs.append(self.op.file_storage_dir)
9591
9592       joinargs.append(self.op.instance_name)
9593
9594       # pylint: disable=W0142
9595       self.instance_file_storage_dir = utils.PathJoin(*joinargs)
9596
9597   def CheckPrereq(self): # pylint: disable=R0914
9598     """Check prerequisites.
9599
9600     """
9601     self._CalculateFileStorageDir()
9602
9603     if self.op.mode == constants.INSTANCE_IMPORT:
9604       export_info = self._ReadExportInfo()
9605       self._ReadExportParams(export_info)
9606
9607     if (not self.cfg.GetVGName() and
9608         self.op.disk_template not in constants.DTS_NOT_LVM):
9609       raise errors.OpPrereqError("Cluster does not support lvm-based"
9610                                  " instances", errors.ECODE_STATE)
9611
9612     if (self.op.hypervisor is None or
9613         self.op.hypervisor == constants.VALUE_AUTO):
9614       self.op.hypervisor = self.cfg.GetHypervisorType()
9615
9616     cluster = self.cfg.GetClusterInfo()
9617     enabled_hvs = cluster.enabled_hypervisors
9618     if self.op.hypervisor not in enabled_hvs:
9619       raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
9620                                  " cluster (%s)" % (self.op.hypervisor,
9621                                   ",".join(enabled_hvs)),
9622                                  errors.ECODE_STATE)
9623
9624     # Check tag validity
9625     for tag in self.op.tags:
9626       objects.TaggableObject.ValidateTag(tag)
9627
9628     # check hypervisor parameter syntax (locally)
9629     utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
9630     filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type,
9631                                       self.op.hvparams)
9632     hv_type = hypervisor.GetHypervisor(self.op.hypervisor)
9633     hv_type.CheckParameterSyntax(filled_hvp)
9634     self.hv_full = filled_hvp
9635     # check that we don't specify global parameters on an instance
9636     _CheckGlobalHvParams(self.op.hvparams)
9637
9638     # fill and remember the beparams dict
9639     default_beparams = cluster.beparams[constants.PP_DEFAULT]
9640     for param, value in self.op.beparams.iteritems():
9641       if value == constants.VALUE_AUTO:
9642         self.op.beparams[param] = default_beparams[param]
9643     objects.UpgradeBeParams(self.op.beparams)
9644     utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
9645     self.be_full = cluster.SimpleFillBE(self.op.beparams)
9646
9647     # build os parameters
9648     self.os_full = cluster.SimpleFillOS(self.op.os_type, self.op.osparams)
9649
9650     # now that hvp/bep are in final format, let's reset to defaults,
9651     # if told to do so
9652     if self.op.identify_defaults:
9653       self._RevertToDefaults(cluster)
9654
9655     # NIC buildup
9656     self.nics = []
9657     for idx, nic in enumerate(self.op.nics):
9658       nic_mode_req = nic.get(constants.INIC_MODE, None)
9659       nic_mode = nic_mode_req
9660       if nic_mode is None or nic_mode == constants.VALUE_AUTO:
9661         nic_mode = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_MODE]
9662
9663       # in routed mode, for the first nic, the default ip is 'auto'
9664       if nic_mode == constants.NIC_MODE_ROUTED and idx == 0:
9665         default_ip_mode = constants.VALUE_AUTO
9666       else:
9667         default_ip_mode = constants.VALUE_NONE
9668
9669       # ip validity checks
9670       ip = nic.get(constants.INIC_IP, default_ip_mode)
9671       if ip is None or ip.lower() == constants.VALUE_NONE:
9672         nic_ip = None
9673       elif ip.lower() == constants.VALUE_AUTO:
9674         if not self.op.name_check:
9675           raise errors.OpPrereqError("IP address set to auto but name checks"
9676                                      " have been skipped",
9677                                      errors.ECODE_INVAL)
9678         nic_ip = self.hostname1.ip
9679       else:
9680         if not netutils.IPAddress.IsValid(ip):
9681           raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
9682                                      errors.ECODE_INVAL)
9683         nic_ip = ip
9684
9685       # TODO: check the ip address for uniqueness
9686       if nic_mode == constants.NIC_MODE_ROUTED and not nic_ip:
9687         raise errors.OpPrereqError("Routed nic mode requires an ip address",
9688                                    errors.ECODE_INVAL)
9689
9690       # MAC address verification
9691       mac = nic.get(constants.INIC_MAC, constants.VALUE_AUTO)
9692       if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
9693         mac = utils.NormalizeAndValidateMac(mac)
9694
9695         try:
9696           self.cfg.ReserveMAC(mac, self.proc.GetECId())
9697         except errors.ReservationError:
9698           raise errors.OpPrereqError("MAC address %s already in use"
9699                                      " in cluster" % mac,
9700                                      errors.ECODE_NOTUNIQUE)
9701
9702       #  Build nic parameters
9703       link = nic.get(constants.INIC_LINK, None)
9704       if link == constants.VALUE_AUTO:
9705         link = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_LINK]
9706       nicparams = {}
9707       if nic_mode_req:
9708         nicparams[constants.NIC_MODE] = nic_mode
9709       if link:
9710         nicparams[constants.NIC_LINK] = link
9711
9712       check_params = cluster.SimpleFillNIC(nicparams)
9713       objects.NIC.CheckParameterSyntax(check_params)
9714       self.nics.append(objects.NIC(mac=mac, ip=nic_ip, nicparams=nicparams))
9715
9716     # disk checks/pre-build
9717     default_vg = self.cfg.GetVGName()
9718     self.disks = []
9719     for disk in self.op.disks:
9720       mode = disk.get(constants.IDISK_MODE, constants.DISK_RDWR)
9721       if mode not in constants.DISK_ACCESS_SET:
9722         raise errors.OpPrereqError("Invalid disk access mode '%s'" %
9723                                    mode, errors.ECODE_INVAL)
9724       size = disk.get(constants.IDISK_SIZE, None)
9725       if size is None:
9726         raise errors.OpPrereqError("Missing disk size", errors.ECODE_INVAL)
9727       try:
9728         size = int(size)
9729       except (TypeError, ValueError):
9730         raise errors.OpPrereqError("Invalid disk size '%s'" % size,
9731                                    errors.ECODE_INVAL)
9732
9733       data_vg = disk.get(constants.IDISK_VG, default_vg)
9734       new_disk = {
9735         constants.IDISK_SIZE: size,
9736         constants.IDISK_MODE: mode,
9737         constants.IDISK_VG: data_vg,
9738         }
9739       if constants.IDISK_METAVG in disk:
9740         new_disk[constants.IDISK_METAVG] = disk[constants.IDISK_METAVG]
9741       if constants.IDISK_ADOPT in disk:
9742         new_disk[constants.IDISK_ADOPT] = disk[constants.IDISK_ADOPT]
9743       self.disks.append(new_disk)
9744
9745     if self.op.mode == constants.INSTANCE_IMPORT:
9746       disk_images = []
9747       for idx in range(len(self.disks)):
9748         option = "disk%d_dump" % idx
9749         if export_info.has_option(constants.INISECT_INS, option):
9750           # FIXME: are the old os-es, disk sizes, etc. useful?
9751           export_name = export_info.get(constants.INISECT_INS, option)
9752           image = utils.PathJoin(self.op.src_path, export_name)
9753           disk_images.append(image)
9754         else:
9755           disk_images.append(False)
9756
9757       self.src_images = disk_images
9758
9759       old_name = export_info.get(constants.INISECT_INS, "name")
9760       if self.op.instance_name == old_name:
9761         for idx, nic in enumerate(self.nics):
9762           if nic.mac == constants.VALUE_AUTO:
9763             nic_mac_ini = "nic%d_mac" % idx
9764             nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
9765
9766     # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
9767
9768     # ip ping checks (we use the same ip that was resolved in ExpandNames)
9769     if self.op.ip_check:
9770       if netutils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
9771         raise errors.OpPrereqError("IP %s of instance %s already in use" %
9772                                    (self.check_ip, self.op.instance_name),
9773                                    errors.ECODE_NOTUNIQUE)
9774
9775     #### mac address generation
9776     # By generating here the mac address both the allocator and the hooks get
9777     # the real final mac address rather than the 'auto' or 'generate' value.
9778     # There is a race condition between the generation and the instance object
9779     # creation, which means that we know the mac is valid now, but we're not
9780     # sure it will be when we actually add the instance. If things go bad
9781     # adding the instance will abort because of a duplicate mac, and the
9782     # creation job will fail.
9783     for nic in self.nics:
9784       if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
9785         nic.mac = self.cfg.GenerateMAC(self.proc.GetECId())
9786
9787     #### allocator run
9788
9789     if self.op.iallocator is not None:
9790       self._RunAllocator()
9791
9792     # Release all unneeded node locks
9793     _ReleaseLocks(self, locking.LEVEL_NODE,
9794                   keep=filter(None, [self.op.pnode, self.op.snode,
9795                                      self.op.src_node]))
9796     _ReleaseLocks(self, locking.LEVEL_NODE_RES,
9797                   keep=filter(None, [self.op.pnode, self.op.snode,
9798                                      self.op.src_node]))
9799
9800     #### node related checks
9801
9802     # check primary node
9803     self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode)
9804     assert self.pnode is not None, \
9805       "Cannot retrieve locked node %s" % self.op.pnode
9806     if pnode.offline:
9807       raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
9808                                  pnode.name, errors.ECODE_STATE)
9809     if pnode.drained:
9810       raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
9811                                  pnode.name, errors.ECODE_STATE)
9812     if not pnode.vm_capable:
9813       raise errors.OpPrereqError("Cannot use non-vm_capable primary node"
9814                                  " '%s'" % pnode.name, errors.ECODE_STATE)
9815
9816     self.secondaries = []
9817
9818     # mirror node verification
9819     if self.op.disk_template in constants.DTS_INT_MIRROR:
9820       if self.op.snode == pnode.name:
9821         raise errors.OpPrereqError("The secondary node cannot be the"
9822                                    " primary node", errors.ECODE_INVAL)
9823       _CheckNodeOnline(self, self.op.snode)
9824       _CheckNodeNotDrained(self, self.op.snode)
9825       _CheckNodeVmCapable(self, self.op.snode)
9826       self.secondaries.append(self.op.snode)
9827
9828       snode = self.cfg.GetNodeInfo(self.op.snode)
9829       if pnode.group != snode.group:
9830         self.LogWarning("The primary and secondary nodes are in two"
9831                         " different node groups; the disk parameters"
9832                         " from the first disk's node group will be"
9833                         " used")
9834
9835     nodenames = [pnode.name] + self.secondaries
9836
9837     # Verify instance specs
9838     spindle_use = self.be_full.get(constants.BE_SPINDLE_USE, None)
9839     ispec = {
9840       constants.ISPEC_MEM_SIZE: self.be_full.get(constants.BE_MAXMEM, None),
9841       constants.ISPEC_CPU_COUNT: self.be_full.get(constants.BE_VCPUS, None),
9842       constants.ISPEC_DISK_COUNT: len(self.disks),
9843       constants.ISPEC_DISK_SIZE: [disk["size"] for disk in self.disks],
9844       constants.ISPEC_NIC_COUNT: len(self.nics),
9845       constants.ISPEC_SPINDLE_USE: spindle_use,
9846       }
9847
9848     group_info = self.cfg.GetNodeGroup(pnode.group)
9849     ipolicy = _CalculateGroupIPolicy(cluster, group_info)
9850     res = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec)
9851     if not self.op.ignore_ipolicy and res:
9852       raise errors.OpPrereqError(("Instance allocation to group %s violates"
9853                                   " policy: %s") % (pnode.group,
9854                                                     utils.CommaJoin(res)),
9855                                   errors.ECODE_INVAL)
9856
9857     # disk parameters (not customizable at instance or node level)
9858     # just use the primary node parameters, ignoring the secondary.
9859     self.diskparams = group_info.diskparams
9860
9861     if not self.adopt_disks:
9862       if self.op.disk_template == constants.DT_RBD:
9863         # _CheckRADOSFreeSpace() is just a placeholder.
9864         # Any function that checks prerequisites can be placed here.
9865         # Check if there is enough space on the RADOS cluster.
9866         _CheckRADOSFreeSpace()
9867       else:
9868         # Check lv size requirements, if not adopting
9869         req_sizes = _ComputeDiskSizePerVG(self.op.disk_template, self.disks)
9870         _CheckNodesFreeDiskPerVG(self, nodenames, req_sizes)
9871
9872     elif self.op.disk_template == constants.DT_PLAIN: # Check the adoption data
9873       all_lvs = set(["%s/%s" % (disk[constants.IDISK_VG],
9874                                 disk[constants.IDISK_ADOPT])
9875                      for disk in self.disks])
9876       if len(all_lvs) != len(self.disks):
9877         raise errors.OpPrereqError("Duplicate volume names given for adoption",
9878                                    errors.ECODE_INVAL)
9879       for lv_name in all_lvs:
9880         try:
9881           # FIXME: lv_name here is "vg/lv" need to ensure that other calls
9882           # to ReserveLV uses the same syntax
9883           self.cfg.ReserveLV(lv_name, self.proc.GetECId())
9884         except errors.ReservationError:
9885           raise errors.OpPrereqError("LV named %s used by another instance" %
9886                                      lv_name, errors.ECODE_NOTUNIQUE)
9887
9888       vg_names = self.rpc.call_vg_list([pnode.name])[pnode.name]
9889       vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
9890
9891       node_lvs = self.rpc.call_lv_list([pnode.name],
9892                                        vg_names.payload.keys())[pnode.name]
9893       node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
9894       node_lvs = node_lvs.payload
9895
9896       delta = all_lvs.difference(node_lvs.keys())
9897       if delta:
9898         raise errors.OpPrereqError("Missing logical volume(s): %s" %
9899                                    utils.CommaJoin(delta),
9900                                    errors.ECODE_INVAL)
9901       online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
9902       if online_lvs:
9903         raise errors.OpPrereqError("Online logical volumes found, cannot"
9904                                    " adopt: %s" % utils.CommaJoin(online_lvs),
9905                                    errors.ECODE_STATE)
9906       # update the size of disk based on what is found
9907       for dsk in self.disks:
9908         dsk[constants.IDISK_SIZE] = \
9909           int(float(node_lvs["%s/%s" % (dsk[constants.IDISK_VG],
9910                                         dsk[constants.IDISK_ADOPT])][0]))
9911
9912     elif self.op.disk_template == constants.DT_BLOCK:
9913       # Normalize and de-duplicate device paths
9914       all_disks = set([os.path.abspath(disk[constants.IDISK_ADOPT])
9915                        for disk in self.disks])
9916       if len(all_disks) != len(self.disks):
9917         raise errors.OpPrereqError("Duplicate disk names given for adoption",
9918                                    errors.ECODE_INVAL)
9919       baddisks = [d for d in all_disks
9920                   if not d.startswith(constants.ADOPTABLE_BLOCKDEV_ROOT)]
9921       if baddisks:
9922         raise errors.OpPrereqError("Device node(s) %s lie outside %s and"
9923                                    " cannot be adopted" %
9924                                    (", ".join(baddisks),
9925                                     constants.ADOPTABLE_BLOCKDEV_ROOT),
9926                                    errors.ECODE_INVAL)
9927
9928       node_disks = self.rpc.call_bdev_sizes([pnode.name],
9929                                             list(all_disks))[pnode.name]
9930       node_disks.Raise("Cannot get block device information from node %s" %
9931                        pnode.name)
9932       node_disks = node_disks.payload
9933       delta = all_disks.difference(node_disks.keys())
9934       if delta:
9935         raise errors.OpPrereqError("Missing block device(s): %s" %
9936                                    utils.CommaJoin(delta),
9937                                    errors.ECODE_INVAL)
9938       for dsk in self.disks:
9939         dsk[constants.IDISK_SIZE] = \
9940           int(float(node_disks[dsk[constants.IDISK_ADOPT]]))
9941
9942     _CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
9943
9944     _CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant)
9945     # check OS parameters (remotely)
9946     _CheckOSParams(self, True, nodenames, self.op.os_type, self.os_full)
9947
9948     _CheckNicsBridgesExist(self, self.nics, self.pnode.name)
9949
9950     # memory check on primary node
9951     #TODO(dynmem): use MINMEM for checking
9952     if self.op.start:
9953       _CheckNodeFreeMemory(self, self.pnode.name,
9954                            "creating instance %s" % self.op.instance_name,
9955                            self.be_full[constants.BE_MAXMEM],
9956                            self.op.hypervisor)
9957
9958     self.dry_run_result = list(nodenames)
9959
9960   def Exec(self, feedback_fn):
9961     """Create and add the instance to the cluster.
9962
9963     """
9964     instance = self.op.instance_name
9965     pnode_name = self.pnode.name
9966
9967     assert not (self.owned_locks(locking.LEVEL_NODE_RES) -
9968                 self.owned_locks(locking.LEVEL_NODE)), \
9969       "Node locks differ from node resource locks"
9970
9971     ht_kind = self.op.hypervisor
9972     if ht_kind in constants.HTS_REQ_PORT:
9973       network_port = self.cfg.AllocatePort()
9974     else:
9975       network_port = None
9976
9977     disks = _GenerateDiskTemplate(self,
9978                                   self.op.disk_template,
9979                                   instance, pnode_name,
9980                                   self.secondaries,
9981                                   self.disks,
9982                                   self.instance_file_storage_dir,
9983                                   self.op.file_driver,
9984                                   0,
9985                                   feedback_fn,
9986                                   self.diskparams)
9987
9988     iobj = objects.Instance(name=instance, os=self.op.os_type,
9989                             primary_node=pnode_name,
9990                             nics=self.nics, disks=disks,
9991                             disk_template=self.op.disk_template,
9992                             admin_state=constants.ADMINST_DOWN,
9993                             network_port=network_port,
9994                             beparams=self.op.beparams,
9995                             hvparams=self.op.hvparams,
9996                             hypervisor=self.op.hypervisor,
9997                             osparams=self.op.osparams,
9998                             )
9999
10000     if self.op.tags:
10001       for tag in self.op.tags:
10002         iobj.AddTag(tag)
10003
10004     if self.adopt_disks:
10005       if self.op.disk_template == constants.DT_PLAIN:
10006         # rename LVs to the newly-generated names; we need to construct
10007         # 'fake' LV disks with the old data, plus the new unique_id
10008         tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
10009         rename_to = []
10010         for t_dsk, a_dsk in zip(tmp_disks, self.disks):
10011           rename_to.append(t_dsk.logical_id)
10012           t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
10013           self.cfg.SetDiskID(t_dsk, pnode_name)
10014         result = self.rpc.call_blockdev_rename(pnode_name,
10015                                                zip(tmp_disks, rename_to))
10016         result.Raise("Failed to rename adoped LVs")
10017     else:
10018       feedback_fn("* creating instance disks...")
10019       try:
10020         _CreateDisks(self, iobj)
10021       except errors.OpExecError:
10022         self.LogWarning("Device creation failed, reverting...")
10023         try:
10024           _RemoveDisks(self, iobj)
10025         finally:
10026           self.cfg.ReleaseDRBDMinors(instance)
10027           raise
10028
10029     feedback_fn("adding instance %s to cluster config" % instance)
10030
10031     self.cfg.AddInstance(iobj, self.proc.GetECId())
10032
10033     # Declare that we don't want to remove the instance lock anymore, as we've
10034     # added the instance to the config
10035     del self.remove_locks[locking.LEVEL_INSTANCE]
10036
10037     if self.op.mode == constants.INSTANCE_IMPORT:
10038       # Release unused nodes
10039       _ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node])
10040     else:
10041       # Release all nodes
10042       _ReleaseLocks(self, locking.LEVEL_NODE)
10043
10044     disk_abort = False
10045     if not self.adopt_disks and self.cfg.GetClusterInfo().prealloc_wipe_disks:
10046       feedback_fn("* wiping instance disks...")
10047       try:
10048         _WipeDisks(self, iobj)
10049       except errors.OpExecError, err:
10050         logging.exception("Wiping disks failed")
10051         self.LogWarning("Wiping instance disks failed (%s)", err)
10052         disk_abort = True
10053
10054     if disk_abort:
10055       # Something is already wrong with the disks, don't do anything else
10056       pass
10057     elif self.op.wait_for_sync:
10058       disk_abort = not _WaitForSync(self, iobj)
10059     elif iobj.disk_template in constants.DTS_INT_MIRROR:
10060       # make sure the disks are not degraded (still sync-ing is ok)
10061       feedback_fn("* checking mirrors status")
10062       disk_abort = not _WaitForSync(self, iobj, oneshot=True)
10063     else:
10064       disk_abort = False
10065
10066     if disk_abort:
10067       _RemoveDisks(self, iobj)
10068       self.cfg.RemoveInstance(iobj.name)
10069       # Make sure the instance lock gets removed
10070       self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
10071       raise errors.OpExecError("There are some degraded disks for"
10072                                " this instance")
10073
10074     # Release all node resource locks
10075     _ReleaseLocks(self, locking.LEVEL_NODE_RES)
10076
10077     if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
10078       if self.op.mode == constants.INSTANCE_CREATE:
10079         if not self.op.no_install:
10080           pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
10081                         not self.op.wait_for_sync)
10082           if pause_sync:
10083             feedback_fn("* pausing disk sync to install instance OS")
10084             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
10085                                                               (iobj.disks,
10086                                                                iobj), True)
10087             for idx, success in enumerate(result.payload):
10088               if not success:
10089                 logging.warn("pause-sync of instance %s for disk %d failed",
10090                              instance, idx)
10091
10092           feedback_fn("* running the instance OS create scripts...")
10093           # FIXME: pass debug option from opcode to backend
10094           os_add_result = \
10095             self.rpc.call_instance_os_add(pnode_name, (iobj, None), False,
10096                                           self.op.debug_level)
10097           if pause_sync:
10098             feedback_fn("* resuming disk sync")
10099             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
10100                                                               (iobj.disks,
10101                                                                iobj), False)
10102             for idx, success in enumerate(result.payload):
10103               if not success:
10104                 logging.warn("resume-sync of instance %s for disk %d failed",
10105                              instance, idx)
10106
10107           os_add_result.Raise("Could not add os for instance %s"
10108                               " on node %s" % (instance, pnode_name))
10109
10110       elif self.op.mode == constants.INSTANCE_IMPORT:
10111         feedback_fn("* running the instance OS import scripts...")
10112
10113         transfers = []
10114
10115         for idx, image in enumerate(self.src_images):
10116           if not image:
10117             continue
10118
10119           # FIXME: pass debug option from opcode to backend
10120           dt = masterd.instance.DiskTransfer("disk/%s" % idx,
10121                                              constants.IEIO_FILE, (image, ),
10122                                              constants.IEIO_SCRIPT,
10123                                              (iobj.disks[idx], idx),
10124                                              None)
10125           transfers.append(dt)
10126
10127         import_result = \
10128           masterd.instance.TransferInstanceData(self, feedback_fn,
10129                                                 self.op.src_node, pnode_name,
10130                                                 self.pnode.secondary_ip,
10131                                                 iobj, transfers)
10132         if not compat.all(import_result):
10133           self.LogWarning("Some disks for instance %s on node %s were not"
10134                           " imported successfully" % (instance, pnode_name))
10135
10136       elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
10137         feedback_fn("* preparing remote import...")
10138         # The source cluster will stop the instance before attempting to make a
10139         # connection. In some cases stopping an instance can take a long time,
10140         # hence the shutdown timeout is added to the connection timeout.
10141         connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
10142                            self.op.source_shutdown_timeout)
10143         timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
10144
10145         assert iobj.primary_node == self.pnode.name
10146         disk_results = \
10147           masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
10148                                         self.source_x509_ca,
10149                                         self._cds, timeouts)
10150         if not compat.all(disk_results):
10151           # TODO: Should the instance still be started, even if some disks
10152           # failed to import (valid for local imports, too)?
10153           self.LogWarning("Some disks for instance %s on node %s were not"
10154                           " imported successfully" % (instance, pnode_name))
10155
10156         # Run rename script on newly imported instance
10157         assert iobj.name == instance
10158         feedback_fn("Running rename script for %s" % instance)
10159         result = self.rpc.call_instance_run_rename(pnode_name, iobj,
10160                                                    self.source_instance_name,
10161                                                    self.op.debug_level)
10162         if result.fail_msg:
10163           self.LogWarning("Failed to run rename script for %s on node"
10164                           " %s: %s" % (instance, pnode_name, result.fail_msg))
10165
10166       else:
10167         # also checked in the prereq part
10168         raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
10169                                      % self.op.mode)
10170
10171     assert not self.owned_locks(locking.LEVEL_NODE_RES)
10172
10173     if self.op.start:
10174       iobj.admin_state = constants.ADMINST_UP
10175       self.cfg.Update(iobj, feedback_fn)
10176       logging.info("Starting instance %s on node %s", instance, pnode_name)
10177       feedback_fn("* starting instance...")
10178       result = self.rpc.call_instance_start(pnode_name, (iobj, None, None),
10179                                             False)
10180       result.Raise("Could not start instance")
10181
10182     return list(iobj.all_nodes)
10183
10184
10185 def _CheckRADOSFreeSpace():
10186   """Compute disk size requirements inside the RADOS cluster.
10187
10188   """
10189   # For the RADOS cluster we assume there is always enough space.
10190   pass
10191
10192
10193 class LUInstanceConsole(NoHooksLU):
10194   """Connect to an instance's console.
10195
10196   This is somewhat special in that it returns the command line that
10197   you need to run on the master node in order to connect to the
10198   console.
10199
10200   """
10201   REQ_BGL = False
10202
10203   def ExpandNames(self):
10204     self.share_locks = _ShareAll()
10205     self._ExpandAndLockInstance()
10206
10207   def CheckPrereq(self):
10208     """Check prerequisites.
10209
10210     This checks that the instance is in the cluster.
10211
10212     """
10213     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
10214     assert self.instance is not None, \
10215       "Cannot retrieve locked instance %s" % self.op.instance_name
10216     _CheckNodeOnline(self, self.instance.primary_node)
10217
10218   def Exec(self, feedback_fn):
10219     """Connect to the console of an instance
10220
10221     """
10222     instance = self.instance
10223     node = instance.primary_node
10224
10225     node_insts = self.rpc.call_instance_list([node],
10226                                              [instance.hypervisor])[node]
10227     node_insts.Raise("Can't get node information from %s" % node)
10228
10229     if instance.name not in node_insts.payload:
10230       if instance.admin_state == constants.ADMINST_UP:
10231         state = constants.INSTST_ERRORDOWN
10232       elif instance.admin_state == constants.ADMINST_DOWN:
10233         state = constants.INSTST_ADMINDOWN
10234       else:
10235         state = constants.INSTST_ADMINOFFLINE
10236       raise errors.OpExecError("Instance %s is not running (state %s)" %
10237                                (instance.name, state))
10238
10239     logging.debug("Connecting to console of %s on %s", instance.name, node)
10240
10241     return _GetInstanceConsole(self.cfg.GetClusterInfo(), instance)
10242
10243
10244 def _GetInstanceConsole(cluster, instance):
10245   """Returns console information for an instance.
10246
10247   @type cluster: L{objects.Cluster}
10248   @type instance: L{objects.Instance}
10249   @rtype: dict
10250
10251   """
10252   hyper = hypervisor.GetHypervisor(instance.hypervisor)
10253   # beparams and hvparams are passed separately, to avoid editing the
10254   # instance and then saving the defaults in the instance itself.
10255   hvparams = cluster.FillHV(instance)
10256   beparams = cluster.FillBE(instance)
10257   console = hyper.GetInstanceConsole(instance, hvparams, beparams)
10258
10259   assert console.instance == instance.name
10260   assert console.Validate()
10261
10262   return console.ToDict()
10263
10264
10265 class LUInstanceReplaceDisks(LogicalUnit):
10266   """Replace the disks of an instance.
10267
10268   """
10269   HPATH = "mirrors-replace"
10270   HTYPE = constants.HTYPE_INSTANCE
10271   REQ_BGL = False
10272
10273   def CheckArguments(self):
10274     TLReplaceDisks.CheckArguments(self.op.mode, self.op.remote_node,
10275                                   self.op.iallocator)
10276
10277   def ExpandNames(self):
10278     self._ExpandAndLockInstance()
10279
10280     assert locking.LEVEL_NODE not in self.needed_locks
10281     assert locking.LEVEL_NODE_RES not in self.needed_locks
10282     assert locking.LEVEL_NODEGROUP not in self.needed_locks
10283
10284     assert self.op.iallocator is None or self.op.remote_node is None, \
10285       "Conflicting options"
10286
10287     if self.op.remote_node is not None:
10288       self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
10289
10290       # Warning: do not remove the locking of the new secondary here
10291       # unless DRBD8.AddChildren is changed to work in parallel;
10292       # currently it doesn't since parallel invocations of
10293       # FindUnusedMinor will conflict
10294       self.needed_locks[locking.LEVEL_NODE] = [self.op.remote_node]
10295       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
10296     else:
10297       self.needed_locks[locking.LEVEL_NODE] = []
10298       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
10299
10300       if self.op.iallocator is not None:
10301         # iallocator will select a new node in the same group
10302         self.needed_locks[locking.LEVEL_NODEGROUP] = []
10303
10304     self.needed_locks[locking.LEVEL_NODE_RES] = []
10305
10306     self.replacer = TLReplaceDisks(self, self.op.instance_name, self.op.mode,
10307                                    self.op.iallocator, self.op.remote_node,
10308                                    self.op.disks, False, self.op.early_release,
10309                                    self.op.ignore_ipolicy)
10310
10311     self.tasklets = [self.replacer]
10312
10313   def DeclareLocks(self, level):
10314     if level == locking.LEVEL_NODEGROUP:
10315       assert self.op.remote_node is None
10316       assert self.op.iallocator is not None
10317       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
10318
10319       self.share_locks[locking.LEVEL_NODEGROUP] = 1
10320       # Lock all groups used by instance optimistically; this requires going
10321       # via the node before it's locked, requiring verification later on
10322       self.needed_locks[locking.LEVEL_NODEGROUP] = \
10323         self.cfg.GetInstanceNodeGroups(self.op.instance_name)
10324
10325     elif level == locking.LEVEL_NODE:
10326       if self.op.iallocator is not None:
10327         assert self.op.remote_node is None
10328         assert not self.needed_locks[locking.LEVEL_NODE]
10329
10330         # Lock member nodes of all locked groups
10331         self.needed_locks[locking.LEVEL_NODE] = [node_name
10332           for group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
10333           for node_name in self.cfg.GetNodeGroup(group_uuid).members]
10334       else:
10335         self._LockInstancesNodes()
10336     elif level == locking.LEVEL_NODE_RES:
10337       # Reuse node locks
10338       self.needed_locks[locking.LEVEL_NODE_RES] = \
10339         self.needed_locks[locking.LEVEL_NODE]
10340
10341   def BuildHooksEnv(self):
10342     """Build hooks env.
10343
10344     This runs on the master, the primary and all the secondaries.
10345
10346     """
10347     instance = self.replacer.instance
10348     env = {
10349       "MODE": self.op.mode,
10350       "NEW_SECONDARY": self.op.remote_node,
10351       "OLD_SECONDARY": instance.secondary_nodes[0],
10352       }
10353     env.update(_BuildInstanceHookEnvByObject(self, instance))
10354     return env
10355
10356   def BuildHooksNodes(self):
10357     """Build hooks nodes.
10358
10359     """
10360     instance = self.replacer.instance
10361     nl = [
10362       self.cfg.GetMasterNode(),
10363       instance.primary_node,
10364       ]
10365     if self.op.remote_node is not None:
10366       nl.append(self.op.remote_node)
10367     return nl, nl
10368
10369   def CheckPrereq(self):
10370     """Check prerequisites.
10371
10372     """
10373     assert (self.glm.is_owned(locking.LEVEL_NODEGROUP) or
10374             self.op.iallocator is None)
10375
10376     # Verify if node group locks are still correct
10377     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
10378     if owned_groups:
10379       _CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
10380
10381     return LogicalUnit.CheckPrereq(self)
10382
10383
10384 class TLReplaceDisks(Tasklet):
10385   """Replaces disks for an instance.
10386
10387   Note: Locking is not within the scope of this class.
10388
10389   """
10390   def __init__(self, lu, instance_name, mode, iallocator_name, remote_node,
10391                disks, delay_iallocator, early_release, ignore_ipolicy):
10392     """Initializes this class.
10393
10394     """
10395     Tasklet.__init__(self, lu)
10396
10397     # Parameters
10398     self.instance_name = instance_name
10399     self.mode = mode
10400     self.iallocator_name = iallocator_name
10401     self.remote_node = remote_node
10402     self.disks = disks
10403     self.delay_iallocator = delay_iallocator
10404     self.early_release = early_release
10405     self.ignore_ipolicy = ignore_ipolicy
10406
10407     # Runtime data
10408     self.instance = None
10409     self.new_node = None
10410     self.target_node = None
10411     self.other_node = None
10412     self.remote_node_info = None
10413     self.node_secondary_ip = None
10414
10415   @staticmethod
10416   def CheckArguments(mode, remote_node, iallocator):
10417     """Helper function for users of this class.
10418
10419     """
10420     # check for valid parameter combination
10421     if mode == constants.REPLACE_DISK_CHG:
10422       if remote_node is None and iallocator is None:
10423         raise errors.OpPrereqError("When changing the secondary either an"
10424                                    " iallocator script must be used or the"
10425                                    " new node given", errors.ECODE_INVAL)
10426
10427       if remote_node is not None and iallocator is not None:
10428         raise errors.OpPrereqError("Give either the iallocator or the new"
10429                                    " secondary, not both", errors.ECODE_INVAL)
10430
10431     elif remote_node is not None or iallocator is not None:
10432       # Not replacing the secondary
10433       raise errors.OpPrereqError("The iallocator and new node options can"
10434                                  " only be used when changing the"
10435                                  " secondary node", errors.ECODE_INVAL)
10436
10437   @staticmethod
10438   def _RunAllocator(lu, iallocator_name, instance_name, relocate_from):
10439     """Compute a new secondary node using an IAllocator.
10440
10441     """
10442     ial = IAllocator(lu.cfg, lu.rpc,
10443                      mode=constants.IALLOCATOR_MODE_RELOC,
10444                      name=instance_name,
10445                      relocate_from=list(relocate_from))
10446
10447     ial.Run(iallocator_name)
10448
10449     if not ial.success:
10450       raise errors.OpPrereqError("Can't compute nodes using iallocator '%s':"
10451                                  " %s" % (iallocator_name, ial.info),
10452                                  errors.ECODE_NORES)
10453
10454     if len(ial.result) != ial.required_nodes:
10455       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
10456                                  " of nodes (%s), required %s" %
10457                                  (iallocator_name,
10458                                   len(ial.result), ial.required_nodes),
10459                                  errors.ECODE_FAULT)
10460
10461     remote_node_name = ial.result[0]
10462
10463     lu.LogInfo("Selected new secondary for instance '%s': %s",
10464                instance_name, remote_node_name)
10465
10466     return remote_node_name
10467
10468   def _FindFaultyDisks(self, node_name):
10469     """Wrapper for L{_FindFaultyInstanceDisks}.
10470
10471     """
10472     return _FindFaultyInstanceDisks(self.cfg, self.rpc, self.instance,
10473                                     node_name, True)
10474
10475   def _CheckDisksActivated(self, instance):
10476     """Checks if the instance disks are activated.
10477
10478     @param instance: The instance to check disks
10479     @return: True if they are activated, False otherwise
10480
10481     """
10482     nodes = instance.all_nodes
10483
10484     for idx, dev in enumerate(instance.disks):
10485       for node in nodes:
10486         self.lu.LogInfo("Checking disk/%d on %s", idx, node)
10487         self.cfg.SetDiskID(dev, node)
10488
10489         result = self.rpc.call_blockdev_find(node, dev)
10490
10491         if result.offline:
10492           continue
10493         elif result.fail_msg or not result.payload:
10494           return False
10495
10496     return True
10497
10498   def CheckPrereq(self):
10499     """Check prerequisites.
10500
10501     This checks that the instance is in the cluster.
10502
10503     """
10504     self.instance = instance = self.cfg.GetInstanceInfo(self.instance_name)
10505     assert instance is not None, \
10506       "Cannot retrieve locked instance %s" % self.instance_name
10507
10508     if instance.disk_template != constants.DT_DRBD8:
10509       raise errors.OpPrereqError("Can only run replace disks for DRBD8-based"
10510                                  " instances", errors.ECODE_INVAL)
10511
10512     if len(instance.secondary_nodes) != 1:
10513       raise errors.OpPrereqError("The instance has a strange layout,"
10514                                  " expected one secondary but found %d" %
10515                                  len(instance.secondary_nodes),
10516                                  errors.ECODE_FAULT)
10517
10518     if not self.delay_iallocator:
10519       self._CheckPrereq2()
10520
10521   def _CheckPrereq2(self):
10522     """Check prerequisites, second part.
10523
10524     This function should always be part of CheckPrereq. It was separated and is
10525     now called from Exec because during node evacuation iallocator was only
10526     called with an unmodified cluster model, not taking planned changes into
10527     account.
10528
10529     """
10530     instance = self.instance
10531     secondary_node = instance.secondary_nodes[0]
10532
10533     if self.iallocator_name is None:
10534       remote_node = self.remote_node
10535     else:
10536       remote_node = self._RunAllocator(self.lu, self.iallocator_name,
10537                                        instance.name, instance.secondary_nodes)
10538
10539     if remote_node is None:
10540       self.remote_node_info = None
10541     else:
10542       assert remote_node in self.lu.owned_locks(locking.LEVEL_NODE), \
10543              "Remote node '%s' is not locked" % remote_node
10544
10545       self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
10546       assert self.remote_node_info is not None, \
10547         "Cannot retrieve locked node %s" % remote_node
10548
10549     if remote_node == self.instance.primary_node:
10550       raise errors.OpPrereqError("The specified node is the primary node of"
10551                                  " the instance", errors.ECODE_INVAL)
10552
10553     if remote_node == secondary_node:
10554       raise errors.OpPrereqError("The specified node is already the"
10555                                  " secondary node of the instance",
10556                                  errors.ECODE_INVAL)
10557
10558     if self.disks and self.mode in (constants.REPLACE_DISK_AUTO,
10559                                     constants.REPLACE_DISK_CHG):
10560       raise errors.OpPrereqError("Cannot specify disks to be replaced",
10561                                  errors.ECODE_INVAL)
10562
10563     if self.mode == constants.REPLACE_DISK_AUTO:
10564       if not self._CheckDisksActivated(instance):
10565         raise errors.OpPrereqError("Please run activate-disks on instance %s"
10566                                    " first" % self.instance_name,
10567                                    errors.ECODE_STATE)
10568       faulty_primary = self._FindFaultyDisks(instance.primary_node)
10569       faulty_secondary = self._FindFaultyDisks(secondary_node)
10570
10571       if faulty_primary and faulty_secondary:
10572         raise errors.OpPrereqError("Instance %s has faulty disks on more than"
10573                                    " one node and can not be repaired"
10574                                    " automatically" % self.instance_name,
10575                                    errors.ECODE_STATE)
10576
10577       if faulty_primary:
10578         self.disks = faulty_primary
10579         self.target_node = instance.primary_node
10580         self.other_node = secondary_node
10581         check_nodes = [self.target_node, self.other_node]
10582       elif faulty_secondary:
10583         self.disks = faulty_secondary
10584         self.target_node = secondary_node
10585         self.other_node = instance.primary_node
10586         check_nodes = [self.target_node, self.other_node]
10587       else:
10588         self.disks = []
10589         check_nodes = []
10590
10591     else:
10592       # Non-automatic modes
10593       if self.mode == constants.REPLACE_DISK_PRI:
10594         self.target_node = instance.primary_node
10595         self.other_node = secondary_node
10596         check_nodes = [self.target_node, self.other_node]
10597
10598       elif self.mode == constants.REPLACE_DISK_SEC:
10599         self.target_node = secondary_node
10600         self.other_node = instance.primary_node
10601         check_nodes = [self.target_node, self.other_node]
10602
10603       elif self.mode == constants.REPLACE_DISK_CHG:
10604         self.new_node = remote_node
10605         self.other_node = instance.primary_node
10606         self.target_node = secondary_node
10607         check_nodes = [self.new_node, self.other_node]
10608
10609         _CheckNodeNotDrained(self.lu, remote_node)
10610         _CheckNodeVmCapable(self.lu, remote_node)
10611
10612         old_node_info = self.cfg.GetNodeInfo(secondary_node)
10613         assert old_node_info is not None
10614         if old_node_info.offline and not self.early_release:
10615           # doesn't make sense to delay the release
10616           self.early_release = True
10617           self.lu.LogInfo("Old secondary %s is offline, automatically enabling"
10618                           " early-release mode", secondary_node)
10619
10620       else:
10621         raise errors.ProgrammerError("Unhandled disk replace mode (%s)" %
10622                                      self.mode)
10623
10624       # If not specified all disks should be replaced
10625       if not self.disks:
10626         self.disks = range(len(self.instance.disks))
10627
10628     # TODO: This is ugly, but right now we can't distinguish between internal
10629     # submitted opcode and external one. We should fix that.
10630     if self.remote_node_info:
10631       # We change the node, lets verify it still meets instance policy
10632       new_group_info = self.cfg.GetNodeGroup(self.remote_node_info.group)
10633       ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(),
10634                                        new_group_info)
10635       _CheckTargetNodeIPolicy(self, ipolicy, instance, self.remote_node_info,
10636                               ignore=self.ignore_ipolicy)
10637
10638     # TODO: compute disk parameters
10639     primary_node_info = self.cfg.GetNodeInfo(instance.primary_node)
10640     secondary_node_info = self.cfg.GetNodeInfo(secondary_node)
10641     if primary_node_info.group != secondary_node_info.group:
10642       self.lu.LogInfo("The instance primary and secondary nodes are in two"
10643                       " different node groups; the disk parameters of the"
10644                       " primary node's group will be applied.")
10645
10646     self.diskparams = self.cfg.GetNodeGroup(primary_node_info.group).diskparams
10647
10648     for node in check_nodes:
10649       _CheckNodeOnline(self.lu, node)
10650
10651     touched_nodes = frozenset(node_name for node_name in [self.new_node,
10652                                                           self.other_node,
10653                                                           self.target_node]
10654                               if node_name is not None)
10655
10656     # Release unneeded node and node resource locks
10657     _ReleaseLocks(self.lu, locking.LEVEL_NODE, keep=touched_nodes)
10658     _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES, keep=touched_nodes)
10659
10660     # Release any owned node group
10661     if self.lu.glm.is_owned(locking.LEVEL_NODEGROUP):
10662       _ReleaseLocks(self.lu, locking.LEVEL_NODEGROUP)
10663
10664     # Check whether disks are valid
10665     for disk_idx in self.disks:
10666       instance.FindDisk(disk_idx)
10667
10668     # Get secondary node IP addresses
10669     self.node_secondary_ip = dict((name, node.secondary_ip) for (name, node)
10670                                   in self.cfg.GetMultiNodeInfo(touched_nodes))
10671
10672   def Exec(self, feedback_fn):
10673     """Execute disk replacement.
10674
10675     This dispatches the disk replacement to the appropriate handler.
10676
10677     """
10678     if self.delay_iallocator:
10679       self._CheckPrereq2()
10680
10681     if __debug__:
10682       # Verify owned locks before starting operation
10683       owned_nodes = self.lu.owned_locks(locking.LEVEL_NODE)
10684       assert set(owned_nodes) == set(self.node_secondary_ip), \
10685           ("Incorrect node locks, owning %s, expected %s" %
10686            (owned_nodes, self.node_secondary_ip.keys()))
10687       assert (self.lu.owned_locks(locking.LEVEL_NODE) ==
10688               self.lu.owned_locks(locking.LEVEL_NODE_RES))
10689
10690       owned_instances = self.lu.owned_locks(locking.LEVEL_INSTANCE)
10691       assert list(owned_instances) == [self.instance_name], \
10692           "Instance '%s' not locked" % self.instance_name
10693
10694       assert not self.lu.glm.is_owned(locking.LEVEL_NODEGROUP), \
10695           "Should not own any node group lock at this point"
10696
10697     if not self.disks:
10698       feedback_fn("No disks need replacement")
10699       return
10700
10701     feedback_fn("Replacing disk(s) %s for %s" %
10702                 (utils.CommaJoin(self.disks), self.instance.name))
10703
10704     activate_disks = (self.instance.admin_state != constants.ADMINST_UP)
10705
10706     # Activate the instance disks if we're replacing them on a down instance
10707     if activate_disks:
10708       _StartInstanceDisks(self.lu, self.instance, True)
10709
10710     try:
10711       # Should we replace the secondary node?
10712       if self.new_node is not None:
10713         fn = self._ExecDrbd8Secondary
10714       else:
10715         fn = self._ExecDrbd8DiskOnly
10716
10717       result = fn(feedback_fn)
10718     finally:
10719       # Deactivate the instance disks if we're replacing them on a
10720       # down instance
10721       if activate_disks:
10722         _SafeShutdownInstanceDisks(self.lu, self.instance)
10723
10724     assert not self.lu.owned_locks(locking.LEVEL_NODE)
10725
10726     if __debug__:
10727       # Verify owned locks
10728       owned_nodes = self.lu.owned_locks(locking.LEVEL_NODE_RES)
10729       nodes = frozenset(self.node_secondary_ip)
10730       assert ((self.early_release and not owned_nodes) or
10731               (not self.early_release and not (set(owned_nodes) - nodes))), \
10732         ("Not owning the correct locks, early_release=%s, owned=%r,"
10733          " nodes=%r" % (self.early_release, owned_nodes, nodes))
10734
10735     return result
10736
10737   def _CheckVolumeGroup(self, nodes):
10738     self.lu.LogInfo("Checking volume groups")
10739
10740     vgname = self.cfg.GetVGName()
10741
10742     # Make sure volume group exists on all involved nodes
10743     results = self.rpc.call_vg_list(nodes)
10744     if not results:
10745       raise errors.OpExecError("Can't list volume groups on the nodes")
10746
10747     for node in nodes:
10748       res = results[node]
10749       res.Raise("Error checking node %s" % node)
10750       if vgname not in res.payload:
10751         raise errors.OpExecError("Volume group '%s' not found on node %s" %
10752                                  (vgname, node))
10753
10754   def _CheckDisksExistence(self, nodes):
10755     # Check disk existence
10756     for idx, dev in enumerate(self.instance.disks):
10757       if idx not in self.disks:
10758         continue
10759
10760       for node in nodes:
10761         self.lu.LogInfo("Checking disk/%d on %s" % (idx, node))
10762         self.cfg.SetDiskID(dev, node)
10763
10764         result = self.rpc.call_blockdev_find(node, dev)
10765
10766         msg = result.fail_msg
10767         if msg or not result.payload:
10768           if not msg:
10769             msg = "disk not found"
10770           raise errors.OpExecError("Can't find disk/%d on node %s: %s" %
10771                                    (idx, node, msg))
10772
10773   def _CheckDisksConsistency(self, node_name, on_primary, ldisk):
10774     for idx, dev in enumerate(self.instance.disks):
10775       if idx not in self.disks:
10776         continue
10777
10778       self.lu.LogInfo("Checking disk/%d consistency on node %s" %
10779                       (idx, node_name))
10780
10781       if not _CheckDiskConsistency(self.lu, self.instance, dev, node_name,
10782                                    on_primary, ldisk=ldisk):
10783         raise errors.OpExecError("Node %s has degraded storage, unsafe to"
10784                                  " replace disks for instance %s" %
10785                                  (node_name, self.instance.name))
10786
10787   def _CreateNewStorage(self, node_name):
10788     """Create new storage on the primary or secondary node.
10789
10790     This is only used for same-node replaces, not for changing the
10791     secondary node, hence we don't want to modify the existing disk.
10792
10793     """
10794     iv_names = {}
10795
10796     for idx, dev in enumerate(self.instance.disks):
10797       if idx not in self.disks:
10798         continue
10799
10800       self.lu.LogInfo("Adding storage on %s for disk/%d" % (node_name, idx))
10801
10802       self.cfg.SetDiskID(dev, node_name)
10803
10804       lv_names = [".disk%d_%s" % (idx, suffix) for suffix in ["data", "meta"]]
10805       names = _GenerateUniqueNames(self.lu, lv_names)
10806
10807       _, data_p, meta_p = _ComputeLDParams(constants.DT_DRBD8, self.diskparams)
10808
10809       vg_data = dev.children[0].logical_id[0]
10810       lv_data = objects.Disk(dev_type=constants.LD_LV, size=dev.size,
10811                              logical_id=(vg_data, names[0]), params=data_p)
10812       vg_meta = dev.children[1].logical_id[0]
10813       lv_meta = objects.Disk(dev_type=constants.LD_LV, size=DRBD_META_SIZE,
10814                              logical_id=(vg_meta, names[1]), params=meta_p)
10815
10816       new_lvs = [lv_data, lv_meta]
10817       old_lvs = [child.Copy() for child in dev.children]
10818       iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
10819
10820       # we pass force_create=True to force the LVM creation
10821       for new_lv in new_lvs:
10822         _CreateBlockDev(self.lu, node_name, self.instance, new_lv, True,
10823                         _GetInstanceInfoText(self.instance), False)
10824
10825     return iv_names
10826
10827   def _CheckDevices(self, node_name, iv_names):
10828     for name, (dev, _, _) in iv_names.iteritems():
10829       self.cfg.SetDiskID(dev, node_name)
10830
10831       result = self.rpc.call_blockdev_find(node_name, dev)
10832
10833       msg = result.fail_msg
10834       if msg or not result.payload:
10835         if not msg:
10836           msg = "disk not found"
10837         raise errors.OpExecError("Can't find DRBD device %s: %s" %
10838                                  (name, msg))
10839
10840       if result.payload.is_degraded:
10841         raise errors.OpExecError("DRBD device %s is degraded!" % name)
10842
10843   def _RemoveOldStorage(self, node_name, iv_names):
10844     for name, (_, old_lvs, _) in iv_names.iteritems():
10845       self.lu.LogInfo("Remove logical volumes for %s" % name)
10846
10847       for lv in old_lvs:
10848         self.cfg.SetDiskID(lv, node_name)
10849
10850         msg = self.rpc.call_blockdev_remove(node_name, lv).fail_msg
10851         if msg:
10852           self.lu.LogWarning("Can't remove old LV: %s" % msg,
10853                              hint="remove unused LVs manually")
10854
10855   def _ExecDrbd8DiskOnly(self, feedback_fn): # pylint: disable=W0613
10856     """Replace a disk on the primary or secondary for DRBD 8.
10857
10858     The algorithm for replace is quite complicated:
10859
10860       1. for each disk to be replaced:
10861
10862         1. create new LVs on the target node with unique names
10863         1. detach old LVs from the drbd device
10864         1. rename old LVs to name_replaced.<time_t>
10865         1. rename new LVs to old LVs
10866         1. attach the new LVs (with the old names now) to the drbd device
10867
10868       1. wait for sync across all devices
10869
10870       1. for each modified disk:
10871
10872         1. remove old LVs (which have the name name_replaces.<time_t>)
10873
10874     Failures are not very well handled.
10875
10876     """
10877     steps_total = 6
10878
10879     # Step: check device activation
10880     self.lu.LogStep(1, steps_total, "Check device existence")
10881     self._CheckDisksExistence([self.other_node, self.target_node])
10882     self._CheckVolumeGroup([self.target_node, self.other_node])
10883
10884     # Step: check other node consistency
10885     self.lu.LogStep(2, steps_total, "Check peer consistency")
10886     self._CheckDisksConsistency(self.other_node,
10887                                 self.other_node == self.instance.primary_node,
10888                                 False)
10889
10890     # Step: create new storage
10891     self.lu.LogStep(3, steps_total, "Allocate new storage")
10892     iv_names = self._CreateNewStorage(self.target_node)
10893
10894     # Step: for each lv, detach+rename*2+attach
10895     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
10896     for dev, old_lvs, new_lvs in iv_names.itervalues():
10897       self.lu.LogInfo("Detaching %s drbd from local storage" % dev.iv_name)
10898
10899       result = self.rpc.call_blockdev_removechildren(self.target_node, dev,
10900                                                      old_lvs)
10901       result.Raise("Can't detach drbd from local storage on node"
10902                    " %s for device %s" % (self.target_node, dev.iv_name))
10903       #dev.children = []
10904       #cfg.Update(instance)
10905
10906       # ok, we created the new LVs, so now we know we have the needed
10907       # storage; as such, we proceed on the target node to rename
10908       # old_lv to _old, and new_lv to old_lv; note that we rename LVs
10909       # using the assumption that logical_id == physical_id (which in
10910       # turn is the unique_id on that node)
10911
10912       # FIXME(iustin): use a better name for the replaced LVs
10913       temp_suffix = int(time.time())
10914       ren_fn = lambda d, suff: (d.physical_id[0],
10915                                 d.physical_id[1] + "_replaced-%s" % suff)
10916
10917       # Build the rename list based on what LVs exist on the node
10918       rename_old_to_new = []
10919       for to_ren in old_lvs:
10920         result = self.rpc.call_blockdev_find(self.target_node, to_ren)
10921         if not result.fail_msg and result.payload:
10922           # device exists
10923           rename_old_to_new.append((to_ren, ren_fn(to_ren, temp_suffix)))
10924
10925       self.lu.LogInfo("Renaming the old LVs on the target node")
10926       result = self.rpc.call_blockdev_rename(self.target_node,
10927                                              rename_old_to_new)
10928       result.Raise("Can't rename old LVs on node %s" % self.target_node)
10929
10930       # Now we rename the new LVs to the old LVs
10931       self.lu.LogInfo("Renaming the new LVs on the target node")
10932       rename_new_to_old = [(new, old.physical_id)
10933                            for old, new in zip(old_lvs, new_lvs)]
10934       result = self.rpc.call_blockdev_rename(self.target_node,
10935                                              rename_new_to_old)
10936       result.Raise("Can't rename new LVs on node %s" % self.target_node)
10937
10938       # Intermediate steps of in memory modifications
10939       for old, new in zip(old_lvs, new_lvs):
10940         new.logical_id = old.logical_id
10941         self.cfg.SetDiskID(new, self.target_node)
10942
10943       # We need to modify old_lvs so that removal later removes the
10944       # right LVs, not the newly added ones; note that old_lvs is a
10945       # copy here
10946       for disk in old_lvs:
10947         disk.logical_id = ren_fn(disk, temp_suffix)
10948         self.cfg.SetDiskID(disk, self.target_node)
10949
10950       # Now that the new lvs have the old name, we can add them to the device
10951       self.lu.LogInfo("Adding new mirror component on %s" % self.target_node)
10952       result = self.rpc.call_blockdev_addchildren(self.target_node,
10953                                                   (dev, self.instance),
10954                                                   (new_lvs, self.instance))
10955       msg = result.fail_msg
10956       if msg:
10957         for new_lv in new_lvs:
10958           msg2 = self.rpc.call_blockdev_remove(self.target_node,
10959                                                new_lv).fail_msg
10960           if msg2:
10961             self.lu.LogWarning("Can't rollback device %s: %s", dev, msg2,
10962                                hint=("cleanup manually the unused logical"
10963                                      "volumes"))
10964         raise errors.OpExecError("Can't add local storage to drbd: %s" % msg)
10965
10966     cstep = itertools.count(5)
10967
10968     if self.early_release:
10969       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
10970       self._RemoveOldStorage(self.target_node, iv_names)
10971       # TODO: Check if releasing locks early still makes sense
10972       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
10973     else:
10974       # Release all resource locks except those used by the instance
10975       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES,
10976                     keep=self.node_secondary_ip.keys())
10977
10978     # Release all node locks while waiting for sync
10979     _ReleaseLocks(self.lu, locking.LEVEL_NODE)
10980
10981     # TODO: Can the instance lock be downgraded here? Take the optional disk
10982     # shutdown in the caller into consideration.
10983
10984     # Wait for sync
10985     # This can fail as the old devices are degraded and _WaitForSync
10986     # does a combined result over all disks, so we don't check its return value
10987     self.lu.LogStep(cstep.next(), steps_total, "Sync devices")
10988     _WaitForSync(self.lu, self.instance)
10989
10990     # Check all devices manually
10991     self._CheckDevices(self.instance.primary_node, iv_names)
10992
10993     # Step: remove old storage
10994     if not self.early_release:
10995       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
10996       self._RemoveOldStorage(self.target_node, iv_names)
10997
10998   def _ExecDrbd8Secondary(self, feedback_fn):
10999     """Replace the secondary node for DRBD 8.
11000
11001     The algorithm for replace is quite complicated:
11002       - for all disks of the instance:
11003         - create new LVs on the new node with same names
11004         - shutdown the drbd device on the old secondary
11005         - disconnect the drbd network on the primary
11006         - create the drbd device on the new secondary
11007         - network attach the drbd on the primary, using an artifice:
11008           the drbd code for Attach() will connect to the network if it
11009           finds a device which is connected to the good local disks but
11010           not network enabled
11011       - wait for sync across all devices
11012       - remove all disks from the old secondary
11013
11014     Failures are not very well handled.
11015
11016     """
11017     steps_total = 6
11018
11019     pnode = self.instance.primary_node
11020
11021     # Step: check device activation
11022     self.lu.LogStep(1, steps_total, "Check device existence")
11023     self._CheckDisksExistence([self.instance.primary_node])
11024     self._CheckVolumeGroup([self.instance.primary_node])
11025
11026     # Step: check other node consistency
11027     self.lu.LogStep(2, steps_total, "Check peer consistency")
11028     self._CheckDisksConsistency(self.instance.primary_node, True, True)
11029
11030     # Step: create new storage
11031     self.lu.LogStep(3, steps_total, "Allocate new storage")
11032     for idx, dev in enumerate(self.instance.disks):
11033       self.lu.LogInfo("Adding new local storage on %s for disk/%d" %
11034                       (self.new_node, idx))
11035       # we pass force_create=True to force LVM creation
11036       for new_lv in dev.children:
11037         _CreateBlockDev(self.lu, self.new_node, self.instance, new_lv, True,
11038                         _GetInstanceInfoText(self.instance), False)
11039
11040     # Step 4: dbrd minors and drbd setups changes
11041     # after this, we must manually remove the drbd minors on both the
11042     # error and the success paths
11043     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
11044     minors = self.cfg.AllocateDRBDMinor([self.new_node
11045                                          for dev in self.instance.disks],
11046                                         self.instance.name)
11047     logging.debug("Allocated minors %r", minors)
11048
11049     iv_names = {}
11050     for idx, (dev, new_minor) in enumerate(zip(self.instance.disks, minors)):
11051       self.lu.LogInfo("activating a new drbd on %s for disk/%d" %
11052                       (self.new_node, idx))
11053       # create new devices on new_node; note that we create two IDs:
11054       # one without port, so the drbd will be activated without
11055       # networking information on the new node at this stage, and one
11056       # with network, for the latter activation in step 4
11057       (o_node1, o_node2, o_port, o_minor1, o_minor2, o_secret) = dev.logical_id
11058       if self.instance.primary_node == o_node1:
11059         p_minor = o_minor1
11060       else:
11061         assert self.instance.primary_node == o_node2, "Three-node instance?"
11062         p_minor = o_minor2
11063
11064       new_alone_id = (self.instance.primary_node, self.new_node, None,
11065                       p_minor, new_minor, o_secret)
11066       new_net_id = (self.instance.primary_node, self.new_node, o_port,
11067                     p_minor, new_minor, o_secret)
11068
11069       iv_names[idx] = (dev, dev.children, new_net_id)
11070       logging.debug("Allocated new_minor: %s, new_logical_id: %s", new_minor,
11071                     new_net_id)
11072       drbd_params, _, _ = _ComputeLDParams(constants.DT_DRBD8, self.diskparams)
11073       new_drbd = objects.Disk(dev_type=constants.LD_DRBD8,
11074                               logical_id=new_alone_id,
11075                               children=dev.children,
11076                               size=dev.size,
11077                               params=drbd_params)
11078       try:
11079         _CreateSingleBlockDev(self.lu, self.new_node, self.instance, new_drbd,
11080                               _GetInstanceInfoText(self.instance), False)
11081       except errors.GenericError:
11082         self.cfg.ReleaseDRBDMinors(self.instance.name)
11083         raise
11084
11085     # We have new devices, shutdown the drbd on the old secondary
11086     for idx, dev in enumerate(self.instance.disks):
11087       self.lu.LogInfo("Shutting down drbd for disk/%d on old node" % idx)
11088       self.cfg.SetDiskID(dev, self.target_node)
11089       msg = self.rpc.call_blockdev_shutdown(self.target_node, dev).fail_msg
11090       if msg:
11091         self.lu.LogWarning("Failed to shutdown drbd for disk/%d on old"
11092                            "node: %s" % (idx, msg),
11093                            hint=("Please cleanup this device manually as"
11094                                  " soon as possible"))
11095
11096     self.lu.LogInfo("Detaching primary drbds from the network (=> standalone)")
11097     result = self.rpc.call_drbd_disconnect_net([pnode], self.node_secondary_ip,
11098                                                self.instance.disks)[pnode]
11099
11100     msg = result.fail_msg
11101     if msg:
11102       # detaches didn't succeed (unlikely)
11103       self.cfg.ReleaseDRBDMinors(self.instance.name)
11104       raise errors.OpExecError("Can't detach the disks from the network on"
11105                                " old node: %s" % (msg,))
11106
11107     # if we managed to detach at least one, we update all the disks of
11108     # the instance to point to the new secondary
11109     self.lu.LogInfo("Updating instance configuration")
11110     for dev, _, new_logical_id in iv_names.itervalues():
11111       dev.logical_id = new_logical_id
11112       self.cfg.SetDiskID(dev, self.instance.primary_node)
11113
11114     self.cfg.Update(self.instance, feedback_fn)
11115
11116     # Release all node locks (the configuration has been updated)
11117     _ReleaseLocks(self.lu, locking.LEVEL_NODE)
11118
11119     # and now perform the drbd attach
11120     self.lu.LogInfo("Attaching primary drbds to new secondary"
11121                     " (standalone => connected)")
11122     result = self.rpc.call_drbd_attach_net([self.instance.primary_node,
11123                                             self.new_node],
11124                                            self.node_secondary_ip,
11125                                            (self.instance.disks, self.instance),
11126                                            self.instance.name,
11127                                            False)
11128     for to_node, to_result in result.items():
11129       msg = to_result.fail_msg
11130       if msg:
11131         self.lu.LogWarning("Can't attach drbd disks on node %s: %s",
11132                            to_node, msg,
11133                            hint=("please do a gnt-instance info to see the"
11134                                  " status of disks"))
11135
11136     cstep = itertools.count(5)
11137
11138     if self.early_release:
11139       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11140       self._RemoveOldStorage(self.target_node, iv_names)
11141       # TODO: Check if releasing locks early still makes sense
11142       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
11143     else:
11144       # Release all resource locks except those used by the instance
11145       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES,
11146                     keep=self.node_secondary_ip.keys())
11147
11148     # TODO: Can the instance lock be downgraded here? Take the optional disk
11149     # shutdown in the caller into consideration.
11150
11151     # Wait for sync
11152     # This can fail as the old devices are degraded and _WaitForSync
11153     # does a combined result over all disks, so we don't check its return value
11154     self.lu.LogStep(cstep.next(), steps_total, "Sync devices")
11155     _WaitForSync(self.lu, self.instance)
11156
11157     # Check all devices manually
11158     self._CheckDevices(self.instance.primary_node, iv_names)
11159
11160     # Step: remove old storage
11161     if not self.early_release:
11162       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11163       self._RemoveOldStorage(self.target_node, iv_names)
11164
11165
11166 class LURepairNodeStorage(NoHooksLU):
11167   """Repairs the volume group on a node.
11168
11169   """
11170   REQ_BGL = False
11171
11172   def CheckArguments(self):
11173     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
11174
11175     storage_type = self.op.storage_type
11176
11177     if (constants.SO_FIX_CONSISTENCY not in
11178         constants.VALID_STORAGE_OPERATIONS.get(storage_type, [])):
11179       raise errors.OpPrereqError("Storage units of type '%s' can not be"
11180                                  " repaired" % storage_type,
11181                                  errors.ECODE_INVAL)
11182
11183   def ExpandNames(self):
11184     self.needed_locks = {
11185       locking.LEVEL_NODE: [self.op.node_name],
11186       }
11187
11188   def _CheckFaultyDisks(self, instance, node_name):
11189     """Ensure faulty disks abort the opcode or at least warn."""
11190     try:
11191       if _FindFaultyInstanceDisks(self.cfg, self.rpc, instance,
11192                                   node_name, True):
11193         raise errors.OpPrereqError("Instance '%s' has faulty disks on"
11194                                    " node '%s'" % (instance.name, node_name),
11195                                    errors.ECODE_STATE)
11196     except errors.OpPrereqError, err:
11197       if self.op.ignore_consistency:
11198         self.proc.LogWarning(str(err.args[0]))
11199       else:
11200         raise
11201
11202   def CheckPrereq(self):
11203     """Check prerequisites.
11204
11205     """
11206     # Check whether any instance on this node has faulty disks
11207     for inst in _GetNodeInstances(self.cfg, self.op.node_name):
11208       if inst.admin_state != constants.ADMINST_UP:
11209         continue
11210       check_nodes = set(inst.all_nodes)
11211       check_nodes.discard(self.op.node_name)
11212       for inst_node_name in check_nodes:
11213         self._CheckFaultyDisks(inst, inst_node_name)
11214
11215   def Exec(self, feedback_fn):
11216     feedback_fn("Repairing storage unit '%s' on %s ..." %
11217                 (self.op.name, self.op.node_name))
11218
11219     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
11220     result = self.rpc.call_storage_execute(self.op.node_name,
11221                                            self.op.storage_type, st_args,
11222                                            self.op.name,
11223                                            constants.SO_FIX_CONSISTENCY)
11224     result.Raise("Failed to repair storage unit '%s' on %s" %
11225                  (self.op.name, self.op.node_name))
11226
11227
11228 class LUNodeEvacuate(NoHooksLU):
11229   """Evacuates instances off a list of nodes.
11230
11231   """
11232   REQ_BGL = False
11233
11234   _MODE2IALLOCATOR = {
11235     constants.NODE_EVAC_PRI: constants.IALLOCATOR_NEVAC_PRI,
11236     constants.NODE_EVAC_SEC: constants.IALLOCATOR_NEVAC_SEC,
11237     constants.NODE_EVAC_ALL: constants.IALLOCATOR_NEVAC_ALL,
11238     }
11239   assert frozenset(_MODE2IALLOCATOR.keys()) == constants.NODE_EVAC_MODES
11240   assert (frozenset(_MODE2IALLOCATOR.values()) ==
11241           constants.IALLOCATOR_NEVAC_MODES)
11242
11243   def CheckArguments(self):
11244     _CheckIAllocatorOrNode(self, "iallocator", "remote_node")
11245
11246   def ExpandNames(self):
11247     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
11248
11249     if self.op.remote_node is not None:
11250       self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
11251       assert self.op.remote_node
11252
11253       if self.op.remote_node == self.op.node_name:
11254         raise errors.OpPrereqError("Can not use evacuated node as a new"
11255                                    " secondary node", errors.ECODE_INVAL)
11256
11257       if self.op.mode != constants.NODE_EVAC_SEC:
11258         raise errors.OpPrereqError("Without the use of an iallocator only"
11259                                    " secondary instances can be evacuated",
11260                                    errors.ECODE_INVAL)
11261
11262     # Declare locks
11263     self.share_locks = _ShareAll()
11264     self.needed_locks = {
11265       locking.LEVEL_INSTANCE: [],
11266       locking.LEVEL_NODEGROUP: [],
11267       locking.LEVEL_NODE: [],
11268       }
11269
11270     # Determine nodes (via group) optimistically, needs verification once locks
11271     # have been acquired
11272     self.lock_nodes = self._DetermineNodes()
11273
11274   def _DetermineNodes(self):
11275     """Gets the list of nodes to operate on.
11276
11277     """
11278     if self.op.remote_node is None:
11279       # Iallocator will choose any node(s) in the same group
11280       group_nodes = self.cfg.GetNodeGroupMembersByNodes([self.op.node_name])
11281     else:
11282       group_nodes = frozenset([self.op.remote_node])
11283
11284     # Determine nodes to be locked
11285     return set([self.op.node_name]) | group_nodes
11286
11287   def _DetermineInstances(self):
11288     """Builds list of instances to operate on.
11289
11290     """
11291     assert self.op.mode in constants.NODE_EVAC_MODES
11292
11293     if self.op.mode == constants.NODE_EVAC_PRI:
11294       # Primary instances only
11295       inst_fn = _GetNodePrimaryInstances
11296       assert self.op.remote_node is None, \
11297         "Evacuating primary instances requires iallocator"
11298     elif self.op.mode == constants.NODE_EVAC_SEC:
11299       # Secondary instances only
11300       inst_fn = _GetNodeSecondaryInstances
11301     else:
11302       # All instances
11303       assert self.op.mode == constants.NODE_EVAC_ALL
11304       inst_fn = _GetNodeInstances
11305       # TODO: In 2.6, change the iallocator interface to take an evacuation mode
11306       # per instance
11307       raise errors.OpPrereqError("Due to an issue with the iallocator"
11308                                  " interface it is not possible to evacuate"
11309                                  " all instances at once; specify explicitly"
11310                                  " whether to evacuate primary or secondary"
11311                                  " instances",
11312                                  errors.ECODE_INVAL)
11313
11314     return inst_fn(self.cfg, self.op.node_name)
11315
11316   def DeclareLocks(self, level):
11317     if level == locking.LEVEL_INSTANCE:
11318       # Lock instances optimistically, needs verification once node and group
11319       # locks have been acquired
11320       self.needed_locks[locking.LEVEL_INSTANCE] = \
11321         set(i.name for i in self._DetermineInstances())
11322
11323     elif level == locking.LEVEL_NODEGROUP:
11324       # Lock node groups for all potential target nodes optimistically, needs
11325       # verification once nodes have been acquired
11326       self.needed_locks[locking.LEVEL_NODEGROUP] = \
11327         self.cfg.GetNodeGroupsFromNodes(self.lock_nodes)
11328
11329     elif level == locking.LEVEL_NODE:
11330       self.needed_locks[locking.LEVEL_NODE] = self.lock_nodes
11331
11332   def CheckPrereq(self):
11333     # Verify locks
11334     owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
11335     owned_nodes = self.owned_locks(locking.LEVEL_NODE)
11336     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
11337
11338     need_nodes = self._DetermineNodes()
11339
11340     if not owned_nodes.issuperset(need_nodes):
11341       raise errors.OpPrereqError("Nodes in same group as '%s' changed since"
11342                                  " locks were acquired, current nodes are"
11343                                  " are '%s', used to be '%s'; retry the"
11344                                  " operation" %
11345                                  (self.op.node_name,
11346                                   utils.CommaJoin(need_nodes),
11347                                   utils.CommaJoin(owned_nodes)),
11348                                  errors.ECODE_STATE)
11349
11350     wanted_groups = self.cfg.GetNodeGroupsFromNodes(owned_nodes)
11351     if owned_groups != wanted_groups:
11352       raise errors.OpExecError("Node groups changed since locks were acquired,"
11353                                " current groups are '%s', used to be '%s';"
11354                                " retry the operation" %
11355                                (utils.CommaJoin(wanted_groups),
11356                                 utils.CommaJoin(owned_groups)))
11357
11358     # Determine affected instances
11359     self.instances = self._DetermineInstances()
11360     self.instance_names = [i.name for i in self.instances]
11361
11362     if set(self.instance_names) != owned_instances:
11363       raise errors.OpExecError("Instances on node '%s' changed since locks"
11364                                " were acquired, current instances are '%s',"
11365                                " used to be '%s'; retry the operation" %
11366                                (self.op.node_name,
11367                                 utils.CommaJoin(self.instance_names),
11368                                 utils.CommaJoin(owned_instances)))
11369
11370     if self.instance_names:
11371       self.LogInfo("Evacuating instances from node '%s': %s",
11372                    self.op.node_name,
11373                    utils.CommaJoin(utils.NiceSort(self.instance_names)))
11374     else:
11375       self.LogInfo("No instances to evacuate from node '%s'",
11376                    self.op.node_name)
11377
11378     if self.op.remote_node is not None:
11379       for i in self.instances:
11380         if i.primary_node == self.op.remote_node:
11381           raise errors.OpPrereqError("Node %s is the primary node of"
11382                                      " instance %s, cannot use it as"
11383                                      " secondary" %
11384                                      (self.op.remote_node, i.name),
11385                                      errors.ECODE_INVAL)
11386
11387   def Exec(self, feedback_fn):
11388     assert (self.op.iallocator is not None) ^ (self.op.remote_node is not None)
11389
11390     if not self.instance_names:
11391       # No instances to evacuate
11392       jobs = []
11393
11394     elif self.op.iallocator is not None:
11395       # TODO: Implement relocation to other group
11396       ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_NODE_EVAC,
11397                        evac_mode=self._MODE2IALLOCATOR[self.op.mode],
11398                        instances=list(self.instance_names))
11399
11400       ial.Run(self.op.iallocator)
11401
11402       if not ial.success:
11403         raise errors.OpPrereqError("Can't compute node evacuation using"
11404                                    " iallocator '%s': %s" %
11405                                    (self.op.iallocator, ial.info),
11406                                    errors.ECODE_NORES)
11407
11408       jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, True)
11409
11410     elif self.op.remote_node is not None:
11411       assert self.op.mode == constants.NODE_EVAC_SEC
11412       jobs = [
11413         [opcodes.OpInstanceReplaceDisks(instance_name=instance_name,
11414                                         remote_node=self.op.remote_node,
11415                                         disks=[],
11416                                         mode=constants.REPLACE_DISK_CHG,
11417                                         early_release=self.op.early_release)]
11418         for instance_name in self.instance_names
11419         ]
11420
11421     else:
11422       raise errors.ProgrammerError("No iallocator or remote node")
11423
11424     return ResultWithJobs(jobs)
11425
11426
11427 def _SetOpEarlyRelease(early_release, op):
11428   """Sets C{early_release} flag on opcodes if available.
11429
11430   """
11431   try:
11432     op.early_release = early_release
11433   except AttributeError:
11434     assert not isinstance(op, opcodes.OpInstanceReplaceDisks)
11435
11436   return op
11437
11438
11439 def _NodeEvacDest(use_nodes, group, nodes):
11440   """Returns group or nodes depending on caller's choice.
11441
11442   """
11443   if use_nodes:
11444     return utils.CommaJoin(nodes)
11445   else:
11446     return group
11447
11448
11449 def _LoadNodeEvacResult(lu, alloc_result, early_release, use_nodes):
11450   """Unpacks the result of change-group and node-evacuate iallocator requests.
11451
11452   Iallocator modes L{constants.IALLOCATOR_MODE_NODE_EVAC} and
11453   L{constants.IALLOCATOR_MODE_CHG_GROUP}.
11454
11455   @type lu: L{LogicalUnit}
11456   @param lu: Logical unit instance
11457   @type alloc_result: tuple/list
11458   @param alloc_result: Result from iallocator
11459   @type early_release: bool
11460   @param early_release: Whether to release locks early if possible
11461   @type use_nodes: bool
11462   @param use_nodes: Whether to display node names instead of groups
11463
11464   """
11465   (moved, failed, jobs) = alloc_result
11466
11467   if failed:
11468     failreason = utils.CommaJoin("%s (%s)" % (name, reason)
11469                                  for (name, reason) in failed)
11470     lu.LogWarning("Unable to evacuate instances %s", failreason)
11471     raise errors.OpExecError("Unable to evacuate instances %s" % failreason)
11472
11473   if moved:
11474     lu.LogInfo("Instances to be moved: %s",
11475                utils.CommaJoin("%s (to %s)" %
11476                                (name, _NodeEvacDest(use_nodes, group, nodes))
11477                                for (name, group, nodes) in moved))
11478
11479   return [map(compat.partial(_SetOpEarlyRelease, early_release),
11480               map(opcodes.OpCode.LoadOpCode, ops))
11481           for ops in jobs]
11482
11483
11484 class LUInstanceGrowDisk(LogicalUnit):
11485   """Grow a disk of an instance.
11486
11487   """
11488   HPATH = "disk-grow"
11489   HTYPE = constants.HTYPE_INSTANCE
11490   REQ_BGL = False
11491
11492   def ExpandNames(self):
11493     self._ExpandAndLockInstance()
11494     self.needed_locks[locking.LEVEL_NODE] = []
11495     self.needed_locks[locking.LEVEL_NODE_RES] = []
11496     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
11497     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
11498
11499   def DeclareLocks(self, level):
11500     if level == locking.LEVEL_NODE:
11501       self._LockInstancesNodes()
11502     elif level == locking.LEVEL_NODE_RES:
11503       # Copy node locks
11504       self.needed_locks[locking.LEVEL_NODE_RES] = \
11505         self.needed_locks[locking.LEVEL_NODE][:]
11506
11507   def BuildHooksEnv(self):
11508     """Build hooks env.
11509
11510     This runs on the master, the primary and all the secondaries.
11511
11512     """
11513     env = {
11514       "DISK": self.op.disk,
11515       "AMOUNT": self.op.amount,
11516       "ABSOLUTE": self.op.absolute,
11517       }
11518     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
11519     return env
11520
11521   def BuildHooksNodes(self):
11522     """Build hooks nodes.
11523
11524     """
11525     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
11526     return (nl, nl)
11527
11528   def CheckPrereq(self):
11529     """Check prerequisites.
11530
11531     This checks that the instance is in the cluster.
11532
11533     """
11534     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
11535     assert instance is not None, \
11536       "Cannot retrieve locked instance %s" % self.op.instance_name
11537     nodenames = list(instance.all_nodes)
11538     for node in nodenames:
11539       _CheckNodeOnline(self, node)
11540
11541     self.instance = instance
11542
11543     if instance.disk_template not in constants.DTS_GROWABLE:
11544       raise errors.OpPrereqError("Instance's disk layout does not support"
11545                                  " growing", errors.ECODE_INVAL)
11546
11547     self.disk = instance.FindDisk(self.op.disk)
11548
11549     if self.op.absolute:
11550       self.target = self.op.amount
11551       self.delta = self.target - self.disk.size
11552       if self.delta < 0:
11553         raise errors.OpPrereqError("Requested size (%s) is smaller than "
11554                                    "current disk size (%s)" %
11555                                    (utils.FormatUnit(self.target, "h"),
11556                                     utils.FormatUnit(self.disk.size, "h")),
11557                                    errors.ECODE_STATE)
11558     else:
11559       self.delta = self.op.amount
11560       self.target = self.disk.size + self.delta
11561       if self.delta < 0:
11562         raise errors.OpPrereqError("Requested increment (%s) is negative" %
11563                                    utils.FormatUnit(self.delta, "h"),
11564                                    errors.ECODE_INVAL)
11565
11566     if instance.disk_template not in (constants.DT_FILE,
11567                                       constants.DT_SHARED_FILE,
11568                                       constants.DT_RBD):
11569       # TODO: check the free disk space for file, when that feature will be
11570       # supported
11571       _CheckNodesFreeDiskPerVG(self, nodenames,
11572                                self.disk.ComputeGrowth(self.delta))
11573
11574   def Exec(self, feedback_fn):
11575     """Execute disk grow.
11576
11577     """
11578     instance = self.instance
11579     disk = self.disk
11580
11581     assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
11582     assert (self.owned_locks(locking.LEVEL_NODE) ==
11583             self.owned_locks(locking.LEVEL_NODE_RES))
11584
11585     disks_ok, _ = _AssembleInstanceDisks(self, self.instance, disks=[disk])
11586     if not disks_ok:
11587       raise errors.OpExecError("Cannot activate block device to grow")
11588
11589     feedback_fn("Growing disk %s of instance '%s' by %s to %s" %
11590                 (self.op.disk, instance.name,
11591                  utils.FormatUnit(self.delta, "h"),
11592                  utils.FormatUnit(self.target, "h")))
11593
11594     # First run all grow ops in dry-run mode
11595     for node in instance.all_nodes:
11596       self.cfg.SetDiskID(disk, node)
11597       result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
11598                                            True)
11599       result.Raise("Grow request failed to node %s" % node)
11600
11601     # We know that (as far as we can test) operations across different
11602     # nodes will succeed, time to run it for real
11603     for node in instance.all_nodes:
11604       self.cfg.SetDiskID(disk, node)
11605       result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
11606                                            False)
11607       result.Raise("Grow request failed to node %s" % node)
11608
11609       # TODO: Rewrite code to work properly
11610       # DRBD goes into sync mode for a short amount of time after executing the
11611       # "resize" command. DRBD 8.x below version 8.0.13 contains a bug whereby
11612       # calling "resize" in sync mode fails. Sleeping for a short amount of
11613       # time is a work-around.
11614       time.sleep(5)
11615
11616     disk.RecordGrow(self.delta)
11617     self.cfg.Update(instance, feedback_fn)
11618
11619     # Changes have been recorded, release node lock
11620     _ReleaseLocks(self, locking.LEVEL_NODE)
11621
11622     # Downgrade lock while waiting for sync
11623     self.glm.downgrade(locking.LEVEL_INSTANCE)
11624
11625     if self.op.wait_for_sync:
11626       disk_abort = not _WaitForSync(self, instance, disks=[disk])
11627       if disk_abort:
11628         self.proc.LogWarning("Disk sync-ing has not returned a good"
11629                              " status; please check the instance")
11630       if instance.admin_state != constants.ADMINST_UP:
11631         _SafeShutdownInstanceDisks(self, instance, disks=[disk])
11632     elif instance.admin_state != constants.ADMINST_UP:
11633       self.proc.LogWarning("Not shutting down the disk even if the instance is"
11634                            " not supposed to be running because no wait for"
11635                            " sync mode was requested")
11636
11637     assert self.owned_locks(locking.LEVEL_NODE_RES)
11638     assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
11639
11640
11641 class LUInstanceQueryData(NoHooksLU):
11642   """Query runtime instance data.
11643
11644   """
11645   REQ_BGL = False
11646
11647   def ExpandNames(self):
11648     self.needed_locks = {}
11649
11650     # Use locking if requested or when non-static information is wanted
11651     if not (self.op.static or self.op.use_locking):
11652       self.LogWarning("Non-static data requested, locks need to be acquired")
11653       self.op.use_locking = True
11654
11655     if self.op.instances or not self.op.use_locking:
11656       # Expand instance names right here
11657       self.wanted_names = _GetWantedInstances(self, self.op.instances)
11658     else:
11659       # Will use acquired locks
11660       self.wanted_names = None
11661
11662     if self.op.use_locking:
11663       self.share_locks = _ShareAll()
11664
11665       if self.wanted_names is None:
11666         self.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET
11667       else:
11668         self.needed_locks[locking.LEVEL_INSTANCE] = self.wanted_names
11669
11670       self.needed_locks[locking.LEVEL_NODEGROUP] = []
11671       self.needed_locks[locking.LEVEL_NODE] = []
11672       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
11673
11674   def DeclareLocks(self, level):
11675     if self.op.use_locking:
11676       if level == locking.LEVEL_NODEGROUP:
11677         owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
11678
11679         # Lock all groups used by instances optimistically; this requires going
11680         # via the node before it's locked, requiring verification later on
11681         self.needed_locks[locking.LEVEL_NODEGROUP] = \
11682           frozenset(group_uuid
11683                     for instance_name in owned_instances
11684                     for group_uuid in
11685                       self.cfg.GetInstanceNodeGroups(instance_name))
11686
11687       elif level == locking.LEVEL_NODE:
11688         self._LockInstancesNodes()
11689
11690   def CheckPrereq(self):
11691     """Check prerequisites.
11692
11693     This only checks the optional instance list against the existing names.
11694
11695     """
11696     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
11697     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
11698     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
11699
11700     if self.wanted_names is None:
11701       assert self.op.use_locking, "Locking was not used"
11702       self.wanted_names = owned_instances
11703
11704     instances = dict(self.cfg.GetMultiInstanceInfo(self.wanted_names))
11705
11706     if self.op.use_locking:
11707       _CheckInstancesNodeGroups(self.cfg, instances, owned_groups, owned_nodes,
11708                                 None)
11709     else:
11710       assert not (owned_instances or owned_groups or owned_nodes)
11711
11712     self.wanted_instances = instances.values()
11713
11714   def _ComputeBlockdevStatus(self, node, instance, dev):
11715     """Returns the status of a block device
11716
11717     """
11718     if self.op.static or not node:
11719       return None
11720
11721     self.cfg.SetDiskID(dev, node)
11722
11723     result = self.rpc.call_blockdev_find(node, dev)
11724     if result.offline:
11725       return None
11726
11727     result.Raise("Can't compute disk status for %s" % instance.name)
11728
11729     status = result.payload
11730     if status is None:
11731       return None
11732
11733     return (status.dev_path, status.major, status.minor,
11734             status.sync_percent, status.estimated_time,
11735             status.is_degraded, status.ldisk_status)
11736
11737   def _ComputeDiskStatus(self, instance, snode, dev):
11738     """Compute block device status.
11739
11740     """
11741     if dev.dev_type in constants.LDS_DRBD:
11742       # we change the snode then (otherwise we use the one passed in)
11743       if dev.logical_id[0] == instance.primary_node:
11744         snode = dev.logical_id[1]
11745       else:
11746         snode = dev.logical_id[0]
11747
11748     dev_pstatus = self._ComputeBlockdevStatus(instance.primary_node,
11749                                               instance, dev)
11750     dev_sstatus = self._ComputeBlockdevStatus(snode, instance, dev)
11751
11752     if dev.children:
11753       dev_children = map(compat.partial(self._ComputeDiskStatus,
11754                                         instance, snode),
11755                          dev.children)
11756     else:
11757       dev_children = []
11758
11759     return {
11760       "iv_name": dev.iv_name,
11761       "dev_type": dev.dev_type,
11762       "logical_id": dev.logical_id,
11763       "physical_id": dev.physical_id,
11764       "pstatus": dev_pstatus,
11765       "sstatus": dev_sstatus,
11766       "children": dev_children,
11767       "mode": dev.mode,
11768       "size": dev.size,
11769       }
11770
11771   def Exec(self, feedback_fn):
11772     """Gather and return data"""
11773     result = {}
11774
11775     cluster = self.cfg.GetClusterInfo()
11776
11777     node_names = itertools.chain(*(i.all_nodes for i in self.wanted_instances))
11778     nodes = dict(self.cfg.GetMultiNodeInfo(node_names))
11779
11780     groups = dict(self.cfg.GetMultiNodeGroupInfo(node.group
11781                                                  for node in nodes.values()))
11782
11783     group2name_fn = lambda uuid: groups[uuid].name
11784
11785     for instance in self.wanted_instances:
11786       pnode = nodes[instance.primary_node]
11787
11788       if self.op.static or pnode.offline:
11789         remote_state = None
11790         if pnode.offline:
11791           self.LogWarning("Primary node %s is marked offline, returning static"
11792                           " information only for instance %s" %
11793                           (pnode.name, instance.name))
11794       else:
11795         remote_info = self.rpc.call_instance_info(instance.primary_node,
11796                                                   instance.name,
11797                                                   instance.hypervisor)
11798         remote_info.Raise("Error checking node %s" % instance.primary_node)
11799         remote_info = remote_info.payload
11800         if remote_info and "state" in remote_info:
11801           remote_state = "up"
11802         else:
11803           if instance.admin_state == constants.ADMINST_UP:
11804             remote_state = "down"
11805           else:
11806             remote_state = instance.admin_state
11807
11808       disks = map(compat.partial(self._ComputeDiskStatus, instance, None),
11809                   instance.disks)
11810
11811       snodes_group_uuids = [nodes[snode_name].group
11812                             for snode_name in instance.secondary_nodes]
11813
11814       result[instance.name] = {
11815         "name": instance.name,
11816         "config_state": instance.admin_state,
11817         "run_state": remote_state,
11818         "pnode": instance.primary_node,
11819         "pnode_group_uuid": pnode.group,
11820         "pnode_group_name": group2name_fn(pnode.group),
11821         "snodes": instance.secondary_nodes,
11822         "snodes_group_uuids": snodes_group_uuids,
11823         "snodes_group_names": map(group2name_fn, snodes_group_uuids),
11824         "os": instance.os,
11825         # this happens to be the same format used for hooks
11826         "nics": _NICListToTuple(self, instance.nics),
11827         "disk_template": instance.disk_template,
11828         "disks": disks,
11829         "hypervisor": instance.hypervisor,
11830         "network_port": instance.network_port,
11831         "hv_instance": instance.hvparams,
11832         "hv_actual": cluster.FillHV(instance, skip_globals=True),
11833         "be_instance": instance.beparams,
11834         "be_actual": cluster.FillBE(instance),
11835         "os_instance": instance.osparams,
11836         "os_actual": cluster.SimpleFillOS(instance.os, instance.osparams),
11837         "serial_no": instance.serial_no,
11838         "mtime": instance.mtime,
11839         "ctime": instance.ctime,
11840         "uuid": instance.uuid,
11841         }
11842
11843     return result
11844
11845
11846 def PrepareContainerMods(mods, private_fn):
11847   """Prepares a list of container modifications by adding a private data field.
11848
11849   @type mods: list of tuples; (operation, index, parameters)
11850   @param mods: List of modifications
11851   @type private_fn: callable or None
11852   @param private_fn: Callable for constructing a private data field for a
11853     modification
11854   @rtype: list
11855
11856   """
11857   if private_fn is None:
11858     fn = lambda: None
11859   else:
11860     fn = private_fn
11861
11862   return [(op, idx, params, fn()) for (op, idx, params) in mods]
11863
11864
11865 #: Type description for changes as returned by L{ApplyContainerMods}'s
11866 #: callbacks
11867 _TApplyContModsCbChanges = \
11868   ht.TMaybeListOf(ht.TAnd(ht.TIsLength(2), ht.TItems([
11869     ht.TNonEmptyString,
11870     ht.TAny,
11871     ])))
11872
11873
11874 def ApplyContainerMods(kind, container, chgdesc, mods,
11875                        create_fn, modify_fn, remove_fn):
11876   """Applies descriptions in C{mods} to C{container}.
11877
11878   @type kind: string
11879   @param kind: One-word item description
11880   @type container: list
11881   @param container: Container to modify
11882   @type chgdesc: None or list
11883   @param chgdesc: List of applied changes
11884   @type mods: list
11885   @param mods: Modifications as returned by L{PrepareContainerMods}
11886   @type create_fn: callable
11887   @param create_fn: Callback for creating a new item (L{constants.DDM_ADD});
11888     receives absolute item index, parameters and private data object as added
11889     by L{PrepareContainerMods}, returns tuple containing new item and changes
11890     as list
11891   @type modify_fn: callable
11892   @param modify_fn: Callback for modifying an existing item
11893     (L{constants.DDM_MODIFY}); receives absolute item index, item, parameters
11894     and private data object as added by L{PrepareContainerMods}, returns
11895     changes as list
11896   @type remove_fn: callable
11897   @param remove_fn: Callback on removing item; receives absolute item index,
11898     item and private data object as added by L{PrepareContainerMods}
11899
11900   """
11901   for (op, idx, params, private) in mods:
11902     if idx == -1:
11903       # Append
11904       absidx = len(container) - 1
11905     elif idx < 0:
11906       raise IndexError("Not accepting negative indices other than -1")
11907     elif idx > len(container):
11908       raise IndexError("Got %s index %s, but there are only %s" %
11909                        (kind, idx, len(container)))
11910     else:
11911       absidx = idx
11912
11913     changes = None
11914
11915     if op == constants.DDM_ADD:
11916       # Calculate where item will be added
11917       if idx == -1:
11918         addidx = len(container)
11919       else:
11920         addidx = idx
11921
11922       if create_fn is None:
11923         item = params
11924       else:
11925         (item, changes) = create_fn(addidx, params, private)
11926
11927       if idx == -1:
11928         container.append(item)
11929       else:
11930         assert idx >= 0
11931         assert idx <= len(container)
11932         # list.insert does so before the specified index
11933         container.insert(idx, item)
11934     else:
11935       # Retrieve existing item
11936       try:
11937         item = container[absidx]
11938       except IndexError:
11939         raise IndexError("Invalid %s index %s" % (kind, idx))
11940
11941       if op == constants.DDM_REMOVE:
11942         assert not params
11943
11944         if remove_fn is not None:
11945           remove_fn(absidx, item, private)
11946
11947         changes = [("%s/%s" % (kind, absidx), "remove")]
11948
11949         assert container[absidx] == item
11950         del container[absidx]
11951       elif op == constants.DDM_MODIFY:
11952         if modify_fn is not None:
11953           changes = modify_fn(absidx, item, params, private)
11954       else:
11955         raise errors.ProgrammerError("Unhandled operation '%s'" % op)
11956
11957     assert _TApplyContModsCbChanges(changes)
11958
11959     if not (chgdesc is None or changes is None):
11960       chgdesc.extend(changes)
11961
11962
11963 def _UpdateIvNames(base_index, disks):
11964   """Updates the C{iv_name} attribute of disks.
11965
11966   @type disks: list of L{objects.Disk}
11967
11968   """
11969   for (idx, disk) in enumerate(disks):
11970     disk.iv_name = "disk/%s" % (base_index + idx, )
11971
11972
11973 class _InstNicModPrivate:
11974   """Data structure for network interface modifications.
11975
11976   Used by L{LUInstanceSetParams}.
11977
11978   """
11979   def __init__(self):
11980     self.params = None
11981     self.filled = None
11982
11983
11984 class LUInstanceSetParams(LogicalUnit):
11985   """Modifies an instances's parameters.
11986
11987   """
11988   HPATH = "instance-modify"
11989   HTYPE = constants.HTYPE_INSTANCE
11990   REQ_BGL = False
11991
11992   @staticmethod
11993   def _UpgradeDiskNicMods(kind, mods, verify_fn):
11994     assert ht.TList(mods)
11995     assert not mods or len(mods[0]) in (2, 3)
11996
11997     if mods and len(mods[0]) == 2:
11998       result = []
11999
12000       addremove = 0
12001       for op, params in mods:
12002         if op in (constants.DDM_ADD, constants.DDM_REMOVE):
12003           result.append((op, -1, params))
12004           addremove += 1
12005
12006           if addremove > 1:
12007             raise errors.OpPrereqError("Only one %s add or remove operation is"
12008                                        " supported at a time" % kind,
12009                                        errors.ECODE_INVAL)
12010         else:
12011           result.append((constants.DDM_MODIFY, op, params))
12012
12013       assert verify_fn(result)
12014     else:
12015       result = mods
12016
12017     return result
12018
12019   @staticmethod
12020   def _CheckMods(kind, mods, key_types, item_fn):
12021     """Ensures requested disk/NIC modifications are valid.
12022
12023     """
12024     for (op, _, params) in mods:
12025       assert ht.TDict(params)
12026
12027       utils.ForceDictType(params, key_types)
12028
12029       if op == constants.DDM_REMOVE:
12030         if params:
12031           raise errors.OpPrereqError("No settings should be passed when"
12032                                      " removing a %s" % kind,
12033                                      errors.ECODE_INVAL)
12034       elif op in (constants.DDM_ADD, constants.DDM_MODIFY):
12035         item_fn(op, params)
12036       else:
12037         raise errors.ProgrammerError("Unhandled operation '%s'" % op)
12038
12039   @staticmethod
12040   def _VerifyDiskModification(op, params):
12041     """Verifies a disk modification.
12042
12043     """
12044     if op == constants.DDM_ADD:
12045       mode = params.setdefault(constants.IDISK_MODE, constants.DISK_RDWR)
12046       if mode not in constants.DISK_ACCESS_SET:
12047         raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
12048                                    errors.ECODE_INVAL)
12049
12050       size = params.get(constants.IDISK_SIZE, None)
12051       if size is None:
12052         raise errors.OpPrereqError("Required disk parameter '%s' missing" %
12053                                    constants.IDISK_SIZE, errors.ECODE_INVAL)
12054
12055       try:
12056         size = int(size)
12057       except (TypeError, ValueError), err:
12058         raise errors.OpPrereqError("Invalid disk size parameter: %s" % err,
12059                                    errors.ECODE_INVAL)
12060
12061       params[constants.IDISK_SIZE] = size
12062
12063     elif op == constants.DDM_MODIFY and constants.IDISK_SIZE in params:
12064       raise errors.OpPrereqError("Disk size change not possible, use"
12065                                  " grow-disk", errors.ECODE_INVAL)
12066
12067   @staticmethod
12068   def _VerifyNicModification(op, params):
12069     """Verifies a network interface modification.
12070
12071     """
12072     if op in (constants.DDM_ADD, constants.DDM_MODIFY):
12073       ip = params.get(constants.INIC_IP, None)
12074       if ip is None:
12075         pass
12076       elif ip.lower() == constants.VALUE_NONE:
12077         params[constants.INIC_IP] = None
12078       elif not netutils.IPAddress.IsValid(ip):
12079         raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
12080                                    errors.ECODE_INVAL)
12081
12082       bridge = params.get("bridge", None)
12083       link = params.get(constants.INIC_LINK, None)
12084       if bridge and link:
12085         raise errors.OpPrereqError("Cannot pass 'bridge' and 'link'"
12086                                    " at the same time", errors.ECODE_INVAL)
12087       elif bridge and bridge.lower() == constants.VALUE_NONE:
12088         params["bridge"] = None
12089       elif link and link.lower() == constants.VALUE_NONE:
12090         params[constants.INIC_LINK] = None
12091
12092       if op == constants.DDM_ADD:
12093         macaddr = params.get(constants.INIC_MAC, None)
12094         if macaddr is None:
12095           params[constants.INIC_MAC] = constants.VALUE_AUTO
12096
12097       if constants.INIC_MAC in params:
12098         macaddr = params[constants.INIC_MAC]
12099         if macaddr not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
12100           macaddr = utils.NormalizeAndValidateMac(macaddr)
12101
12102         if op == constants.DDM_MODIFY and macaddr == constants.VALUE_AUTO:
12103           raise errors.OpPrereqError("'auto' is not a valid MAC address when"
12104                                      " modifying an existing NIC",
12105                                      errors.ECODE_INVAL)
12106
12107   def CheckArguments(self):
12108     if not (self.op.nics or self.op.disks or self.op.disk_template or
12109             self.op.hvparams or self.op.beparams or self.op.os_name or
12110             self.op.offline is not None or self.op.runtime_mem):
12111       raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
12112
12113     if self.op.hvparams:
12114       _CheckGlobalHvParams(self.op.hvparams)
12115
12116     self.op.disks = \
12117       self._UpgradeDiskNicMods("disk", self.op.disks,
12118         opcodes.OpInstanceSetParams.TestDiskModifications)
12119     self.op.nics = \
12120       self._UpgradeDiskNicMods("NIC", self.op.nics,
12121         opcodes.OpInstanceSetParams.TestNicModifications)
12122
12123     # Check disk modifications
12124     self._CheckMods("disk", self.op.disks, constants.IDISK_PARAMS_TYPES,
12125                     self._VerifyDiskModification)
12126
12127     if self.op.disks and self.op.disk_template is not None:
12128       raise errors.OpPrereqError("Disk template conversion and other disk"
12129                                  " changes not supported at the same time",
12130                                  errors.ECODE_INVAL)
12131
12132     if (self.op.disk_template and
12133         self.op.disk_template in constants.DTS_INT_MIRROR and
12134         self.op.remote_node is None):
12135       raise errors.OpPrereqError("Changing the disk template to a mirrored"
12136                                  " one requires specifying a secondary node",
12137                                  errors.ECODE_INVAL)
12138
12139     # Check NIC modifications
12140     self._CheckMods("NIC", self.op.nics, constants.INIC_PARAMS_TYPES,
12141                     self._VerifyNicModification)
12142
12143   def ExpandNames(self):
12144     self._ExpandAndLockInstance()
12145     # Can't even acquire node locks in shared mode as upcoming changes in
12146     # Ganeti 2.6 will start to modify the node object on disk conversion
12147     self.needed_locks[locking.LEVEL_NODE] = []
12148     self.needed_locks[locking.LEVEL_NODE_RES] = []
12149     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
12150
12151   def DeclareLocks(self, level):
12152     # TODO: Acquire group lock in shared mode (disk parameters)
12153     if level == locking.LEVEL_NODE:
12154       self._LockInstancesNodes()
12155       if self.op.disk_template and self.op.remote_node:
12156         self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
12157         self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
12158     elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
12159       # Copy node locks
12160       self.needed_locks[locking.LEVEL_NODE_RES] = \
12161         self.needed_locks[locking.LEVEL_NODE][:]
12162
12163   def BuildHooksEnv(self):
12164     """Build hooks env.
12165
12166     This runs on the master, primary and secondaries.
12167
12168     """
12169     args = dict()
12170     if constants.BE_MINMEM in self.be_new:
12171       args["minmem"] = self.be_new[constants.BE_MINMEM]
12172     if constants.BE_MAXMEM in self.be_new:
12173       args["maxmem"] = self.be_new[constants.BE_MAXMEM]
12174     if constants.BE_VCPUS in self.be_new:
12175       args["vcpus"] = self.be_new[constants.BE_VCPUS]
12176     # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
12177     # information at all.
12178
12179     if self._new_nics is not None:
12180       nics = []
12181
12182       for nic in self._new_nics:
12183         nicparams = self.cluster.SimpleFillNIC(nic.nicparams)
12184         mode = nicparams[constants.NIC_MODE]
12185         link = nicparams[constants.NIC_LINK]
12186         nics.append((nic.ip, nic.mac, mode, link))
12187
12188       args["nics"] = nics
12189
12190     env = _BuildInstanceHookEnvByObject(self, self.instance, override=args)
12191     if self.op.disk_template:
12192       env["NEW_DISK_TEMPLATE"] = self.op.disk_template
12193     if self.op.runtime_mem:
12194       env["RUNTIME_MEMORY"] = self.op.runtime_mem
12195
12196     return env
12197
12198   def BuildHooksNodes(self):
12199     """Build hooks nodes.
12200
12201     """
12202     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
12203     return (nl, nl)
12204
12205   def _PrepareNicModification(self, params, private, old_ip, old_params,
12206                               cluster, pnode):
12207     update_params_dict = dict([(key, params[key])
12208                                for key in constants.NICS_PARAMETERS
12209                                if key in params])
12210
12211     if "bridge" in params:
12212       update_params_dict[constants.NIC_LINK] = params["bridge"]
12213
12214     new_params = _GetUpdatedParams(old_params, update_params_dict)
12215     utils.ForceDictType(new_params, constants.NICS_PARAMETER_TYPES)
12216
12217     new_filled_params = cluster.SimpleFillNIC(new_params)
12218     objects.NIC.CheckParameterSyntax(new_filled_params)
12219
12220     new_mode = new_filled_params[constants.NIC_MODE]
12221     if new_mode == constants.NIC_MODE_BRIDGED:
12222       bridge = new_filled_params[constants.NIC_LINK]
12223       msg = self.rpc.call_bridges_exist(pnode, [bridge]).fail_msg
12224       if msg:
12225         msg = "Error checking bridges on node '%s': %s" % (pnode, msg)
12226         if self.op.force:
12227           self.warn.append(msg)
12228         else:
12229           raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
12230
12231     elif new_mode == constants.NIC_MODE_ROUTED:
12232       ip = params.get(constants.INIC_IP, old_ip)
12233       if ip is None:
12234         raise errors.OpPrereqError("Cannot set the NIC IP address to None"
12235                                    " on a routed NIC", errors.ECODE_INVAL)
12236
12237     if constants.INIC_MAC in params:
12238       mac = params[constants.INIC_MAC]
12239       if mac is None:
12240         raise errors.OpPrereqError("Cannot unset the NIC MAC address",
12241                                    errors.ECODE_INVAL)
12242       elif mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
12243         # otherwise generate the MAC address
12244         params[constants.INIC_MAC] = \
12245           self.cfg.GenerateMAC(self.proc.GetECId())
12246       else:
12247         # or validate/reserve the current one
12248         try:
12249           self.cfg.ReserveMAC(mac, self.proc.GetECId())
12250         except errors.ReservationError:
12251           raise errors.OpPrereqError("MAC address '%s' already in use"
12252                                      " in cluster" % mac,
12253                                      errors.ECODE_NOTUNIQUE)
12254
12255     private.params = new_params
12256     private.filled = new_filled_params
12257
12258     return (None, None)
12259
12260   def CheckPrereq(self):
12261     """Check prerequisites.
12262
12263     This only checks the instance list against the existing names.
12264
12265     """
12266     # checking the new params on the primary/secondary nodes
12267
12268     instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
12269     cluster = self.cluster = self.cfg.GetClusterInfo()
12270     assert self.instance is not None, \
12271       "Cannot retrieve locked instance %s" % self.op.instance_name
12272     pnode = instance.primary_node
12273     nodelist = list(instance.all_nodes)
12274     pnode_info = self.cfg.GetNodeInfo(pnode)
12275     self.diskparams = self.cfg.GetNodeGroup(pnode_info.group).diskparams
12276
12277     # Prepare disk/NIC modifications
12278     self.diskmod = PrepareContainerMods(self.op.disks, None)
12279     self.nicmod = PrepareContainerMods(self.op.nics, _InstNicModPrivate)
12280
12281     # OS change
12282     if self.op.os_name and not self.op.force:
12283       _CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
12284                       self.op.force_variant)
12285       instance_os = self.op.os_name
12286     else:
12287       instance_os = instance.os
12288
12289     assert not (self.op.disk_template and self.op.disks), \
12290       "Can't modify disk template and apply disk changes at the same time"
12291
12292     if self.op.disk_template:
12293       if instance.disk_template == self.op.disk_template:
12294         raise errors.OpPrereqError("Instance already has disk template %s" %
12295                                    instance.disk_template, errors.ECODE_INVAL)
12296
12297       if (instance.disk_template,
12298           self.op.disk_template) not in self._DISK_CONVERSIONS:
12299         raise errors.OpPrereqError("Unsupported disk template conversion from"
12300                                    " %s to %s" % (instance.disk_template,
12301                                                   self.op.disk_template),
12302                                    errors.ECODE_INVAL)
12303       _CheckInstanceState(self, instance, INSTANCE_DOWN,
12304                           msg="cannot change disk template")
12305       if self.op.disk_template in constants.DTS_INT_MIRROR:
12306         if self.op.remote_node == pnode:
12307           raise errors.OpPrereqError("Given new secondary node %s is the same"
12308                                      " as the primary node of the instance" %
12309                                      self.op.remote_node, errors.ECODE_STATE)
12310         _CheckNodeOnline(self, self.op.remote_node)
12311         _CheckNodeNotDrained(self, self.op.remote_node)
12312         # FIXME: here we assume that the old instance type is DT_PLAIN
12313         assert instance.disk_template == constants.DT_PLAIN
12314         disks = [{constants.IDISK_SIZE: d.size,
12315                   constants.IDISK_VG: d.logical_id[0]}
12316                  for d in instance.disks]
12317         required = _ComputeDiskSizePerVG(self.op.disk_template, disks)
12318         _CheckNodesFreeDiskPerVG(self, [self.op.remote_node], required)
12319
12320         snode_info = self.cfg.GetNodeInfo(self.op.remote_node)
12321         snode_group = self.cfg.GetNodeGroup(snode_info.group)
12322         ipolicy = _CalculateGroupIPolicy(cluster, snode_group)
12323         _CheckTargetNodeIPolicy(self, ipolicy, instance, snode_info,
12324                                 ignore=self.op.ignore_ipolicy)
12325         if pnode_info.group != snode_info.group:
12326           self.LogWarning("The primary and secondary nodes are in two"
12327                           " different node groups; the disk parameters"
12328                           " from the first disk's node group will be"
12329                           " used")
12330
12331     # hvparams processing
12332     if self.op.hvparams:
12333       hv_type = instance.hypervisor
12334       i_hvdict = _GetUpdatedParams(instance.hvparams, self.op.hvparams)
12335       utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
12336       hv_new = cluster.SimpleFillHV(hv_type, instance.os, i_hvdict)
12337
12338       # local check
12339       hypervisor.GetHypervisor(hv_type).CheckParameterSyntax(hv_new)
12340       _CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
12341       self.hv_proposed = self.hv_new = hv_new # the new actual values
12342       self.hv_inst = i_hvdict # the new dict (without defaults)
12343     else:
12344       self.hv_proposed = cluster.SimpleFillHV(instance.hypervisor, instance.os,
12345                                               instance.hvparams)
12346       self.hv_new = self.hv_inst = {}
12347
12348     # beparams processing
12349     if self.op.beparams:
12350       i_bedict = _GetUpdatedParams(instance.beparams, self.op.beparams,
12351                                    use_none=True)
12352       objects.UpgradeBeParams(i_bedict)
12353       utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
12354       be_new = cluster.SimpleFillBE(i_bedict)
12355       self.be_proposed = self.be_new = be_new # the new actual values
12356       self.be_inst = i_bedict # the new dict (without defaults)
12357     else:
12358       self.be_new = self.be_inst = {}
12359       self.be_proposed = cluster.SimpleFillBE(instance.beparams)
12360     be_old = cluster.FillBE(instance)
12361
12362     # CPU param validation -- checking every time a paramtere is
12363     # changed to cover all cases where either CPU mask or vcpus have
12364     # changed
12365     if (constants.BE_VCPUS in self.be_proposed and
12366         constants.HV_CPU_MASK in self.hv_proposed):
12367       cpu_list = \
12368         utils.ParseMultiCpuMask(self.hv_proposed[constants.HV_CPU_MASK])
12369       # Verify mask is consistent with number of vCPUs. Can skip this
12370       # test if only 1 entry in the CPU mask, which means same mask
12371       # is applied to all vCPUs.
12372       if (len(cpu_list) > 1 and
12373           len(cpu_list) != self.be_proposed[constants.BE_VCPUS]):
12374         raise errors.OpPrereqError("Number of vCPUs [%d] does not match the"
12375                                    " CPU mask [%s]" %
12376                                    (self.be_proposed[constants.BE_VCPUS],
12377                                     self.hv_proposed[constants.HV_CPU_MASK]),
12378                                    errors.ECODE_INVAL)
12379
12380       # Only perform this test if a new CPU mask is given
12381       if constants.HV_CPU_MASK in self.hv_new:
12382         # Calculate the largest CPU number requested
12383         max_requested_cpu = max(map(max, cpu_list))
12384         # Check that all of the instance's nodes have enough physical CPUs to
12385         # satisfy the requested CPU mask
12386         _CheckNodesPhysicalCPUs(self, instance.all_nodes,
12387                                 max_requested_cpu + 1, instance.hypervisor)
12388
12389     # osparams processing
12390     if self.op.osparams:
12391       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
12392       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
12393       self.os_inst = i_osdict # the new dict (without defaults)
12394     else:
12395       self.os_inst = {}
12396
12397     self.warn = []
12398
12399     #TODO(dynmem): do the appropriate check involving MINMEM
12400     if (constants.BE_MAXMEM in self.op.beparams and not self.op.force and
12401         be_new[constants.BE_MAXMEM] > be_old[constants.BE_MAXMEM]):
12402       mem_check_list = [pnode]
12403       if be_new[constants.BE_AUTO_BALANCE]:
12404         # either we changed auto_balance to yes or it was from before
12405         mem_check_list.extend(instance.secondary_nodes)
12406       instance_info = self.rpc.call_instance_info(pnode, instance.name,
12407                                                   instance.hypervisor)
12408       nodeinfo = self.rpc.call_node_info(mem_check_list, None,
12409                                          [instance.hypervisor])
12410       pninfo = nodeinfo[pnode]
12411       msg = pninfo.fail_msg
12412       if msg:
12413         # Assume the primary node is unreachable and go ahead
12414         self.warn.append("Can't get info from primary node %s: %s" %
12415                          (pnode, msg))
12416       else:
12417         (_, _, (pnhvinfo, )) = pninfo.payload
12418         if not isinstance(pnhvinfo.get("memory_free", None), int):
12419           self.warn.append("Node data from primary node %s doesn't contain"
12420                            " free memory information" % pnode)
12421         elif instance_info.fail_msg:
12422           self.warn.append("Can't get instance runtime information: %s" %
12423                           instance_info.fail_msg)
12424         else:
12425           if instance_info.payload:
12426             current_mem = int(instance_info.payload["memory"])
12427           else:
12428             # Assume instance not running
12429             # (there is a slight race condition here, but it's not very
12430             # probable, and we have no other way to check)
12431             # TODO: Describe race condition
12432             current_mem = 0
12433           #TODO(dynmem): do the appropriate check involving MINMEM
12434           miss_mem = (be_new[constants.BE_MAXMEM] - current_mem -
12435                       pnhvinfo["memory_free"])
12436           if miss_mem > 0:
12437             raise errors.OpPrereqError("This change will prevent the instance"
12438                                        " from starting, due to %d MB of memory"
12439                                        " missing on its primary node" %
12440                                        miss_mem,
12441                                        errors.ECODE_NORES)
12442
12443       if be_new[constants.BE_AUTO_BALANCE]:
12444         for node, nres in nodeinfo.items():
12445           if node not in instance.secondary_nodes:
12446             continue
12447           nres.Raise("Can't get info from secondary node %s" % node,
12448                      prereq=True, ecode=errors.ECODE_STATE)
12449           (_, _, (nhvinfo, )) = nres.payload
12450           if not isinstance(nhvinfo.get("memory_free", None), int):
12451             raise errors.OpPrereqError("Secondary node %s didn't return free"
12452                                        " memory information" % node,
12453                                        errors.ECODE_STATE)
12454           #TODO(dynmem): do the appropriate check involving MINMEM
12455           elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
12456             raise errors.OpPrereqError("This change will prevent the instance"
12457                                        " from failover to its secondary node"
12458                                        " %s, due to not enough memory" % node,
12459                                        errors.ECODE_STATE)
12460
12461     if self.op.runtime_mem:
12462       remote_info = self.rpc.call_instance_info(instance.primary_node,
12463                                                 instance.name,
12464                                                 instance.hypervisor)
12465       remote_info.Raise("Error checking node %s" % instance.primary_node)
12466       if not remote_info.payload: # not running already
12467         raise errors.OpPrereqError("Instance %s is not running" % instance.name,
12468                                    errors.ECODE_STATE)
12469
12470       current_memory = remote_info.payload["memory"]
12471       if (not self.op.force and
12472            (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or
12473             self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])):
12474         raise errors.OpPrereqError("Instance %s must have memory between %d"
12475                                    " and %d MB of memory unless --force is"
12476                                    " given" % (instance.name,
12477                                     self.be_proposed[constants.BE_MINMEM],
12478                                     self.be_proposed[constants.BE_MAXMEM]),
12479                                    errors.ECODE_INVAL)
12480
12481       if self.op.runtime_mem > current_memory:
12482         _CheckNodeFreeMemory(self, instance.primary_node,
12483                              "ballooning memory for instance %s" %
12484                              instance.name,
12485                              self.op.memory - current_memory,
12486                              instance.hypervisor)
12487
12488     if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
12489       raise errors.OpPrereqError("Disk operations not supported for"
12490                                  " diskless instances",
12491                                  errors.ECODE_INVAL)
12492
12493     def _PrepareNicCreate(_, params, private):
12494       return self._PrepareNicModification(params, private, None, {},
12495                                           cluster, pnode)
12496
12497     def _PrepareNicMod(_, nic, params, private):
12498       return self._PrepareNicModification(params, private, nic.ip,
12499                                           nic.nicparams, cluster, pnode)
12500
12501     # Verify NIC changes (operating on copy)
12502     nics = instance.nics[:]
12503     ApplyContainerMods("NIC", nics, None, self.nicmod,
12504                        _PrepareNicCreate, _PrepareNicMod, None)
12505     if len(nics) > constants.MAX_NICS:
12506       raise errors.OpPrereqError("Instance has too many network interfaces"
12507                                  " (%d), cannot add more" % constants.MAX_NICS,
12508                                  errors.ECODE_STATE)
12509
12510     # Verify disk changes (operating on a copy)
12511     disks = instance.disks[:]
12512     ApplyContainerMods("disk", disks, None, self.diskmod, None, None, None)
12513     if len(disks) > constants.MAX_DISKS:
12514       raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
12515                                  " more" % constants.MAX_DISKS,
12516                                  errors.ECODE_STATE)
12517
12518     if self.op.offline is not None:
12519       if self.op.offline:
12520         msg = "can't change to offline"
12521       else:
12522         msg = "can't change to online"
12523       _CheckInstanceState(self, instance, CAN_CHANGE_INSTANCE_OFFLINE, msg=msg)
12524
12525     # Pre-compute NIC changes (necessary to use result in hooks)
12526     self._nic_chgdesc = []
12527     if self.nicmod:
12528       # Operate on copies as this is still in prereq
12529       nics = [nic.Copy() for nic in instance.nics]
12530       ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
12531                          self._CreateNewNic, self._ApplyNicMods, None)
12532       self._new_nics = nics
12533     else:
12534       self._new_nics = None
12535
12536   def _ConvertPlainToDrbd(self, feedback_fn):
12537     """Converts an instance from plain to drbd.
12538
12539     """
12540     feedback_fn("Converting template to drbd")
12541     instance = self.instance
12542     pnode = instance.primary_node
12543     snode = self.op.remote_node
12544
12545     assert instance.disk_template == constants.DT_PLAIN
12546
12547     # create a fake disk info for _GenerateDiskTemplate
12548     disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
12549                   constants.IDISK_VG: d.logical_id[0]}
12550                  for d in instance.disks]
12551     new_disks = _GenerateDiskTemplate(self, self.op.disk_template,
12552                                       instance.name, pnode, [snode],
12553                                       disk_info, None, None, 0, feedback_fn,
12554                                       self.diskparams)
12555     info = _GetInstanceInfoText(instance)
12556     feedback_fn("Creating additional volumes...")
12557     # first, create the missing data and meta devices
12558     for disk in new_disks:
12559       # unfortunately this is... not too nice
12560       _CreateSingleBlockDev(self, pnode, instance, disk.children[1],
12561                             info, True)
12562       for child in disk.children:
12563         _CreateSingleBlockDev(self, snode, instance, child, info, True)
12564     # at this stage, all new LVs have been created, we can rename the
12565     # old ones
12566     feedback_fn("Renaming original volumes...")
12567     rename_list = [(o, n.children[0].logical_id)
12568                    for (o, n) in zip(instance.disks, new_disks)]
12569     result = self.rpc.call_blockdev_rename(pnode, rename_list)
12570     result.Raise("Failed to rename original LVs")
12571
12572     feedback_fn("Initializing DRBD devices...")
12573     # all child devices are in place, we can now create the DRBD devices
12574     for disk in new_disks:
12575       for node in [pnode, snode]:
12576         f_create = node == pnode
12577         _CreateSingleBlockDev(self, node, instance, disk, info, f_create)
12578
12579     # at this point, the instance has been modified
12580     instance.disk_template = constants.DT_DRBD8
12581     instance.disks = new_disks
12582     self.cfg.Update(instance, feedback_fn)
12583
12584     # Release node locks while waiting for sync
12585     _ReleaseLocks(self, locking.LEVEL_NODE)
12586
12587     # disks are created, waiting for sync
12588     disk_abort = not _WaitForSync(self, instance,
12589                                   oneshot=not self.op.wait_for_sync)
12590     if disk_abort:
12591       raise errors.OpExecError("There are some degraded disks for"
12592                                " this instance, please cleanup manually")
12593
12594     # Node resource locks will be released by caller
12595
12596   def _ConvertDrbdToPlain(self, feedback_fn):
12597     """Converts an instance from drbd to plain.
12598
12599     """
12600     instance = self.instance
12601
12602     assert len(instance.secondary_nodes) == 1
12603     assert instance.disk_template == constants.DT_DRBD8
12604
12605     pnode = instance.primary_node
12606     snode = instance.secondary_nodes[0]
12607     feedback_fn("Converting template to plain")
12608
12609     old_disks = instance.disks
12610     new_disks = [d.children[0] for d in old_disks]
12611
12612     # copy over size and mode
12613     for parent, child in zip(old_disks, new_disks):
12614       child.size = parent.size
12615       child.mode = parent.mode
12616
12617     # this is a DRBD disk, return its port to the pool
12618     # NOTE: this must be done right before the call to cfg.Update!
12619     for disk in old_disks:
12620       tcp_port = disk.logical_id[2]
12621       self.cfg.AddTcpUdpPort(tcp_port)
12622
12623     # update instance structure
12624     instance.disks = new_disks
12625     instance.disk_template = constants.DT_PLAIN
12626     self.cfg.Update(instance, feedback_fn)
12627
12628     # Release locks in case removing disks takes a while
12629     _ReleaseLocks(self, locking.LEVEL_NODE)
12630
12631     feedback_fn("Removing volumes on the secondary node...")
12632     for disk in old_disks:
12633       self.cfg.SetDiskID(disk, snode)
12634       msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
12635       if msg:
12636         self.LogWarning("Could not remove block device %s on node %s,"
12637                         " continuing anyway: %s", disk.iv_name, snode, msg)
12638
12639     feedback_fn("Removing unneeded volumes on the primary node...")
12640     for idx, disk in enumerate(old_disks):
12641       meta = disk.children[1]
12642       self.cfg.SetDiskID(meta, pnode)
12643       msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
12644       if msg:
12645         self.LogWarning("Could not remove metadata for disk %d on node %s,"
12646                         " continuing anyway: %s", idx, pnode, msg)
12647
12648   def _CreateNewDisk(self, idx, params, _):
12649     """Creates a new disk.
12650
12651     """
12652     instance = self.instance
12653
12654     # add a new disk
12655     if instance.disk_template in constants.DTS_FILEBASED:
12656       (file_driver, file_path) = instance.disks[0].logical_id
12657       file_path = os.path.dirname(file_path)
12658     else:
12659       file_driver = file_path = None
12660
12661     disk = \
12662       _GenerateDiskTemplate(self, instance.disk_template, instance.name,
12663                             instance.primary_node, instance.secondary_nodes,
12664                             [params], file_path, file_driver, idx,
12665                             self.Log, self.diskparams)[0]
12666
12667     info = _GetInstanceInfoText(instance)
12668
12669     logging.info("Creating volume %s for instance %s",
12670                  disk.iv_name, instance.name)
12671     # Note: this needs to be kept in sync with _CreateDisks
12672     #HARDCODE
12673     for node in instance.all_nodes:
12674       f_create = (node == instance.primary_node)
12675       try:
12676         _CreateBlockDev(self, node, instance, disk, f_create, info, f_create)
12677       except errors.OpExecError, err:
12678         self.LogWarning("Failed to create volume %s (%s) on node '%s': %s",
12679                         disk.iv_name, disk, node, err)
12680
12681     return (disk, [
12682       ("disk/%d" % idx, "add:size=%s,mode=%s" % (disk.size, disk.mode)),
12683       ])
12684
12685   @staticmethod
12686   def _ModifyDisk(idx, disk, params, _):
12687     """Modifies a disk.
12688
12689     """
12690     disk.mode = params[constants.IDISK_MODE]
12691
12692     return [
12693       ("disk.mode/%d" % idx, disk.mode),
12694       ]
12695
12696   def _RemoveDisk(self, idx, root, _):
12697     """Removes a disk.
12698
12699     """
12700     for node, disk in root.ComputeNodeTree(self.instance.primary_node):
12701       self.cfg.SetDiskID(disk, node)
12702       msg = self.rpc.call_blockdev_remove(node, disk).fail_msg
12703       if msg:
12704         self.LogWarning("Could not remove disk/%d on node '%s': %s,"
12705                         " continuing anyway", idx, node, msg)
12706
12707     # if this is a DRBD disk, return its port to the pool
12708     if root.dev_type in constants.LDS_DRBD:
12709       self.cfg.AddTcpUdpPort(root.logical_id[2])
12710
12711   @staticmethod
12712   def _CreateNewNic(idx, params, private):
12713     """Creates data structure for a new network interface.
12714
12715     """
12716     mac = params[constants.INIC_MAC]
12717     ip = params.get(constants.INIC_IP, None)
12718     nicparams = private.params
12719
12720     return (objects.NIC(mac=mac, ip=ip, nicparams=nicparams), [
12721       ("nic.%d" % idx,
12722        "add:mac=%s,ip=%s,mode=%s,link=%s" %
12723        (mac, ip, private.filled[constants.NIC_MODE],
12724        private.filled[constants.NIC_LINK])),
12725       ])
12726
12727   @staticmethod
12728   def _ApplyNicMods(idx, nic, params, private):
12729     """Modifies a network interface.
12730
12731     """
12732     changes = []
12733
12734     for key in [constants.INIC_MAC, constants.INIC_IP]:
12735       if key in params:
12736         changes.append(("nic.%s/%d" % (key, idx), params[key]))
12737         setattr(nic, key, params[key])
12738
12739     if private.params:
12740       nic.nicparams = private.params
12741
12742       for (key, val) in params.items():
12743         changes.append(("nic.%s/%d" % (key, idx), val))
12744
12745     return changes
12746
12747   def Exec(self, feedback_fn):
12748     """Modifies an instance.
12749
12750     All parameters take effect only at the next restart of the instance.
12751
12752     """
12753     # Process here the warnings from CheckPrereq, as we don't have a
12754     # feedback_fn there.
12755     # TODO: Replace with self.LogWarning
12756     for warn in self.warn:
12757       feedback_fn("WARNING: %s" % warn)
12758
12759     assert ((self.op.disk_template is None) ^
12760             bool(self.owned_locks(locking.LEVEL_NODE_RES))), \
12761       "Not owning any node resource locks"
12762
12763     result = []
12764     instance = self.instance
12765
12766     # runtime memory
12767     if self.op.runtime_mem:
12768       rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
12769                                                      instance,
12770                                                      self.op.runtime_mem)
12771       rpcres.Raise("Cannot modify instance runtime memory")
12772       result.append(("runtime_memory", self.op.runtime_mem))
12773
12774     # Apply disk changes
12775     ApplyContainerMods("disk", instance.disks, result, self.diskmod,
12776                        self._CreateNewDisk, self._ModifyDisk, self._RemoveDisk)
12777     _UpdateIvNames(0, instance.disks)
12778
12779     if self.op.disk_template:
12780       if __debug__:
12781         check_nodes = set(instance.all_nodes)
12782         if self.op.remote_node:
12783           check_nodes.add(self.op.remote_node)
12784         for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
12785           owned = self.owned_locks(level)
12786           assert not (check_nodes - owned), \
12787             ("Not owning the correct locks, owning %r, expected at least %r" %
12788              (owned, check_nodes))
12789
12790       r_shut = _ShutdownInstanceDisks(self, instance)
12791       if not r_shut:
12792         raise errors.OpExecError("Cannot shutdown instance disks, unable to"
12793                                  " proceed with disk template conversion")
12794       mode = (instance.disk_template, self.op.disk_template)
12795       try:
12796         self._DISK_CONVERSIONS[mode](self, feedback_fn)
12797       except:
12798         self.cfg.ReleaseDRBDMinors(instance.name)
12799         raise
12800       result.append(("disk_template", self.op.disk_template))
12801
12802       assert instance.disk_template == self.op.disk_template, \
12803         ("Expected disk template '%s', found '%s'" %
12804          (self.op.disk_template, instance.disk_template))
12805
12806     # Release node and resource locks if there are any (they might already have
12807     # been released during disk conversion)
12808     _ReleaseLocks(self, locking.LEVEL_NODE)
12809     _ReleaseLocks(self, locking.LEVEL_NODE_RES)
12810
12811     # Apply NIC changes
12812     if self._new_nics is not None:
12813       instance.nics = self._new_nics
12814       result.extend(self._nic_chgdesc)
12815
12816     # hvparams changes
12817     if self.op.hvparams:
12818       instance.hvparams = self.hv_inst
12819       for key, val in self.op.hvparams.iteritems():
12820         result.append(("hv/%s" % key, val))
12821
12822     # beparams changes
12823     if self.op.beparams:
12824       instance.beparams = self.be_inst
12825       for key, val in self.op.beparams.iteritems():
12826         result.append(("be/%s" % key, val))
12827
12828     # OS change
12829     if self.op.os_name:
12830       instance.os = self.op.os_name
12831
12832     # osparams changes
12833     if self.op.osparams:
12834       instance.osparams = self.os_inst
12835       for key, val in self.op.osparams.iteritems():
12836         result.append(("os/%s" % key, val))
12837
12838     if self.op.offline is None:
12839       # Ignore
12840       pass
12841     elif self.op.offline:
12842       # Mark instance as offline
12843       self.cfg.MarkInstanceOffline(instance.name)
12844       result.append(("admin_state", constants.ADMINST_OFFLINE))
12845     else:
12846       # Mark instance as online, but stopped
12847       self.cfg.MarkInstanceDown(instance.name)
12848       result.append(("admin_state", constants.ADMINST_DOWN))
12849
12850     self.cfg.Update(instance, feedback_fn)
12851
12852     assert not (self.owned_locks(locking.LEVEL_NODE_RES) or
12853                 self.owned_locks(locking.LEVEL_NODE)), \
12854       "All node locks should have been released by now"
12855
12856     return result
12857
12858   _DISK_CONVERSIONS = {
12859     (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
12860     (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
12861     }
12862
12863
12864 class LUInstanceChangeGroup(LogicalUnit):
12865   HPATH = "instance-change-group"
12866   HTYPE = constants.HTYPE_INSTANCE
12867   REQ_BGL = False
12868
12869   def ExpandNames(self):
12870     self.share_locks = _ShareAll()
12871     self.needed_locks = {
12872       locking.LEVEL_NODEGROUP: [],
12873       locking.LEVEL_NODE: [],
12874       }
12875
12876     self._ExpandAndLockInstance()
12877
12878     if self.op.target_groups:
12879       self.req_target_uuids = map(self.cfg.LookupNodeGroup,
12880                                   self.op.target_groups)
12881     else:
12882       self.req_target_uuids = None
12883
12884     self.op.iallocator = _GetDefaultIAllocator(self.cfg, self.op.iallocator)
12885
12886   def DeclareLocks(self, level):
12887     if level == locking.LEVEL_NODEGROUP:
12888       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
12889
12890       if self.req_target_uuids:
12891         lock_groups = set(self.req_target_uuids)
12892
12893         # Lock all groups used by instance optimistically; this requires going
12894         # via the node before it's locked, requiring verification later on
12895         instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_name)
12896         lock_groups.update(instance_groups)
12897       else:
12898         # No target groups, need to lock all of them
12899         lock_groups = locking.ALL_SET
12900
12901       self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
12902
12903     elif level == locking.LEVEL_NODE:
12904       if self.req_target_uuids:
12905         # Lock all nodes used by instances
12906         self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
12907         self._LockInstancesNodes()
12908
12909         # Lock all nodes in all potential target groups
12910         lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) -
12911                        self.cfg.GetInstanceNodeGroups(self.op.instance_name))
12912         member_nodes = [node_name
12913                         for group in lock_groups
12914                         for node_name in self.cfg.GetNodeGroup(group).members]
12915         self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
12916       else:
12917         # Lock all nodes as all groups are potential targets
12918         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
12919
12920   def CheckPrereq(self):
12921     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
12922     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
12923     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
12924
12925     assert (self.req_target_uuids is None or
12926             owned_groups.issuperset(self.req_target_uuids))
12927     assert owned_instances == set([self.op.instance_name])
12928
12929     # Get instance information
12930     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
12931
12932     # Check if node groups for locked instance are still correct
12933     assert owned_nodes.issuperset(self.instance.all_nodes), \
12934       ("Instance %s's nodes changed while we kept the lock" %
12935        self.op.instance_name)
12936
12937     inst_groups = _CheckInstanceNodeGroups(self.cfg, self.op.instance_name,
12938                                            owned_groups)
12939
12940     if self.req_target_uuids:
12941       # User requested specific target groups
12942       self.target_uuids = frozenset(self.req_target_uuids)
12943     else:
12944       # All groups except those used by the instance are potential targets
12945       self.target_uuids = owned_groups - inst_groups
12946
12947     conflicting_groups = self.target_uuids & inst_groups
12948     if conflicting_groups:
12949       raise errors.OpPrereqError("Can't use group(s) '%s' as targets, they are"
12950                                  " used by the instance '%s'" %
12951                                  (utils.CommaJoin(conflicting_groups),
12952                                   self.op.instance_name),
12953                                  errors.ECODE_INVAL)
12954
12955     if not self.target_uuids:
12956       raise errors.OpPrereqError("There are no possible target groups",
12957                                  errors.ECODE_INVAL)
12958
12959   def BuildHooksEnv(self):
12960     """Build hooks env.
12961
12962     """
12963     assert self.target_uuids
12964
12965     env = {
12966       "TARGET_GROUPS": " ".join(self.target_uuids),
12967       }
12968
12969     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
12970
12971     return env
12972
12973   def BuildHooksNodes(self):
12974     """Build hooks nodes.
12975
12976     """
12977     mn = self.cfg.GetMasterNode()
12978     return ([mn], [mn])
12979
12980   def Exec(self, feedback_fn):
12981     instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
12982
12983     assert instances == [self.op.instance_name], "Instance not locked"
12984
12985     ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_CHG_GROUP,
12986                      instances=instances, target_groups=list(self.target_uuids))
12987
12988     ial.Run(self.op.iallocator)
12989
12990     if not ial.success:
12991       raise errors.OpPrereqError("Can't compute solution for changing group of"
12992                                  " instance '%s' using iallocator '%s': %s" %
12993                                  (self.op.instance_name, self.op.iallocator,
12994                                   ial.info),
12995                                  errors.ECODE_NORES)
12996
12997     jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
12998
12999     self.LogInfo("Iallocator returned %s job(s) for changing group of"
13000                  " instance '%s'", len(jobs), self.op.instance_name)
13001
13002     return ResultWithJobs(jobs)
13003
13004
13005 class LUBackupQuery(NoHooksLU):
13006   """Query the exports list
13007
13008   """
13009   REQ_BGL = False
13010
13011   def CheckArguments(self):
13012     self.expq = _ExportQuery(qlang.MakeSimpleFilter("node", self.op.nodes),
13013                              ["node", "export"], self.op.use_locking)
13014
13015   def ExpandNames(self):
13016     self.expq.ExpandNames(self)
13017
13018   def DeclareLocks(self, level):
13019     self.expq.DeclareLocks(self, level)
13020
13021   def Exec(self, feedback_fn):
13022     result = {}
13023
13024     for (node, expname) in self.expq.OldStyleQuery(self):
13025       if expname is None:
13026         result[node] = False
13027       else:
13028         result.setdefault(node, []).append(expname)
13029
13030     return result
13031
13032
13033 class _ExportQuery(_QueryBase):
13034   FIELDS = query.EXPORT_FIELDS
13035
13036   #: The node name is not a unique key for this query
13037   SORT_FIELD = "node"
13038
13039   def ExpandNames(self, lu):
13040     lu.needed_locks = {}
13041
13042     # The following variables interact with _QueryBase._GetNames
13043     if self.names:
13044       self.wanted = _GetWantedNodes(lu, self.names)
13045     else:
13046       self.wanted = locking.ALL_SET
13047
13048     self.do_locking = self.use_locking
13049
13050     if self.do_locking:
13051       lu.share_locks = _ShareAll()
13052       lu.needed_locks = {
13053         locking.LEVEL_NODE: self.wanted,
13054         }
13055
13056   def DeclareLocks(self, lu, level):
13057     pass
13058
13059   def _GetQueryData(self, lu):
13060     """Computes the list of nodes and their attributes.
13061
13062     """
13063     # Locking is not used
13064     # TODO
13065     assert not (compat.any(lu.glm.is_owned(level)
13066                            for level in locking.LEVELS
13067                            if level != locking.LEVEL_CLUSTER) or
13068                 self.do_locking or self.use_locking)
13069
13070     nodes = self._GetNames(lu, lu.cfg.GetNodeList(), locking.LEVEL_NODE)
13071
13072     result = []
13073
13074     for (node, nres) in lu.rpc.call_export_list(nodes).items():
13075       if nres.fail_msg:
13076         result.append((node, None))
13077       else:
13078         result.extend((node, expname) for expname in nres.payload)
13079
13080     return result
13081
13082
13083 class LUBackupPrepare(NoHooksLU):
13084   """Prepares an instance for an export and returns useful information.
13085
13086   """
13087   REQ_BGL = False
13088
13089   def ExpandNames(self):
13090     self._ExpandAndLockInstance()
13091
13092   def CheckPrereq(self):
13093     """Check prerequisites.
13094
13095     """
13096     instance_name = self.op.instance_name
13097
13098     self.instance = self.cfg.GetInstanceInfo(instance_name)
13099     assert self.instance is not None, \
13100           "Cannot retrieve locked instance %s" % self.op.instance_name
13101     _CheckNodeOnline(self, self.instance.primary_node)
13102
13103     self._cds = _GetClusterDomainSecret()
13104
13105   def Exec(self, feedback_fn):
13106     """Prepares an instance for an export.
13107
13108     """
13109     instance = self.instance
13110
13111     if self.op.mode == constants.EXPORT_MODE_REMOTE:
13112       salt = utils.GenerateSecret(8)
13113
13114       feedback_fn("Generating X509 certificate on %s" % instance.primary_node)
13115       result = self.rpc.call_x509_cert_create(instance.primary_node,
13116                                               constants.RIE_CERT_VALIDITY)
13117       result.Raise("Can't create X509 key and certificate on %s" % result.node)
13118
13119       (name, cert_pem) = result.payload
13120
13121       cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
13122                                              cert_pem)
13123
13124       return {
13125         "handshake": masterd.instance.ComputeRemoteExportHandshake(self._cds),
13126         "x509_key_name": (name, utils.Sha1Hmac(self._cds, name, salt=salt),
13127                           salt),
13128         "x509_ca": utils.SignX509Certificate(cert, self._cds, salt),
13129         }
13130
13131     return None
13132
13133
13134 class LUBackupExport(LogicalUnit):
13135   """Export an instance to an image in the cluster.
13136
13137   """
13138   HPATH = "instance-export"
13139   HTYPE = constants.HTYPE_INSTANCE
13140   REQ_BGL = False
13141
13142   def CheckArguments(self):
13143     """Check the arguments.
13144
13145     """
13146     self.x509_key_name = self.op.x509_key_name
13147     self.dest_x509_ca_pem = self.op.destination_x509_ca
13148
13149     if self.op.mode == constants.EXPORT_MODE_REMOTE:
13150       if not self.x509_key_name:
13151         raise errors.OpPrereqError("Missing X509 key name for encryption",
13152                                    errors.ECODE_INVAL)
13153
13154       if not self.dest_x509_ca_pem:
13155         raise errors.OpPrereqError("Missing destination X509 CA",
13156                                    errors.ECODE_INVAL)
13157
13158   def ExpandNames(self):
13159     self._ExpandAndLockInstance()
13160
13161     # Lock all nodes for local exports
13162     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13163       # FIXME: lock only instance primary and destination node
13164       #
13165       # Sad but true, for now we have do lock all nodes, as we don't know where
13166       # the previous export might be, and in this LU we search for it and
13167       # remove it from its current node. In the future we could fix this by:
13168       #  - making a tasklet to search (share-lock all), then create the
13169       #    new one, then one to remove, after
13170       #  - removing the removal operation altogether
13171       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13172
13173   def DeclareLocks(self, level):
13174     """Last minute lock declaration."""
13175     # All nodes are locked anyway, so nothing to do here.
13176
13177   def BuildHooksEnv(self):
13178     """Build hooks env.
13179
13180     This will run on the master, primary node and target node.
13181
13182     """
13183     env = {
13184       "EXPORT_MODE": self.op.mode,
13185       "EXPORT_NODE": self.op.target_node,
13186       "EXPORT_DO_SHUTDOWN": self.op.shutdown,
13187       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
13188       # TODO: Generic function for boolean env variables
13189       "REMOVE_INSTANCE": str(bool(self.op.remove_instance)),
13190       }
13191
13192     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
13193
13194     return env
13195
13196   def BuildHooksNodes(self):
13197     """Build hooks nodes.
13198
13199     """
13200     nl = [self.cfg.GetMasterNode(), self.instance.primary_node]
13201
13202     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13203       nl.append(self.op.target_node)
13204
13205     return (nl, nl)
13206
13207   def CheckPrereq(self):
13208     """Check prerequisites.
13209
13210     This checks that the instance and node names are valid.
13211
13212     """
13213     instance_name = self.op.instance_name
13214
13215     self.instance = self.cfg.GetInstanceInfo(instance_name)
13216     assert self.instance is not None, \
13217           "Cannot retrieve locked instance %s" % self.op.instance_name
13218     _CheckNodeOnline(self, self.instance.primary_node)
13219
13220     if (self.op.remove_instance and
13221         self.instance.admin_state == constants.ADMINST_UP and
13222         not self.op.shutdown):
13223       raise errors.OpPrereqError("Can not remove instance without shutting it"
13224                                  " down before")
13225
13226     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13227       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
13228       self.dst_node = self.cfg.GetNodeInfo(self.op.target_node)
13229       assert self.dst_node is not None
13230
13231       _CheckNodeOnline(self, self.dst_node.name)
13232       _CheckNodeNotDrained(self, self.dst_node.name)
13233
13234       self._cds = None
13235       self.dest_disk_info = None
13236       self.dest_x509_ca = None
13237
13238     elif self.op.mode == constants.EXPORT_MODE_REMOTE:
13239       self.dst_node = None
13240
13241       if len(self.op.target_node) != len(self.instance.disks):
13242         raise errors.OpPrereqError(("Received destination information for %s"
13243                                     " disks, but instance %s has %s disks") %
13244                                    (len(self.op.target_node), instance_name,
13245                                     len(self.instance.disks)),
13246                                    errors.ECODE_INVAL)
13247
13248       cds = _GetClusterDomainSecret()
13249
13250       # Check X509 key name
13251       try:
13252         (key_name, hmac_digest, hmac_salt) = self.x509_key_name
13253       except (TypeError, ValueError), err:
13254         raise errors.OpPrereqError("Invalid data for X509 key name: %s" % err)
13255
13256       if not utils.VerifySha1Hmac(cds, key_name, hmac_digest, salt=hmac_salt):
13257         raise errors.OpPrereqError("HMAC for X509 key name is wrong",
13258                                    errors.ECODE_INVAL)
13259
13260       # Load and verify CA
13261       try:
13262         (cert, _) = utils.LoadSignedX509Certificate(self.dest_x509_ca_pem, cds)
13263       except OpenSSL.crypto.Error, err:
13264         raise errors.OpPrereqError("Unable to load destination X509 CA (%s)" %
13265                                    (err, ), errors.ECODE_INVAL)
13266
13267       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
13268       if errcode is not None:
13269         raise errors.OpPrereqError("Invalid destination X509 CA (%s)" %
13270                                    (msg, ), errors.ECODE_INVAL)
13271
13272       self.dest_x509_ca = cert
13273
13274       # Verify target information
13275       disk_info = []
13276       for idx, disk_data in enumerate(self.op.target_node):
13277         try:
13278           (host, port, magic) = \
13279             masterd.instance.CheckRemoteExportDiskInfo(cds, idx, disk_data)
13280         except errors.GenericError, err:
13281           raise errors.OpPrereqError("Target info for disk %s: %s" %
13282                                      (idx, err), errors.ECODE_INVAL)
13283
13284         disk_info.append((host, port, magic))
13285
13286       assert len(disk_info) == len(self.op.target_node)
13287       self.dest_disk_info = disk_info
13288
13289     else:
13290       raise errors.ProgrammerError("Unhandled export mode %r" %
13291                                    self.op.mode)
13292
13293     # instance disk type verification
13294     # TODO: Implement export support for file-based disks
13295     for disk in self.instance.disks:
13296       if disk.dev_type == constants.LD_FILE:
13297         raise errors.OpPrereqError("Export not supported for instances with"
13298                                    " file-based disks", errors.ECODE_INVAL)
13299
13300   def _CleanupExports(self, feedback_fn):
13301     """Removes exports of current instance from all other nodes.
13302
13303     If an instance in a cluster with nodes A..D was exported to node C, its
13304     exports will be removed from the nodes A, B and D.
13305
13306     """
13307     assert self.op.mode != constants.EXPORT_MODE_REMOTE
13308
13309     nodelist = self.cfg.GetNodeList()
13310     nodelist.remove(self.dst_node.name)
13311
13312     # on one-node clusters nodelist will be empty after the removal
13313     # if we proceed the backup would be removed because OpBackupQuery
13314     # substitutes an empty list with the full cluster node list.
13315     iname = self.instance.name
13316     if nodelist:
13317       feedback_fn("Removing old exports for instance %s" % iname)
13318       exportlist = self.rpc.call_export_list(nodelist)
13319       for node in exportlist:
13320         if exportlist[node].fail_msg:
13321           continue
13322         if iname in exportlist[node].payload:
13323           msg = self.rpc.call_export_remove(node, iname).fail_msg
13324           if msg:
13325             self.LogWarning("Could not remove older export for instance %s"
13326                             " on node %s: %s", iname, node, msg)
13327
13328   def Exec(self, feedback_fn):
13329     """Export an instance to an image in the cluster.
13330
13331     """
13332     assert self.op.mode in constants.EXPORT_MODES
13333
13334     instance = self.instance
13335     src_node = instance.primary_node
13336
13337     if self.op.shutdown:
13338       # shutdown the instance, but not the disks
13339       feedback_fn("Shutting down instance %s" % instance.name)
13340       result = self.rpc.call_instance_shutdown(src_node, instance,
13341                                                self.op.shutdown_timeout)
13342       # TODO: Maybe ignore failures if ignore_remove_failures is set
13343       result.Raise("Could not shutdown instance %s on"
13344                    " node %s" % (instance.name, src_node))
13345
13346     # set the disks ID correctly since call_instance_start needs the
13347     # correct drbd minor to create the symlinks
13348     for disk in instance.disks:
13349       self.cfg.SetDiskID(disk, src_node)
13350
13351     activate_disks = (instance.admin_state != constants.ADMINST_UP)
13352
13353     if activate_disks:
13354       # Activate the instance disks if we'exporting a stopped instance
13355       feedback_fn("Activating disks for %s" % instance.name)
13356       _StartInstanceDisks(self, instance, None)
13357
13358     try:
13359       helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
13360                                                      instance)
13361
13362       helper.CreateSnapshots()
13363       try:
13364         if (self.op.shutdown and
13365             instance.admin_state == constants.ADMINST_UP and
13366             not self.op.remove_instance):
13367           assert not activate_disks
13368           feedback_fn("Starting instance %s" % instance.name)
13369           result = self.rpc.call_instance_start(src_node,
13370                                                 (instance, None, None), False)
13371           msg = result.fail_msg
13372           if msg:
13373             feedback_fn("Failed to start instance: %s" % msg)
13374             _ShutdownInstanceDisks(self, instance)
13375             raise errors.OpExecError("Could not start instance: %s" % msg)
13376
13377         if self.op.mode == constants.EXPORT_MODE_LOCAL:
13378           (fin_resu, dresults) = helper.LocalExport(self.dst_node)
13379         elif self.op.mode == constants.EXPORT_MODE_REMOTE:
13380           connect_timeout = constants.RIE_CONNECT_TIMEOUT
13381           timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
13382
13383           (key_name, _, _) = self.x509_key_name
13384
13385           dest_ca_pem = \
13386             OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
13387                                             self.dest_x509_ca)
13388
13389           (fin_resu, dresults) = helper.RemoteExport(self.dest_disk_info,
13390                                                      key_name, dest_ca_pem,
13391                                                      timeouts)
13392       finally:
13393         helper.Cleanup()
13394
13395       # Check for backwards compatibility
13396       assert len(dresults) == len(instance.disks)
13397       assert compat.all(isinstance(i, bool) for i in dresults), \
13398              "Not all results are boolean: %r" % dresults
13399
13400     finally:
13401       if activate_disks:
13402         feedback_fn("Deactivating disks for %s" % instance.name)
13403         _ShutdownInstanceDisks(self, instance)
13404
13405     if not (compat.all(dresults) and fin_resu):
13406       failures = []
13407       if not fin_resu:
13408         failures.append("export finalization")
13409       if not compat.all(dresults):
13410         fdsk = utils.CommaJoin(idx for (idx, dsk) in enumerate(dresults)
13411                                if not dsk)
13412         failures.append("disk export: disk(s) %s" % fdsk)
13413
13414       raise errors.OpExecError("Export failed, errors in %s" %
13415                                utils.CommaJoin(failures))
13416
13417     # At this point, the export was successful, we can cleanup/finish
13418
13419     # Remove instance if requested
13420     if self.op.remove_instance:
13421       feedback_fn("Removing instance %s" % instance.name)
13422       _RemoveInstance(self, feedback_fn, instance,
13423                       self.op.ignore_remove_failures)
13424
13425     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13426       self._CleanupExports(feedback_fn)
13427
13428     return fin_resu, dresults
13429
13430
13431 class LUBackupRemove(NoHooksLU):
13432   """Remove exports related to the named instance.
13433
13434   """
13435   REQ_BGL = False
13436
13437   def ExpandNames(self):
13438     self.needed_locks = {}
13439     # We need all nodes to be locked in order for RemoveExport to work, but we
13440     # don't need to lock the instance itself, as nothing will happen to it (and
13441     # we can remove exports also for a removed instance)
13442     self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13443
13444   def Exec(self, feedback_fn):
13445     """Remove any export.
13446
13447     """
13448     instance_name = self.cfg.ExpandInstanceName(self.op.instance_name)
13449     # If the instance was not found we'll try with the name that was passed in.
13450     # This will only work if it was an FQDN, though.
13451     fqdn_warn = False
13452     if not instance_name:
13453       fqdn_warn = True
13454       instance_name = self.op.instance_name
13455
13456     locked_nodes = self.owned_locks(locking.LEVEL_NODE)
13457     exportlist = self.rpc.call_export_list(locked_nodes)
13458     found = False
13459     for node in exportlist:
13460       msg = exportlist[node].fail_msg
13461       if msg:
13462         self.LogWarning("Failed to query node %s (continuing): %s", node, msg)
13463         continue
13464       if instance_name in exportlist[node].payload:
13465         found = True
13466         result = self.rpc.call_export_remove(node, instance_name)
13467         msg = result.fail_msg
13468         if msg:
13469           logging.error("Could not remove export for instance %s"
13470                         " on node %s: %s", instance_name, node, msg)
13471
13472     if fqdn_warn and not found:
13473       feedback_fn("Export not found. If trying to remove an export belonging"
13474                   " to a deleted instance please use its Fully Qualified"
13475                   " Domain Name.")
13476
13477
13478 class LUGroupAdd(LogicalUnit):
13479   """Logical unit for creating node groups.
13480
13481   """
13482   HPATH = "group-add"
13483   HTYPE = constants.HTYPE_GROUP
13484   REQ_BGL = False
13485
13486   def ExpandNames(self):
13487     # We need the new group's UUID here so that we can create and acquire the
13488     # corresponding lock. Later, in Exec(), we'll indicate to cfg.AddNodeGroup
13489     # that it should not check whether the UUID exists in the configuration.
13490     self.group_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
13491     self.needed_locks = {}
13492     self.add_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
13493
13494   def CheckPrereq(self):
13495     """Check prerequisites.
13496
13497     This checks that the given group name is not an existing node group
13498     already.
13499
13500     """
13501     try:
13502       existing_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13503     except errors.OpPrereqError:
13504       pass
13505     else:
13506       raise errors.OpPrereqError("Desired group name '%s' already exists as a"
13507                                  " node group (UUID: %s)" %
13508                                  (self.op.group_name, existing_uuid),
13509                                  errors.ECODE_EXISTS)
13510
13511     if self.op.ndparams:
13512       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
13513
13514     if self.op.hv_state:
13515       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None)
13516     else:
13517       self.new_hv_state = None
13518
13519     if self.op.disk_state:
13520       self.new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state, None)
13521     else:
13522       self.new_disk_state = None
13523
13524     if self.op.diskparams:
13525       for templ in constants.DISK_TEMPLATES:
13526         if templ not in self.op.diskparams:
13527           self.op.diskparams[templ] = {}
13528         utils.ForceDictType(self.op.diskparams[templ], constants.DISK_DT_TYPES)
13529     else:
13530       self.op.diskparams = self.cfg.GetClusterInfo().diskparams
13531
13532     if self.op.ipolicy:
13533       cluster = self.cfg.GetClusterInfo()
13534       full_ipolicy = cluster.SimpleFillIPolicy(self.op.ipolicy)
13535       try:
13536         objects.InstancePolicy.CheckParameterSyntax(full_ipolicy)
13537       except errors.ConfigurationError, err:
13538         raise errors.OpPrereqError("Invalid instance policy: %s" % err,
13539                                    errors.ECODE_INVAL)
13540
13541   def BuildHooksEnv(self):
13542     """Build hooks env.
13543
13544     """
13545     return {
13546       "GROUP_NAME": self.op.group_name,
13547       }
13548
13549   def BuildHooksNodes(self):
13550     """Build hooks nodes.
13551
13552     """
13553     mn = self.cfg.GetMasterNode()
13554     return ([mn], [mn])
13555
13556   def Exec(self, feedback_fn):
13557     """Add the node group to the cluster.
13558
13559     """
13560     group_obj = objects.NodeGroup(name=self.op.group_name, members=[],
13561                                   uuid=self.group_uuid,
13562                                   alloc_policy=self.op.alloc_policy,
13563                                   ndparams=self.op.ndparams,
13564                                   diskparams=self.op.diskparams,
13565                                   ipolicy=self.op.ipolicy,
13566                                   hv_state_static=self.new_hv_state,
13567                                   disk_state_static=self.new_disk_state)
13568
13569     self.cfg.AddNodeGroup(group_obj, self.proc.GetECId(), check_uuid=False)
13570     del self.remove_locks[locking.LEVEL_NODEGROUP]
13571
13572
13573 class LUGroupAssignNodes(NoHooksLU):
13574   """Logical unit for assigning nodes to groups.
13575
13576   """
13577   REQ_BGL = False
13578
13579   def ExpandNames(self):
13580     # These raise errors.OpPrereqError on their own:
13581     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13582     self.op.nodes = _GetWantedNodes(self, self.op.nodes)
13583
13584     # We want to lock all the affected nodes and groups. We have readily
13585     # available the list of nodes, and the *destination* group. To gather the
13586     # list of "source" groups, we need to fetch node information later on.
13587     self.needed_locks = {
13588       locking.LEVEL_NODEGROUP: set([self.group_uuid]),
13589       locking.LEVEL_NODE: self.op.nodes,
13590       }
13591
13592   def DeclareLocks(self, level):
13593     if level == locking.LEVEL_NODEGROUP:
13594       assert len(self.needed_locks[locking.LEVEL_NODEGROUP]) == 1
13595
13596       # Try to get all affected nodes' groups without having the group or node
13597       # lock yet. Needs verification later in the code flow.
13598       groups = self.cfg.GetNodeGroupsFromNodes(self.op.nodes)
13599
13600       self.needed_locks[locking.LEVEL_NODEGROUP].update(groups)
13601
13602   def CheckPrereq(self):
13603     """Check prerequisites.
13604
13605     """
13606     assert self.needed_locks[locking.LEVEL_NODEGROUP]
13607     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
13608             frozenset(self.op.nodes))
13609
13610     expected_locks = (set([self.group_uuid]) |
13611                       self.cfg.GetNodeGroupsFromNodes(self.op.nodes))
13612     actual_locks = self.owned_locks(locking.LEVEL_NODEGROUP)
13613     if actual_locks != expected_locks:
13614       raise errors.OpExecError("Nodes changed groups since locks were acquired,"
13615                                " current groups are '%s', used to be '%s'" %
13616                                (utils.CommaJoin(expected_locks),
13617                                 utils.CommaJoin(actual_locks)))
13618
13619     self.node_data = self.cfg.GetAllNodesInfo()
13620     self.group = self.cfg.GetNodeGroup(self.group_uuid)
13621     instance_data = self.cfg.GetAllInstancesInfo()
13622
13623     if self.group is None:
13624       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
13625                                (self.op.group_name, self.group_uuid))
13626
13627     (new_splits, previous_splits) = \
13628       self.CheckAssignmentForSplitInstances([(node, self.group_uuid)
13629                                              for node in self.op.nodes],
13630                                             self.node_data, instance_data)
13631
13632     if new_splits:
13633       fmt_new_splits = utils.CommaJoin(utils.NiceSort(new_splits))
13634
13635       if not self.op.force:
13636         raise errors.OpExecError("The following instances get split by this"
13637                                  " change and --force was not given: %s" %
13638                                  fmt_new_splits)
13639       else:
13640         self.LogWarning("This operation will split the following instances: %s",
13641                         fmt_new_splits)
13642
13643         if previous_splits:
13644           self.LogWarning("In addition, these already-split instances continue"
13645                           " to be split across groups: %s",
13646                           utils.CommaJoin(utils.NiceSort(previous_splits)))
13647
13648   def Exec(self, feedback_fn):
13649     """Assign nodes to a new group.
13650
13651     """
13652     mods = [(node_name, self.group_uuid) for node_name in self.op.nodes]
13653
13654     self.cfg.AssignGroupNodes(mods)
13655
13656   @staticmethod
13657   def CheckAssignmentForSplitInstances(changes, node_data, instance_data):
13658     """Check for split instances after a node assignment.
13659
13660     This method considers a series of node assignments as an atomic operation,
13661     and returns information about split instances after applying the set of
13662     changes.
13663
13664     In particular, it returns information about newly split instances, and
13665     instances that were already split, and remain so after the change.
13666
13667     Only instances whose disk template is listed in constants.DTS_INT_MIRROR are
13668     considered.
13669
13670     @type changes: list of (node_name, new_group_uuid) pairs.
13671     @param changes: list of node assignments to consider.
13672     @param node_data: a dict with data for all nodes
13673     @param instance_data: a dict with all instances to consider
13674     @rtype: a two-tuple
13675     @return: a list of instances that were previously okay and result split as a
13676       consequence of this change, and a list of instances that were previously
13677       split and this change does not fix.
13678
13679     """
13680     changed_nodes = dict((node, group) for node, group in changes
13681                          if node_data[node].group != group)
13682
13683     all_split_instances = set()
13684     previously_split_instances = set()
13685
13686     def InstanceNodes(instance):
13687       return [instance.primary_node] + list(instance.secondary_nodes)
13688
13689     for inst in instance_data.values():
13690       if inst.disk_template not in constants.DTS_INT_MIRROR:
13691         continue
13692
13693       instance_nodes = InstanceNodes(inst)
13694
13695       if len(set(node_data[node].group for node in instance_nodes)) > 1:
13696         previously_split_instances.add(inst.name)
13697
13698       if len(set(changed_nodes.get(node, node_data[node].group)
13699                  for node in instance_nodes)) > 1:
13700         all_split_instances.add(inst.name)
13701
13702     return (list(all_split_instances - previously_split_instances),
13703             list(previously_split_instances & all_split_instances))
13704
13705
13706 class _GroupQuery(_QueryBase):
13707   FIELDS = query.GROUP_FIELDS
13708
13709   def ExpandNames(self, lu):
13710     lu.needed_locks = {}
13711
13712     self._all_groups = lu.cfg.GetAllNodeGroupsInfo()
13713     self._cluster = lu.cfg.GetClusterInfo()
13714     name_to_uuid = dict((g.name, g.uuid) for g in self._all_groups.values())
13715
13716     if not self.names:
13717       self.wanted = [name_to_uuid[name]
13718                      for name in utils.NiceSort(name_to_uuid.keys())]
13719     else:
13720       # Accept names to be either names or UUIDs.
13721       missing = []
13722       self.wanted = []
13723       all_uuid = frozenset(self._all_groups.keys())
13724
13725       for name in self.names:
13726         if name in all_uuid:
13727           self.wanted.append(name)
13728         elif name in name_to_uuid:
13729           self.wanted.append(name_to_uuid[name])
13730         else:
13731           missing.append(name)
13732
13733       if missing:
13734         raise errors.OpPrereqError("Some groups do not exist: %s" %
13735                                    utils.CommaJoin(missing),
13736                                    errors.ECODE_NOENT)
13737
13738   def DeclareLocks(self, lu, level):
13739     pass
13740
13741   def _GetQueryData(self, lu):
13742     """Computes the list of node groups and their attributes.
13743
13744     """
13745     do_nodes = query.GQ_NODE in self.requested_data
13746     do_instances = query.GQ_INST in self.requested_data
13747
13748     group_to_nodes = None
13749     group_to_instances = None
13750
13751     # For GQ_NODE, we need to map group->[nodes], and group->[instances] for
13752     # GQ_INST. The former is attainable with just GetAllNodesInfo(), but for the
13753     # latter GetAllInstancesInfo() is not enough, for we have to go through
13754     # instance->node. Hence, we will need to process nodes even if we only need
13755     # instance information.
13756     if do_nodes or do_instances:
13757       all_nodes = lu.cfg.GetAllNodesInfo()
13758       group_to_nodes = dict((uuid, []) for uuid in self.wanted)
13759       node_to_group = {}
13760
13761       for node in all_nodes.values():
13762         if node.group in group_to_nodes:
13763           group_to_nodes[node.group].append(node.name)
13764           node_to_group[node.name] = node.group
13765
13766       if do_instances:
13767         all_instances = lu.cfg.GetAllInstancesInfo()
13768         group_to_instances = dict((uuid, []) for uuid in self.wanted)
13769
13770         for instance in all_instances.values():
13771           node = instance.primary_node
13772           if node in node_to_group:
13773             group_to_instances[node_to_group[node]].append(instance.name)
13774
13775         if not do_nodes:
13776           # Do not pass on node information if it was not requested.
13777           group_to_nodes = None
13778
13779     return query.GroupQueryData(self._cluster,
13780                                 [self._all_groups[uuid]
13781                                  for uuid in self.wanted],
13782                                 group_to_nodes, group_to_instances)
13783
13784
13785 class LUGroupQuery(NoHooksLU):
13786   """Logical unit for querying node groups.
13787
13788   """
13789   REQ_BGL = False
13790
13791   def CheckArguments(self):
13792     self.gq = _GroupQuery(qlang.MakeSimpleFilter("name", self.op.names),
13793                           self.op.output_fields, False)
13794
13795   def ExpandNames(self):
13796     self.gq.ExpandNames(self)
13797
13798   def DeclareLocks(self, level):
13799     self.gq.DeclareLocks(self, level)
13800
13801   def Exec(self, feedback_fn):
13802     return self.gq.OldStyleQuery(self)
13803
13804
13805 class LUGroupSetParams(LogicalUnit):
13806   """Modifies the parameters of a node group.
13807
13808   """
13809   HPATH = "group-modify"
13810   HTYPE = constants.HTYPE_GROUP
13811   REQ_BGL = False
13812
13813   def CheckArguments(self):
13814     all_changes = [
13815       self.op.ndparams,
13816       self.op.diskparams,
13817       self.op.alloc_policy,
13818       self.op.hv_state,
13819       self.op.disk_state,
13820       self.op.ipolicy,
13821       ]
13822
13823     if all_changes.count(None) == len(all_changes):
13824       raise errors.OpPrereqError("Please pass at least one modification",
13825                                  errors.ECODE_INVAL)
13826
13827   def ExpandNames(self):
13828     # This raises errors.OpPrereqError on its own:
13829     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13830
13831     self.needed_locks = {
13832       locking.LEVEL_INSTANCE: [],
13833       locking.LEVEL_NODEGROUP: [self.group_uuid],
13834       }
13835
13836     self.share_locks[locking.LEVEL_INSTANCE] = 1
13837
13838   def DeclareLocks(self, level):
13839     if level == locking.LEVEL_INSTANCE:
13840       assert not self.needed_locks[locking.LEVEL_INSTANCE]
13841
13842       # Lock instances optimistically, needs verification once group lock has
13843       # been acquired
13844       self.needed_locks[locking.LEVEL_INSTANCE] = \
13845           self.cfg.GetNodeGroupInstances(self.group_uuid)
13846
13847   def CheckPrereq(self):
13848     """Check prerequisites.
13849
13850     """
13851     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
13852
13853     # Check if locked instances are still correct
13854     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
13855
13856     self.group = self.cfg.GetNodeGroup(self.group_uuid)
13857     cluster = self.cfg.GetClusterInfo()
13858
13859     if self.group is None:
13860       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
13861                                (self.op.group_name, self.group_uuid))
13862
13863     if self.op.ndparams:
13864       new_ndparams = _GetUpdatedParams(self.group.ndparams, self.op.ndparams)
13865       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
13866       self.new_ndparams = new_ndparams
13867
13868     if self.op.diskparams:
13869       self.new_diskparams = dict()
13870       for templ in constants.DISK_TEMPLATES:
13871         if templ not in self.op.diskparams:
13872           self.op.diskparams[templ] = {}
13873         new_templ_params = _GetUpdatedParams(self.group.diskparams[templ],
13874                                              self.op.diskparams[templ])
13875         utils.ForceDictType(new_templ_params, constants.DISK_DT_TYPES)
13876         self.new_diskparams[templ] = new_templ_params
13877
13878     if self.op.hv_state:
13879       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
13880                                                  self.group.hv_state_static)
13881
13882     if self.op.disk_state:
13883       self.new_disk_state = \
13884         _MergeAndVerifyDiskState(self.op.disk_state,
13885                                  self.group.disk_state_static)
13886
13887     if self.op.ipolicy:
13888       self.new_ipolicy = _GetUpdatedIPolicy(self.group.ipolicy,
13889                                             self.op.ipolicy,
13890                                             group_policy=True)
13891
13892       new_ipolicy = cluster.SimpleFillIPolicy(self.new_ipolicy)
13893       inst_filter = lambda inst: inst.name in owned_instances
13894       instances = self.cfg.GetInstancesInfoByFilter(inst_filter).values()
13895       violations = \
13896           _ComputeNewInstanceViolations(_CalculateGroupIPolicy(cluster,
13897                                                                self.group),
13898                                         new_ipolicy, instances)
13899
13900       if violations:
13901         self.LogWarning("After the ipolicy change the following instances"
13902                         " violate them: %s",
13903                         utils.CommaJoin(violations))
13904
13905   def BuildHooksEnv(self):
13906     """Build hooks env.
13907
13908     """
13909     return {
13910       "GROUP_NAME": self.op.group_name,
13911       "NEW_ALLOC_POLICY": self.op.alloc_policy,
13912       }
13913
13914   def BuildHooksNodes(self):
13915     """Build hooks nodes.
13916
13917     """
13918     mn = self.cfg.GetMasterNode()
13919     return ([mn], [mn])
13920
13921   def Exec(self, feedback_fn):
13922     """Modifies the node group.
13923
13924     """
13925     result = []
13926
13927     if self.op.ndparams:
13928       self.group.ndparams = self.new_ndparams
13929       result.append(("ndparams", str(self.group.ndparams)))
13930
13931     if self.op.diskparams:
13932       self.group.diskparams = self.new_diskparams
13933       result.append(("diskparams", str(self.group.diskparams)))
13934
13935     if self.op.alloc_policy:
13936       self.group.alloc_policy = self.op.alloc_policy
13937
13938     if self.op.hv_state:
13939       self.group.hv_state_static = self.new_hv_state
13940
13941     if self.op.disk_state:
13942       self.group.disk_state_static = self.new_disk_state
13943
13944     if self.op.ipolicy:
13945       self.group.ipolicy = self.new_ipolicy
13946
13947     self.cfg.Update(self.group, feedback_fn)
13948     return result
13949
13950
13951 class LUGroupRemove(LogicalUnit):
13952   HPATH = "group-remove"
13953   HTYPE = constants.HTYPE_GROUP
13954   REQ_BGL = False
13955
13956   def ExpandNames(self):
13957     # This will raises errors.OpPrereqError on its own:
13958     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13959     self.needed_locks = {
13960       locking.LEVEL_NODEGROUP: [self.group_uuid],
13961       }
13962
13963   def CheckPrereq(self):
13964     """Check prerequisites.
13965
13966     This checks that the given group name exists as a node group, that is
13967     empty (i.e., contains no nodes), and that is not the last group of the
13968     cluster.
13969
13970     """
13971     # Verify that the group is empty.
13972     group_nodes = [node.name
13973                    for node in self.cfg.GetAllNodesInfo().values()
13974                    if node.group == self.group_uuid]
13975
13976     if group_nodes:
13977       raise errors.OpPrereqError("Group '%s' not empty, has the following"
13978                                  " nodes: %s" %
13979                                  (self.op.group_name,
13980                                   utils.CommaJoin(utils.NiceSort(group_nodes))),
13981                                  errors.ECODE_STATE)
13982
13983     # Verify the cluster would not be left group-less.
13984     if len(self.cfg.GetNodeGroupList()) == 1:
13985       raise errors.OpPrereqError("Group '%s' is the only group,"
13986                                  " cannot be removed" %
13987                                  self.op.group_name,
13988                                  errors.ECODE_STATE)
13989
13990   def BuildHooksEnv(self):
13991     """Build hooks env.
13992
13993     """
13994     return {
13995       "GROUP_NAME": self.op.group_name,
13996       }
13997
13998   def BuildHooksNodes(self):
13999     """Build hooks nodes.
14000
14001     """
14002     mn = self.cfg.GetMasterNode()
14003     return ([mn], [mn])
14004
14005   def Exec(self, feedback_fn):
14006     """Remove the node group.
14007
14008     """
14009     try:
14010       self.cfg.RemoveNodeGroup(self.group_uuid)
14011     except errors.ConfigurationError:
14012       raise errors.OpExecError("Group '%s' with UUID %s disappeared" %
14013                                (self.op.group_name, self.group_uuid))
14014
14015     self.remove_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
14016
14017
14018 class LUGroupRename(LogicalUnit):
14019   HPATH = "group-rename"
14020   HTYPE = constants.HTYPE_GROUP
14021   REQ_BGL = False
14022
14023   def ExpandNames(self):
14024     # This raises errors.OpPrereqError on its own:
14025     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14026
14027     self.needed_locks = {
14028       locking.LEVEL_NODEGROUP: [self.group_uuid],
14029       }
14030
14031   def CheckPrereq(self):
14032     """Check prerequisites.
14033
14034     Ensures requested new name is not yet used.
14035
14036     """
14037     try:
14038       new_name_uuid = self.cfg.LookupNodeGroup(self.op.new_name)
14039     except errors.OpPrereqError:
14040       pass
14041     else:
14042       raise errors.OpPrereqError("Desired new name '%s' clashes with existing"
14043                                  " node group (UUID: %s)" %
14044                                  (self.op.new_name, new_name_uuid),
14045                                  errors.ECODE_EXISTS)
14046
14047   def BuildHooksEnv(self):
14048     """Build hooks env.
14049
14050     """
14051     return {
14052       "OLD_NAME": self.op.group_name,
14053       "NEW_NAME": self.op.new_name,
14054       }
14055
14056   def BuildHooksNodes(self):
14057     """Build hooks nodes.
14058
14059     """
14060     mn = self.cfg.GetMasterNode()
14061
14062     all_nodes = self.cfg.GetAllNodesInfo()
14063     all_nodes.pop(mn, None)
14064
14065     run_nodes = [mn]
14066     run_nodes.extend(node.name for node in all_nodes.values()
14067                      if node.group == self.group_uuid)
14068
14069     return (run_nodes, run_nodes)
14070
14071   def Exec(self, feedback_fn):
14072     """Rename the node group.
14073
14074     """
14075     group = self.cfg.GetNodeGroup(self.group_uuid)
14076
14077     if group is None:
14078       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
14079                                (self.op.group_name, self.group_uuid))
14080
14081     group.name = self.op.new_name
14082     self.cfg.Update(group, feedback_fn)
14083
14084     return self.op.new_name
14085
14086
14087 class LUGroupEvacuate(LogicalUnit):
14088   HPATH = "group-evacuate"
14089   HTYPE = constants.HTYPE_GROUP
14090   REQ_BGL = False
14091
14092   def ExpandNames(self):
14093     # This raises errors.OpPrereqError on its own:
14094     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14095
14096     if self.op.target_groups:
14097       self.req_target_uuids = map(self.cfg.LookupNodeGroup,
14098                                   self.op.target_groups)
14099     else:
14100       self.req_target_uuids = []
14101
14102     if self.group_uuid in self.req_target_uuids:
14103       raise errors.OpPrereqError("Group to be evacuated (%s) can not be used"
14104                                  " as a target group (targets are %s)" %
14105                                  (self.group_uuid,
14106                                   utils.CommaJoin(self.req_target_uuids)),
14107                                  errors.ECODE_INVAL)
14108
14109     self.op.iallocator = _GetDefaultIAllocator(self.cfg, self.op.iallocator)
14110
14111     self.share_locks = _ShareAll()
14112     self.needed_locks = {
14113       locking.LEVEL_INSTANCE: [],
14114       locking.LEVEL_NODEGROUP: [],
14115       locking.LEVEL_NODE: [],
14116       }
14117
14118   def DeclareLocks(self, level):
14119     if level == locking.LEVEL_INSTANCE:
14120       assert not self.needed_locks[locking.LEVEL_INSTANCE]
14121
14122       # Lock instances optimistically, needs verification once node and group
14123       # locks have been acquired
14124       self.needed_locks[locking.LEVEL_INSTANCE] = \
14125         self.cfg.GetNodeGroupInstances(self.group_uuid)
14126
14127     elif level == locking.LEVEL_NODEGROUP:
14128       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
14129
14130       if self.req_target_uuids:
14131         lock_groups = set([self.group_uuid] + self.req_target_uuids)
14132
14133         # Lock all groups used by instances optimistically; this requires going
14134         # via the node before it's locked, requiring verification later on
14135         lock_groups.update(group_uuid
14136                            for instance_name in
14137                              self.owned_locks(locking.LEVEL_INSTANCE)
14138                            for group_uuid in
14139                              self.cfg.GetInstanceNodeGroups(instance_name))
14140       else:
14141         # No target groups, need to lock all of them
14142         lock_groups = locking.ALL_SET
14143
14144       self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
14145
14146     elif level == locking.LEVEL_NODE:
14147       # This will only lock the nodes in the group to be evacuated which
14148       # contain actual instances
14149       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
14150       self._LockInstancesNodes()
14151
14152       # Lock all nodes in group to be evacuated and target groups
14153       owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
14154       assert self.group_uuid in owned_groups
14155       member_nodes = [node_name
14156                       for group in owned_groups
14157                       for node_name in self.cfg.GetNodeGroup(group).members]
14158       self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
14159
14160   def CheckPrereq(self):
14161     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
14162     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
14163     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
14164
14165     assert owned_groups.issuperset(self.req_target_uuids)
14166     assert self.group_uuid in owned_groups
14167
14168     # Check if locked instances are still correct
14169     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
14170
14171     # Get instance information
14172     self.instances = dict(self.cfg.GetMultiInstanceInfo(owned_instances))
14173
14174     # Check if node groups for locked instances are still correct
14175     _CheckInstancesNodeGroups(self.cfg, self.instances,
14176                               owned_groups, owned_nodes, self.group_uuid)
14177
14178     if self.req_target_uuids:
14179       # User requested specific target groups
14180       self.target_uuids = self.req_target_uuids
14181     else:
14182       # All groups except the one to be evacuated are potential targets
14183       self.target_uuids = [group_uuid for group_uuid in owned_groups
14184                            if group_uuid != self.group_uuid]
14185
14186       if not self.target_uuids:
14187         raise errors.OpPrereqError("There are no possible target groups",
14188                                    errors.ECODE_INVAL)
14189
14190   def BuildHooksEnv(self):
14191     """Build hooks env.
14192
14193     """
14194     return {
14195       "GROUP_NAME": self.op.group_name,
14196       "TARGET_GROUPS": " ".join(self.target_uuids),
14197       }
14198
14199   def BuildHooksNodes(self):
14200     """Build hooks nodes.
14201
14202     """
14203     mn = self.cfg.GetMasterNode()
14204
14205     assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
14206
14207     run_nodes = [mn] + self.cfg.GetNodeGroup(self.group_uuid).members
14208
14209     return (run_nodes, run_nodes)
14210
14211   def Exec(self, feedback_fn):
14212     instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
14213
14214     assert self.group_uuid not in self.target_uuids
14215
14216     ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_CHG_GROUP,
14217                      instances=instances, target_groups=self.target_uuids)
14218
14219     ial.Run(self.op.iallocator)
14220
14221     if not ial.success:
14222       raise errors.OpPrereqError("Can't compute group evacuation using"
14223                                  " iallocator '%s': %s" %
14224                                  (self.op.iallocator, ial.info),
14225                                  errors.ECODE_NORES)
14226
14227     jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
14228
14229     self.LogInfo("Iallocator returned %s job(s) for evacuating node group %s",
14230                  len(jobs), self.op.group_name)
14231
14232     return ResultWithJobs(jobs)
14233
14234
14235 class TagsLU(NoHooksLU): # pylint: disable=W0223
14236   """Generic tags LU.
14237
14238   This is an abstract class which is the parent of all the other tags LUs.
14239
14240   """
14241   def ExpandNames(self):
14242     self.group_uuid = None
14243     self.needed_locks = {}
14244
14245     if self.op.kind == constants.TAG_NODE:
14246       self.op.name = _ExpandNodeName(self.cfg, self.op.name)
14247       lock_level = locking.LEVEL_NODE
14248       lock_name = self.op.name
14249     elif self.op.kind == constants.TAG_INSTANCE:
14250       self.op.name = _ExpandInstanceName(self.cfg, self.op.name)
14251       lock_level = locking.LEVEL_INSTANCE
14252       lock_name = self.op.name
14253     elif self.op.kind == constants.TAG_NODEGROUP:
14254       self.group_uuid = self.cfg.LookupNodeGroup(self.op.name)
14255       lock_level = locking.LEVEL_NODEGROUP
14256       lock_name = self.group_uuid
14257     else:
14258       lock_level = None
14259       lock_name = None
14260
14261     if lock_level and getattr(self.op, "use_locking", True):
14262       self.needed_locks[lock_level] = lock_name
14263
14264     # FIXME: Acquire BGL for cluster tag operations (as of this writing it's
14265     # not possible to acquire the BGL based on opcode parameters)
14266
14267   def CheckPrereq(self):
14268     """Check prerequisites.
14269
14270     """
14271     if self.op.kind == constants.TAG_CLUSTER:
14272       self.target = self.cfg.GetClusterInfo()
14273     elif self.op.kind == constants.TAG_NODE:
14274       self.target = self.cfg.GetNodeInfo(self.op.name)
14275     elif self.op.kind == constants.TAG_INSTANCE:
14276       self.target = self.cfg.GetInstanceInfo(self.op.name)
14277     elif self.op.kind == constants.TAG_NODEGROUP:
14278       self.target = self.cfg.GetNodeGroup(self.group_uuid)
14279     else:
14280       raise errors.OpPrereqError("Wrong tag type requested (%s)" %
14281                                  str(self.op.kind), errors.ECODE_INVAL)
14282
14283
14284 class LUTagsGet(TagsLU):
14285   """Returns the tags of a given object.
14286
14287   """
14288   REQ_BGL = False
14289
14290   def ExpandNames(self):
14291     TagsLU.ExpandNames(self)
14292
14293     # Share locks as this is only a read operation
14294     self.share_locks = _ShareAll()
14295
14296   def Exec(self, feedback_fn):
14297     """Returns the tag list.
14298
14299     """
14300     return list(self.target.GetTags())
14301
14302
14303 class LUTagsSearch(NoHooksLU):
14304   """Searches the tags for a given pattern.
14305
14306   """
14307   REQ_BGL = False
14308
14309   def ExpandNames(self):
14310     self.needed_locks = {}
14311
14312   def CheckPrereq(self):
14313     """Check prerequisites.
14314
14315     This checks the pattern passed for validity by compiling it.
14316
14317     """
14318     try:
14319       self.re = re.compile(self.op.pattern)
14320     except re.error, err:
14321       raise errors.OpPrereqError("Invalid search pattern '%s': %s" %
14322                                  (self.op.pattern, err), errors.ECODE_INVAL)
14323
14324   def Exec(self, feedback_fn):
14325     """Returns the tag list.
14326
14327     """
14328     cfg = self.cfg
14329     tgts = [("/cluster", cfg.GetClusterInfo())]
14330     ilist = cfg.GetAllInstancesInfo().values()
14331     tgts.extend([("/instances/%s" % i.name, i) for i in ilist])
14332     nlist = cfg.GetAllNodesInfo().values()
14333     tgts.extend([("/nodes/%s" % n.name, n) for n in nlist])
14334     tgts.extend(("/nodegroup/%s" % n.name, n)
14335                 for n in cfg.GetAllNodeGroupsInfo().values())
14336     results = []
14337     for path, target in tgts:
14338       for tag in target.GetTags():
14339         if self.re.search(tag):
14340           results.append((path, tag))
14341     return results
14342
14343
14344 class LUTagsSet(TagsLU):
14345   """Sets a tag on a given object.
14346
14347   """
14348   REQ_BGL = False
14349
14350   def CheckPrereq(self):
14351     """Check prerequisites.
14352
14353     This checks the type and length of the tag name and value.
14354
14355     """
14356     TagsLU.CheckPrereq(self)
14357     for tag in self.op.tags:
14358       objects.TaggableObject.ValidateTag(tag)
14359
14360   def Exec(self, feedback_fn):
14361     """Sets the tag.
14362
14363     """
14364     try:
14365       for tag in self.op.tags:
14366         self.target.AddTag(tag)
14367     except errors.TagError, err:
14368       raise errors.OpExecError("Error while setting tag: %s" % str(err))
14369     self.cfg.Update(self.target, feedback_fn)
14370
14371
14372 class LUTagsDel(TagsLU):
14373   """Delete a list of tags from a given object.
14374
14375   """
14376   REQ_BGL = False
14377
14378   def CheckPrereq(self):
14379     """Check prerequisites.
14380
14381     This checks that we have the given tag.
14382
14383     """
14384     TagsLU.CheckPrereq(self)
14385     for tag in self.op.tags:
14386       objects.TaggableObject.ValidateTag(tag)
14387     del_tags = frozenset(self.op.tags)
14388     cur_tags = self.target.GetTags()
14389
14390     diff_tags = del_tags - cur_tags
14391     if diff_tags:
14392       diff_names = ("'%s'" % i for i in sorted(diff_tags))
14393       raise errors.OpPrereqError("Tag(s) %s not found" %
14394                                  (utils.CommaJoin(diff_names), ),
14395                                  errors.ECODE_NOENT)
14396
14397   def Exec(self, feedback_fn):
14398     """Remove the tag from the object.
14399
14400     """
14401     for tag in self.op.tags:
14402       self.target.RemoveTag(tag)
14403     self.cfg.Update(self.target, feedback_fn)
14404
14405
14406 class LUTestDelay(NoHooksLU):
14407   """Sleep for a specified amount of time.
14408
14409   This LU sleeps on the master and/or nodes for a specified amount of
14410   time.
14411
14412   """
14413   REQ_BGL = False
14414
14415   def ExpandNames(self):
14416     """Expand names and set required locks.
14417
14418     This expands the node list, if any.
14419
14420     """
14421     self.needed_locks = {}
14422     if self.op.on_nodes:
14423       # _GetWantedNodes can be used here, but is not always appropriate to use
14424       # this way in ExpandNames. Check LogicalUnit.ExpandNames docstring for
14425       # more information.
14426       self.op.on_nodes = _GetWantedNodes(self, self.op.on_nodes)
14427       self.needed_locks[locking.LEVEL_NODE] = self.op.on_nodes
14428
14429   def _TestDelay(self):
14430     """Do the actual sleep.
14431
14432     """
14433     if self.op.on_master:
14434       if not utils.TestDelay(self.op.duration):
14435         raise errors.OpExecError("Error during master delay test")
14436     if self.op.on_nodes:
14437       result = self.rpc.call_test_delay(self.op.on_nodes, self.op.duration)
14438       for node, node_result in result.items():
14439         node_result.Raise("Failure during rpc call to node %s" % node)
14440
14441   def Exec(self, feedback_fn):
14442     """Execute the test delay opcode, with the wanted repetitions.
14443
14444     """
14445     if self.op.repeat == 0:
14446       self._TestDelay()
14447     else:
14448       top_value = self.op.repeat - 1
14449       for i in range(self.op.repeat):
14450         self.LogInfo("Test delay iteration %d/%d" % (i, top_value))
14451         self._TestDelay()
14452
14453
14454 class LUTestJqueue(NoHooksLU):
14455   """Utility LU to test some aspects of the job queue.
14456
14457   """
14458   REQ_BGL = False
14459
14460   # Must be lower than default timeout for WaitForJobChange to see whether it
14461   # notices changed jobs
14462   _CLIENT_CONNECT_TIMEOUT = 20.0
14463   _CLIENT_CONFIRM_TIMEOUT = 60.0
14464
14465   @classmethod
14466   def _NotifyUsingSocket(cls, cb, errcls):
14467     """Opens a Unix socket and waits for another program to connect.
14468
14469     @type cb: callable
14470     @param cb: Callback to send socket name to client
14471     @type errcls: class
14472     @param errcls: Exception class to use for errors
14473
14474     """
14475     # Using a temporary directory as there's no easy way to create temporary
14476     # sockets without writing a custom loop around tempfile.mktemp and
14477     # socket.bind
14478     tmpdir = tempfile.mkdtemp()
14479     try:
14480       tmpsock = utils.PathJoin(tmpdir, "sock")
14481
14482       logging.debug("Creating temporary socket at %s", tmpsock)
14483       sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
14484       try:
14485         sock.bind(tmpsock)
14486         sock.listen(1)
14487
14488         # Send details to client
14489         cb(tmpsock)
14490
14491         # Wait for client to connect before continuing
14492         sock.settimeout(cls._CLIENT_CONNECT_TIMEOUT)
14493         try:
14494           (conn, _) = sock.accept()
14495         except socket.error, err:
14496           raise errcls("Client didn't connect in time (%s)" % err)
14497       finally:
14498         sock.close()
14499     finally:
14500       # Remove as soon as client is connected
14501       shutil.rmtree(tmpdir)
14502
14503     # Wait for client to close
14504     try:
14505       try:
14506         # pylint: disable=E1101
14507         # Instance of '_socketobject' has no ... member
14508         conn.settimeout(cls._CLIENT_CONFIRM_TIMEOUT)
14509         conn.recv(1)
14510       except socket.error, err:
14511         raise errcls("Client failed to confirm notification (%s)" % err)
14512     finally:
14513       conn.close()
14514
14515   def _SendNotification(self, test, arg, sockname):
14516     """Sends a notification to the client.
14517
14518     @type test: string
14519     @param test: Test name
14520     @param arg: Test argument (depends on test)
14521     @type sockname: string
14522     @param sockname: Socket path
14523
14524     """
14525     self.Log(constants.ELOG_JQUEUE_TEST, (sockname, test, arg))
14526
14527   def _Notify(self, prereq, test, arg):
14528     """Notifies the client of a test.
14529
14530     @type prereq: bool
14531     @param prereq: Whether this is a prereq-phase test
14532     @type test: string
14533     @param test: Test name
14534     @param arg: Test argument (depends on test)
14535
14536     """
14537     if prereq:
14538       errcls = errors.OpPrereqError
14539     else:
14540       errcls = errors.OpExecError
14541
14542     return self._NotifyUsingSocket(compat.partial(self._SendNotification,
14543                                                   test, arg),
14544                                    errcls)
14545
14546   def CheckArguments(self):
14547     self.checkargs_calls = getattr(self, "checkargs_calls", 0) + 1
14548     self.expandnames_calls = 0
14549
14550   def ExpandNames(self):
14551     checkargs_calls = getattr(self, "checkargs_calls", 0)
14552     if checkargs_calls < 1:
14553       raise errors.ProgrammerError("CheckArguments was not called")
14554
14555     self.expandnames_calls += 1
14556
14557     if self.op.notify_waitlock:
14558       self._Notify(True, constants.JQT_EXPANDNAMES, None)
14559
14560     self.LogInfo("Expanding names")
14561
14562     # Get lock on master node (just to get a lock, not for a particular reason)
14563     self.needed_locks = {
14564       locking.LEVEL_NODE: self.cfg.GetMasterNode(),
14565       }
14566
14567   def Exec(self, feedback_fn):
14568     if self.expandnames_calls < 1:
14569       raise errors.ProgrammerError("ExpandNames was not called")
14570
14571     if self.op.notify_exec:
14572       self._Notify(False, constants.JQT_EXEC, None)
14573
14574     self.LogInfo("Executing")
14575
14576     if self.op.log_messages:
14577       self._Notify(False, constants.JQT_STARTMSG, len(self.op.log_messages))
14578       for idx, msg in enumerate(self.op.log_messages):
14579         self.LogInfo("Sending log message %s", idx + 1)
14580         feedback_fn(constants.JQT_MSGPREFIX + msg)
14581         # Report how many test messages have been sent
14582         self._Notify(False, constants.JQT_LOGMSG, idx + 1)
14583
14584     if self.op.fail:
14585       raise errors.OpExecError("Opcode failure was requested")
14586
14587     return True
14588
14589
14590 class IAllocator(object):
14591   """IAllocator framework.
14592
14593   An IAllocator instance has three sets of attributes:
14594     - cfg that is needed to query the cluster
14595     - input data (all members of the _KEYS class attribute are required)
14596     - four buffer attributes (in|out_data|text), that represent the
14597       input (to the external script) in text and data structure format,
14598       and the output from it, again in two formats
14599     - the result variables from the script (success, info, nodes) for
14600       easy usage
14601
14602   """
14603   # pylint: disable=R0902
14604   # lots of instance attributes
14605
14606   def __init__(self, cfg, rpc_runner, mode, **kwargs):
14607     self.cfg = cfg
14608     self.rpc = rpc_runner
14609     # init buffer variables
14610     self.in_text = self.out_text = self.in_data = self.out_data = None
14611     # init all input fields so that pylint is happy
14612     self.mode = mode
14613     self.memory = self.disks = self.disk_template = self.spindle_use = None
14614     self.os = self.tags = self.nics = self.vcpus = None
14615     self.hypervisor = None
14616     self.relocate_from = None
14617     self.name = None
14618     self.instances = None
14619     self.evac_mode = None
14620     self.target_groups = []
14621     # computed fields
14622     self.required_nodes = None
14623     # init result fields
14624     self.success = self.info = self.result = None
14625
14626     try:
14627       (fn, keydata, self._result_check) = self._MODE_DATA[self.mode]
14628     except KeyError:
14629       raise errors.ProgrammerError("Unknown mode '%s' passed to the"
14630                                    " IAllocator" % self.mode)
14631
14632     keyset = [n for (n, _) in keydata]
14633
14634     for key in kwargs:
14635       if key not in keyset:
14636         raise errors.ProgrammerError("Invalid input parameter '%s' to"
14637                                      " IAllocator" % key)
14638       setattr(self, key, kwargs[key])
14639
14640     for key in keyset:
14641       if key not in kwargs:
14642         raise errors.ProgrammerError("Missing input parameter '%s' to"
14643                                      " IAllocator" % key)
14644     self._BuildInputData(compat.partial(fn, self), keydata)
14645
14646   def _ComputeClusterData(self):
14647     """Compute the generic allocator input data.
14648
14649     This is the data that is independent of the actual operation.
14650
14651     """
14652     cfg = self.cfg
14653     cluster_info = cfg.GetClusterInfo()
14654     # cluster data
14655     data = {
14656       "version": constants.IALLOCATOR_VERSION,
14657       "cluster_name": cfg.GetClusterName(),
14658       "cluster_tags": list(cluster_info.GetTags()),
14659       "enabled_hypervisors": list(cluster_info.enabled_hypervisors),
14660       "ipolicy": cluster_info.ipolicy,
14661       }
14662     ninfo = cfg.GetAllNodesInfo()
14663     iinfo = cfg.GetAllInstancesInfo().values()
14664     i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo]
14665
14666     # node data
14667     node_list = [n.name for n in ninfo.values() if n.vm_capable]
14668
14669     if self.mode == constants.IALLOCATOR_MODE_ALLOC:
14670       hypervisor_name = self.hypervisor
14671     elif self.mode == constants.IALLOCATOR_MODE_RELOC:
14672       hypervisor_name = cfg.GetInstanceInfo(self.name).hypervisor
14673     else:
14674       hypervisor_name = cluster_info.primary_hypervisor
14675
14676     node_data = self.rpc.call_node_info(node_list, [cfg.GetVGName()],
14677                                         [hypervisor_name])
14678     node_iinfo = \
14679       self.rpc.call_all_instances_info(node_list,
14680                                        cluster_info.enabled_hypervisors)
14681
14682     data["nodegroups"] = self._ComputeNodeGroupData(cfg)
14683
14684     config_ndata = self._ComputeBasicNodeData(cfg, ninfo)
14685     data["nodes"] = self._ComputeDynamicNodeData(ninfo, node_data, node_iinfo,
14686                                                  i_list, config_ndata)
14687     assert len(data["nodes"]) == len(ninfo), \
14688         "Incomplete node data computed"
14689
14690     data["instances"] = self._ComputeInstanceData(cluster_info, i_list)
14691
14692     self.in_data = data
14693
14694   @staticmethod
14695   def _ComputeNodeGroupData(cfg):
14696     """Compute node groups data.
14697
14698     """
14699     cluster = cfg.GetClusterInfo()
14700     ng = dict((guuid, {
14701       "name": gdata.name,
14702       "alloc_policy": gdata.alloc_policy,
14703       "ipolicy": _CalculateGroupIPolicy(cluster, gdata),
14704       })
14705       for guuid, gdata in cfg.GetAllNodeGroupsInfo().items())
14706
14707     return ng
14708
14709   @staticmethod
14710   def _ComputeBasicNodeData(cfg, node_cfg):
14711     """Compute global node data.
14712
14713     @rtype: dict
14714     @returns: a dict of name: (node dict, node config)
14715
14716     """
14717     # fill in static (config-based) values
14718     node_results = dict((ninfo.name, {
14719       "tags": list(ninfo.GetTags()),
14720       "primary_ip": ninfo.primary_ip,
14721       "secondary_ip": ninfo.secondary_ip,
14722       "offline": ninfo.offline,
14723       "drained": ninfo.drained,
14724       "master_candidate": ninfo.master_candidate,
14725       "group": ninfo.group,
14726       "master_capable": ninfo.master_capable,
14727       "vm_capable": ninfo.vm_capable,
14728       "ndparams": cfg.GetNdParams(ninfo),
14729       })
14730       for ninfo in node_cfg.values())
14731
14732     return node_results
14733
14734   @staticmethod
14735   def _ComputeDynamicNodeData(node_cfg, node_data, node_iinfo, i_list,
14736                               node_results):
14737     """Compute global node data.
14738
14739     @param node_results: the basic node structures as filled from the config
14740
14741     """
14742     #TODO(dynmem): compute the right data on MAX and MIN memory
14743     # make a copy of the current dict
14744     node_results = dict(node_results)
14745     for nname, nresult in node_data.items():
14746       assert nname in node_results, "Missing basic data for node %s" % nname
14747       ninfo = node_cfg[nname]
14748
14749       if not (ninfo.offline or ninfo.drained):
14750         nresult.Raise("Can't get data for node %s" % nname)
14751         node_iinfo[nname].Raise("Can't get node instance info from node %s" %
14752                                 nname)
14753         remote_info = _MakeLegacyNodeInfo(nresult.payload)
14754
14755         for attr in ["memory_total", "memory_free", "memory_dom0",
14756                      "vg_size", "vg_free", "cpu_total"]:
14757           if attr not in remote_info:
14758             raise errors.OpExecError("Node '%s' didn't return attribute"
14759                                      " '%s'" % (nname, attr))
14760           if not isinstance(remote_info[attr], int):
14761             raise errors.OpExecError("Node '%s' returned invalid value"
14762                                      " for '%s': %s" %
14763                                      (nname, attr, remote_info[attr]))
14764         # compute memory used by primary instances
14765         i_p_mem = i_p_up_mem = 0
14766         for iinfo, beinfo in i_list:
14767           if iinfo.primary_node == nname:
14768             i_p_mem += beinfo[constants.BE_MAXMEM]
14769             if iinfo.name not in node_iinfo[nname].payload:
14770               i_used_mem = 0
14771             else:
14772               i_used_mem = int(node_iinfo[nname].payload[iinfo.name]["memory"])
14773             i_mem_diff = beinfo[constants.BE_MAXMEM] - i_used_mem
14774             remote_info["memory_free"] -= max(0, i_mem_diff)
14775
14776             if iinfo.admin_state == constants.ADMINST_UP:
14777               i_p_up_mem += beinfo[constants.BE_MAXMEM]
14778
14779         # compute memory used by instances
14780         pnr_dyn = {
14781           "total_memory": remote_info["memory_total"],
14782           "reserved_memory": remote_info["memory_dom0"],
14783           "free_memory": remote_info["memory_free"],
14784           "total_disk": remote_info["vg_size"],
14785           "free_disk": remote_info["vg_free"],
14786           "total_cpus": remote_info["cpu_total"],
14787           "i_pri_memory": i_p_mem,
14788           "i_pri_up_memory": i_p_up_mem,
14789           }
14790         pnr_dyn.update(node_results[nname])
14791         node_results[nname] = pnr_dyn
14792
14793     return node_results
14794
14795   @staticmethod
14796   def _ComputeInstanceData(cluster_info, i_list):
14797     """Compute global instance data.
14798
14799     """
14800     instance_data = {}
14801     for iinfo, beinfo in i_list:
14802       nic_data = []
14803       for nic in iinfo.nics:
14804         filled_params = cluster_info.SimpleFillNIC(nic.nicparams)
14805         nic_dict = {
14806           "mac": nic.mac,
14807           "ip": nic.ip,
14808           "mode": filled_params[constants.NIC_MODE],
14809           "link": filled_params[constants.NIC_LINK],
14810           }
14811         if filled_params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
14812           nic_dict["bridge"] = filled_params[constants.NIC_LINK]
14813         nic_data.append(nic_dict)
14814       pir = {
14815         "tags": list(iinfo.GetTags()),
14816         "admin_state": iinfo.admin_state,
14817         "vcpus": beinfo[constants.BE_VCPUS],
14818         "memory": beinfo[constants.BE_MAXMEM],
14819         "spindle_use": beinfo[constants.BE_SPINDLE_USE],
14820         "os": iinfo.os,
14821         "nodes": [iinfo.primary_node] + list(iinfo.secondary_nodes),
14822         "nics": nic_data,
14823         "disks": [{constants.IDISK_SIZE: dsk.size,
14824                    constants.IDISK_MODE: dsk.mode}
14825                   for dsk in iinfo.disks],
14826         "disk_template": iinfo.disk_template,
14827         "hypervisor": iinfo.hypervisor,
14828         }
14829       pir["disk_space_total"] = _ComputeDiskSize(iinfo.disk_template,
14830                                                  pir["disks"])
14831       instance_data[iinfo.name] = pir
14832
14833     return instance_data
14834
14835   def _AddNewInstance(self):
14836     """Add new instance data to allocator structure.
14837
14838     This in combination with _AllocatorGetClusterData will create the
14839     correct structure needed as input for the allocator.
14840
14841     The checks for the completeness of the opcode must have already been
14842     done.
14843
14844     """
14845     disk_space = _ComputeDiskSize(self.disk_template, self.disks)
14846
14847     if self.disk_template in constants.DTS_INT_MIRROR:
14848       self.required_nodes = 2
14849     else:
14850       self.required_nodes = 1
14851
14852     request = {
14853       "name": self.name,
14854       "disk_template": self.disk_template,
14855       "tags": self.tags,
14856       "os": self.os,
14857       "vcpus": self.vcpus,
14858       "memory": self.memory,
14859       "spindle_use": self.spindle_use,
14860       "disks": self.disks,
14861       "disk_space_total": disk_space,
14862       "nics": self.nics,
14863       "required_nodes": self.required_nodes,
14864       "hypervisor": self.hypervisor,
14865       }
14866
14867     return request
14868
14869   def _AddRelocateInstance(self):
14870     """Add relocate instance data to allocator structure.
14871
14872     This in combination with _IAllocatorGetClusterData will create the
14873     correct structure needed as input for the allocator.
14874
14875     The checks for the completeness of the opcode must have already been
14876     done.
14877
14878     """
14879     instance = self.cfg.GetInstanceInfo(self.name)
14880     if instance is None:
14881       raise errors.ProgrammerError("Unknown instance '%s' passed to"
14882                                    " IAllocator" % self.name)
14883
14884     if instance.disk_template not in constants.DTS_MIRRORED:
14885       raise errors.OpPrereqError("Can't relocate non-mirrored instances",
14886                                  errors.ECODE_INVAL)
14887
14888     if instance.disk_template in constants.DTS_INT_MIRROR and \
14889         len(instance.secondary_nodes) != 1:
14890       raise errors.OpPrereqError("Instance has not exactly one secondary node",
14891                                  errors.ECODE_STATE)
14892
14893     self.required_nodes = 1
14894     disk_sizes = [{constants.IDISK_SIZE: disk.size} for disk in instance.disks]
14895     disk_space = _ComputeDiskSize(instance.disk_template, disk_sizes)
14896
14897     request = {
14898       "name": self.name,
14899       "disk_space_total": disk_space,
14900       "required_nodes": self.required_nodes,
14901       "relocate_from": self.relocate_from,
14902       }
14903     return request
14904
14905   def _AddNodeEvacuate(self):
14906     """Get data for node-evacuate requests.
14907
14908     """
14909     return {
14910       "instances": self.instances,
14911       "evac_mode": self.evac_mode,
14912       }
14913
14914   def _AddChangeGroup(self):
14915     """Get data for node-evacuate requests.
14916
14917     """
14918     return {
14919       "instances": self.instances,
14920       "target_groups": self.target_groups,
14921       }
14922
14923   def _BuildInputData(self, fn, keydata):
14924     """Build input data structures.
14925
14926     """
14927     self._ComputeClusterData()
14928
14929     request = fn()
14930     request["type"] = self.mode
14931     for keyname, keytype in keydata:
14932       if keyname not in request:
14933         raise errors.ProgrammerError("Request parameter %s is missing" %
14934                                      keyname)
14935       val = request[keyname]
14936       if not keytype(val):
14937         raise errors.ProgrammerError("Request parameter %s doesn't pass"
14938                                      " validation, value %s, expected"
14939                                      " type %s" % (keyname, val, keytype))
14940     self.in_data["request"] = request
14941
14942     self.in_text = serializer.Dump(self.in_data)
14943
14944   _STRING_LIST = ht.TListOf(ht.TString)
14945   _JOB_LIST = ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, {
14946      # pylint: disable=E1101
14947      # Class '...' has no 'OP_ID' member
14948      "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID,
14949                           opcodes.OpInstanceMigrate.OP_ID,
14950                           opcodes.OpInstanceReplaceDisks.OP_ID])
14951      })))
14952
14953   _NEVAC_MOVED = \
14954     ht.TListOf(ht.TAnd(ht.TIsLength(3),
14955                        ht.TItems([ht.TNonEmptyString,
14956                                   ht.TNonEmptyString,
14957                                   ht.TListOf(ht.TNonEmptyString),
14958                                  ])))
14959   _NEVAC_FAILED = \
14960     ht.TListOf(ht.TAnd(ht.TIsLength(2),
14961                        ht.TItems([ht.TNonEmptyString,
14962                                   ht.TMaybeString,
14963                                  ])))
14964   _NEVAC_RESULT = ht.TAnd(ht.TIsLength(3),
14965                           ht.TItems([_NEVAC_MOVED, _NEVAC_FAILED, _JOB_LIST]))
14966
14967   _MODE_DATA = {
14968     constants.IALLOCATOR_MODE_ALLOC:
14969       (_AddNewInstance,
14970        [
14971         ("name", ht.TString),
14972         ("memory", ht.TInt),
14973         ("spindle_use", ht.TInt),
14974         ("disks", ht.TListOf(ht.TDict)),
14975         ("disk_template", ht.TString),
14976         ("os", ht.TString),
14977         ("tags", _STRING_LIST),
14978         ("nics", ht.TListOf(ht.TDict)),
14979         ("vcpus", ht.TInt),
14980         ("hypervisor", ht.TString),
14981         ], ht.TList),
14982     constants.IALLOCATOR_MODE_RELOC:
14983       (_AddRelocateInstance,
14984        [("name", ht.TString), ("relocate_from", _STRING_LIST)],
14985        ht.TList),
14986      constants.IALLOCATOR_MODE_NODE_EVAC:
14987       (_AddNodeEvacuate, [
14988         ("instances", _STRING_LIST),
14989         ("evac_mode", ht.TElemOf(constants.IALLOCATOR_NEVAC_MODES)),
14990         ], _NEVAC_RESULT),
14991      constants.IALLOCATOR_MODE_CHG_GROUP:
14992       (_AddChangeGroup, [
14993         ("instances", _STRING_LIST),
14994         ("target_groups", _STRING_LIST),
14995         ], _NEVAC_RESULT),
14996     }
14997
14998   def Run(self, name, validate=True, call_fn=None):
14999     """Run an instance allocator and return the results.
15000
15001     """
15002     if call_fn is None:
15003       call_fn = self.rpc.call_iallocator_runner
15004
15005     result = call_fn(self.cfg.GetMasterNode(), name, self.in_text)
15006     result.Raise("Failure while running the iallocator script")
15007
15008     self.out_text = result.payload
15009     if validate:
15010       self._ValidateResult()
15011
15012   def _ValidateResult(self):
15013     """Process the allocator results.
15014
15015     This will process and if successful save the result in
15016     self.out_data and the other parameters.
15017
15018     """
15019     try:
15020       rdict = serializer.Load(self.out_text)
15021     except Exception, err:
15022       raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
15023
15024     if not isinstance(rdict, dict):
15025       raise errors.OpExecError("Can't parse iallocator results: not a dict")
15026
15027     # TODO: remove backwards compatiblity in later versions
15028     if "nodes" in rdict and "result" not in rdict:
15029       rdict["result"] = rdict["nodes"]
15030       del rdict["nodes"]
15031
15032     for key in "success", "info", "result":
15033       if key not in rdict:
15034         raise errors.OpExecError("Can't parse iallocator results:"
15035                                  " missing key '%s'" % key)
15036       setattr(self, key, rdict[key])
15037
15038     if not self._result_check(self.result):
15039       raise errors.OpExecError("Iallocator returned invalid result,"
15040                                " expected %s, got %s" %
15041                                (self._result_check, self.result),
15042                                errors.ECODE_INVAL)
15043
15044     if self.mode == constants.IALLOCATOR_MODE_RELOC:
15045       assert self.relocate_from is not None
15046       assert self.required_nodes == 1
15047
15048       node2group = dict((name, ndata["group"])
15049                         for (name, ndata) in self.in_data["nodes"].items())
15050
15051       fn = compat.partial(self._NodesToGroups, node2group,
15052                           self.in_data["nodegroups"])
15053
15054       instance = self.cfg.GetInstanceInfo(self.name)
15055       request_groups = fn(self.relocate_from + [instance.primary_node])
15056       result_groups = fn(rdict["result"] + [instance.primary_node])
15057
15058       if self.success and not set(result_groups).issubset(request_groups):
15059         raise errors.OpExecError("Groups of nodes returned by iallocator (%s)"
15060                                  " differ from original groups (%s)" %
15061                                  (utils.CommaJoin(result_groups),
15062                                   utils.CommaJoin(request_groups)))
15063
15064     elif self.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
15065       assert self.evac_mode in constants.IALLOCATOR_NEVAC_MODES
15066
15067     self.out_data = rdict
15068
15069   @staticmethod
15070   def _NodesToGroups(node2group, groups, nodes):
15071     """Returns a list of unique group names for a list of nodes.
15072
15073     @type node2group: dict
15074     @param node2group: Map from node name to group UUID
15075     @type groups: dict
15076     @param groups: Group information
15077     @type nodes: list
15078     @param nodes: Node names
15079
15080     """
15081     result = set()
15082
15083     for node in nodes:
15084       try:
15085         group_uuid = node2group[node]
15086       except KeyError:
15087         # Ignore unknown node
15088         pass
15089       else:
15090         try:
15091           group = groups[group_uuid]
15092         except KeyError:
15093           # Can't find group, let's use UUID
15094           group_name = group_uuid
15095         else:
15096           group_name = group["name"]
15097
15098         result.add(group_name)
15099
15100     return sorted(result)
15101
15102
15103 class LUTestAllocator(NoHooksLU):
15104   """Run allocator tests.
15105
15106   This LU runs the allocator tests
15107
15108   """
15109   def CheckPrereq(self):
15110     """Check prerequisites.
15111
15112     This checks the opcode parameters depending on the director and mode test.
15113
15114     """
15115     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
15116       for attr in ["memory", "disks", "disk_template",
15117                    "os", "tags", "nics", "vcpus"]:
15118         if not hasattr(self.op, attr):
15119           raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
15120                                      attr, errors.ECODE_INVAL)
15121       iname = self.cfg.ExpandInstanceName(self.op.name)
15122       if iname is not None:
15123         raise errors.OpPrereqError("Instance '%s' already in the cluster" %
15124                                    iname, errors.ECODE_EXISTS)
15125       if not isinstance(self.op.nics, list):
15126         raise errors.OpPrereqError("Invalid parameter 'nics'",
15127                                    errors.ECODE_INVAL)
15128       if not isinstance(self.op.disks, list):
15129         raise errors.OpPrereqError("Invalid parameter 'disks'",
15130                                    errors.ECODE_INVAL)
15131       for row in self.op.disks:
15132         if (not isinstance(row, dict) or
15133             constants.IDISK_SIZE not in row or
15134             not isinstance(row[constants.IDISK_SIZE], int) or
15135             constants.IDISK_MODE not in row or
15136             row[constants.IDISK_MODE] not in constants.DISK_ACCESS_SET):
15137           raise errors.OpPrereqError("Invalid contents of the 'disks'"
15138                                      " parameter", errors.ECODE_INVAL)
15139       if self.op.hypervisor is None:
15140         self.op.hypervisor = self.cfg.GetHypervisorType()
15141     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
15142       fname = _ExpandInstanceName(self.cfg, self.op.name)
15143       self.op.name = fname
15144       self.relocate_from = \
15145           list(self.cfg.GetInstanceInfo(fname).secondary_nodes)
15146     elif self.op.mode in (constants.IALLOCATOR_MODE_CHG_GROUP,
15147                           constants.IALLOCATOR_MODE_NODE_EVAC):
15148       if not self.op.instances:
15149         raise errors.OpPrereqError("Missing instances", errors.ECODE_INVAL)
15150       self.op.instances = _GetWantedInstances(self, self.op.instances)
15151     else:
15152       raise errors.OpPrereqError("Invalid test allocator mode '%s'" %
15153                                  self.op.mode, errors.ECODE_INVAL)
15154
15155     if self.op.direction == constants.IALLOCATOR_DIR_OUT:
15156       if self.op.allocator is None:
15157         raise errors.OpPrereqError("Missing allocator name",
15158                                    errors.ECODE_INVAL)
15159     elif self.op.direction != constants.IALLOCATOR_DIR_IN:
15160       raise errors.OpPrereqError("Wrong allocator test '%s'" %
15161                                  self.op.direction, errors.ECODE_INVAL)
15162
15163   def Exec(self, feedback_fn):
15164     """Run the allocator test.
15165
15166     """
15167     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
15168       ial = IAllocator(self.cfg, self.rpc,
15169                        mode=self.op.mode,
15170                        name=self.op.name,
15171                        memory=self.op.memory,
15172                        disks=self.op.disks,
15173                        disk_template=self.op.disk_template,
15174                        os=self.op.os,
15175                        tags=self.op.tags,
15176                        nics=self.op.nics,
15177                        vcpus=self.op.vcpus,
15178                        hypervisor=self.op.hypervisor,
15179                        )
15180     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
15181       ial = IAllocator(self.cfg, self.rpc,
15182                        mode=self.op.mode,
15183                        name=self.op.name,
15184                        relocate_from=list(self.relocate_from),
15185                        )
15186     elif self.op.mode == constants.IALLOCATOR_MODE_CHG_GROUP:
15187       ial = IAllocator(self.cfg, self.rpc,
15188                        mode=self.op.mode,
15189                        instances=self.op.instances,
15190                        target_groups=self.op.target_groups)
15191     elif self.op.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
15192       ial = IAllocator(self.cfg, self.rpc,
15193                        mode=self.op.mode,
15194                        instances=self.op.instances,
15195                        evac_mode=self.op.evac_mode)
15196     else:
15197       raise errors.ProgrammerError("Uncatched mode %s in"
15198                                    " LUTestAllocator.Exec", self.op.mode)
15199
15200     if self.op.direction == constants.IALLOCATOR_DIR_IN:
15201       result = ial.in_text
15202     else:
15203       ial.Run(self.op.allocator, validate=False)
15204       result = ial.out_text
15205     return result
15206
15207
15208 #: Query type implementations
15209 _QUERY_IMPL = {
15210   constants.QR_CLUSTER: _ClusterQuery,
15211   constants.QR_INSTANCE: _InstanceQuery,
15212   constants.QR_NODE: _NodeQuery,
15213   constants.QR_GROUP: _GroupQuery,
15214   constants.QR_OS: _OsQuery,
15215   constants.QR_EXPORT: _ExportQuery,
15216   }
15217
15218 assert set(_QUERY_IMPL.keys()) == constants.QR_VIA_OP
15219
15220
15221 def _GetQueryImplementation(name):
15222   """Returns the implemtnation for a query type.
15223
15224   @param name: Query type, must be one of L{constants.QR_VIA_OP}
15225
15226   """
15227   try:
15228     return _QUERY_IMPL[name]
15229   except KeyError:
15230     raise errors.OpPrereqError("Unknown query resource '%s'" % name,
15231                                errors.ECODE_INVAL)