Merge branch 'stable-2.6'
[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 _AnnotateDiskParams(instance, devs, cfg):
603   """Little helper wrapper to the rpc annotation method.
604
605   @param instance: The instance object
606   @type devs: List of L{objects.Disk}
607   @param devs: The root devices (not any of its children!)
608   @param cfg: The config object
609   @returns The annotated disk copies
610   @see L{rpc.AnnotateDiskParams}
611
612   """
613   return rpc.AnnotateDiskParams(instance.disk_template, devs,
614                                 cfg.GetInstanceDiskParams(instance))
615
616
617 def _CheckInstancesNodeGroups(cfg, instances, owned_groups, owned_nodes,
618                               cur_group_uuid):
619   """Checks if node groups for locked instances are still correct.
620
621   @type cfg: L{config.ConfigWriter}
622   @param cfg: Cluster configuration
623   @type instances: dict; string as key, L{objects.Instance} as value
624   @param instances: Dictionary, instance name as key, instance object as value
625   @type owned_groups: iterable of string
626   @param owned_groups: List of owned groups
627   @type owned_nodes: iterable of string
628   @param owned_nodes: List of owned nodes
629   @type cur_group_uuid: string or None
630   @param cur_group_uuid: Optional group UUID to check against instance's groups
631
632   """
633   for (name, inst) in instances.items():
634     assert owned_nodes.issuperset(inst.all_nodes), \
635       "Instance %s's nodes changed while we kept the lock" % name
636
637     inst_groups = _CheckInstanceNodeGroups(cfg, name, owned_groups)
638
639     assert cur_group_uuid is None or cur_group_uuid in inst_groups, \
640       "Instance %s has no node in group %s" % (name, cur_group_uuid)
641
642
643 def _CheckInstanceNodeGroups(cfg, instance_name, owned_groups):
644   """Checks if the owned node groups are still correct for an instance.
645
646   @type cfg: L{config.ConfigWriter}
647   @param cfg: The cluster configuration
648   @type instance_name: string
649   @param instance_name: Instance name
650   @type owned_groups: set or frozenset
651   @param owned_groups: List of currently owned node groups
652
653   """
654   inst_groups = cfg.GetInstanceNodeGroups(instance_name)
655
656   if not owned_groups.issuperset(inst_groups):
657     raise errors.OpPrereqError("Instance %s's node groups changed since"
658                                " locks were acquired, current groups are"
659                                " are '%s', owning groups '%s'; retry the"
660                                " operation" %
661                                (instance_name,
662                                 utils.CommaJoin(inst_groups),
663                                 utils.CommaJoin(owned_groups)),
664                                errors.ECODE_STATE)
665
666   return inst_groups
667
668
669 def _CheckNodeGroupInstances(cfg, group_uuid, owned_instances):
670   """Checks if the instances in a node group are still correct.
671
672   @type cfg: L{config.ConfigWriter}
673   @param cfg: The cluster configuration
674   @type group_uuid: string
675   @param group_uuid: Node group UUID
676   @type owned_instances: set or frozenset
677   @param owned_instances: List of currently owned instances
678
679   """
680   wanted_instances = cfg.GetNodeGroupInstances(group_uuid)
681   if owned_instances != wanted_instances:
682     raise errors.OpPrereqError("Instances in node group '%s' changed since"
683                                " locks were acquired, wanted '%s', have '%s';"
684                                " retry the operation" %
685                                (group_uuid,
686                                 utils.CommaJoin(wanted_instances),
687                                 utils.CommaJoin(owned_instances)),
688                                errors.ECODE_STATE)
689
690   return wanted_instances
691
692
693 def _SupportsOob(cfg, node):
694   """Tells if node supports OOB.
695
696   @type cfg: L{config.ConfigWriter}
697   @param cfg: The cluster configuration
698   @type node: L{objects.Node}
699   @param node: The node
700   @return: The OOB script if supported or an empty string otherwise
701
702   """
703   return cfg.GetNdParams(node)[constants.ND_OOB_PROGRAM]
704
705
706 def _GetWantedNodes(lu, nodes):
707   """Returns list of checked and expanded node names.
708
709   @type lu: L{LogicalUnit}
710   @param lu: the logical unit on whose behalf we execute
711   @type nodes: list
712   @param nodes: list of node names or None for all nodes
713   @rtype: list
714   @return: the list of nodes, sorted
715   @raise errors.ProgrammerError: if the nodes parameter is wrong type
716
717   """
718   if nodes:
719     return [_ExpandNodeName(lu.cfg, name) for name in nodes]
720
721   return utils.NiceSort(lu.cfg.GetNodeList())
722
723
724 def _GetWantedInstances(lu, instances):
725   """Returns list of checked and expanded instance names.
726
727   @type lu: L{LogicalUnit}
728   @param lu: the logical unit on whose behalf we execute
729   @type instances: list
730   @param instances: list of instance names or None for all instances
731   @rtype: list
732   @return: the list of instances, sorted
733   @raise errors.OpPrereqError: if the instances parameter is wrong type
734   @raise errors.OpPrereqError: if any of the passed instances is not found
735
736   """
737   if instances:
738     wanted = [_ExpandInstanceName(lu.cfg, name) for name in instances]
739   else:
740     wanted = utils.NiceSort(lu.cfg.GetInstanceList())
741   return wanted
742
743
744 def _GetUpdatedParams(old_params, update_dict,
745                       use_default=True, use_none=False):
746   """Return the new version of a parameter dictionary.
747
748   @type old_params: dict
749   @param old_params: old parameters
750   @type update_dict: dict
751   @param update_dict: dict containing new parameter values, or
752       constants.VALUE_DEFAULT to reset the parameter to its default
753       value
754   @param use_default: boolean
755   @type use_default: whether to recognise L{constants.VALUE_DEFAULT}
756       values as 'to be deleted' values
757   @param use_none: boolean
758   @type use_none: whether to recognise C{None} values as 'to be
759       deleted' values
760   @rtype: dict
761   @return: the new parameter dictionary
762
763   """
764   params_copy = copy.deepcopy(old_params)
765   for key, val in update_dict.iteritems():
766     if ((use_default and val == constants.VALUE_DEFAULT) or
767         (use_none and val is None)):
768       try:
769         del params_copy[key]
770       except KeyError:
771         pass
772     else:
773       params_copy[key] = val
774   return params_copy
775
776
777 def _GetUpdatedIPolicy(old_ipolicy, new_ipolicy, group_policy=False):
778   """Return the new version of a instance policy.
779
780   @param group_policy: whether this policy applies to a group and thus
781     we should support removal of policy entries
782
783   """
784   use_none = use_default = group_policy
785   ipolicy = copy.deepcopy(old_ipolicy)
786   for key, value in new_ipolicy.items():
787     if key not in constants.IPOLICY_ALL_KEYS:
788       raise errors.OpPrereqError("Invalid key in new ipolicy: %s" % key,
789                                  errors.ECODE_INVAL)
790     if key in constants.IPOLICY_ISPECS:
791       utils.ForceDictType(value, constants.ISPECS_PARAMETER_TYPES)
792       ipolicy[key] = _GetUpdatedParams(old_ipolicy.get(key, {}), value,
793                                        use_none=use_none,
794                                        use_default=use_default)
795     else:
796       if (not value or value == [constants.VALUE_DEFAULT] or
797           value == constants.VALUE_DEFAULT):
798         if group_policy:
799           del ipolicy[key]
800         else:
801           raise errors.OpPrereqError("Can't unset ipolicy attribute '%s'"
802                                      " on the cluster'" % key,
803                                      errors.ECODE_INVAL)
804       else:
805         if key in constants.IPOLICY_PARAMETERS:
806           # FIXME: we assume all such values are float
807           try:
808             ipolicy[key] = float(value)
809           except (TypeError, ValueError), err:
810             raise errors.OpPrereqError("Invalid value for attribute"
811                                        " '%s': '%s', error: %s" %
812                                        (key, value, err), errors.ECODE_INVAL)
813         else:
814           # FIXME: we assume all others are lists; this should be redone
815           # in a nicer way
816           ipolicy[key] = list(value)
817   try:
818     objects.InstancePolicy.CheckParameterSyntax(ipolicy)
819   except errors.ConfigurationError, err:
820     raise errors.OpPrereqError("Invalid instance policy: %s" % err,
821                                errors.ECODE_INVAL)
822   return ipolicy
823
824
825 def _UpdateAndVerifySubDict(base, updates, type_check):
826   """Updates and verifies a dict with sub dicts of the same type.
827
828   @param base: The dict with the old data
829   @param updates: The dict with the new data
830   @param type_check: Dict suitable to ForceDictType to verify correct types
831   @returns: A new dict with updated and verified values
832
833   """
834   def fn(old, value):
835     new = _GetUpdatedParams(old, value)
836     utils.ForceDictType(new, type_check)
837     return new
838
839   ret = copy.deepcopy(base)
840   ret.update(dict((key, fn(base.get(key, {}), value))
841                   for key, value in updates.items()))
842   return ret
843
844
845 def _MergeAndVerifyHvState(op_input, obj_input):
846   """Combines the hv state from an opcode with the one of the object
847
848   @param op_input: The input dict from the opcode
849   @param obj_input: The input dict from the objects
850   @return: The verified and updated dict
851
852   """
853   if op_input:
854     invalid_hvs = set(op_input) - constants.HYPER_TYPES
855     if invalid_hvs:
856       raise errors.OpPrereqError("Invalid hypervisor(s) in hypervisor state:"
857                                  " %s" % utils.CommaJoin(invalid_hvs),
858                                  errors.ECODE_INVAL)
859     if obj_input is None:
860       obj_input = {}
861     type_check = constants.HVSTS_PARAMETER_TYPES
862     return _UpdateAndVerifySubDict(obj_input, op_input, type_check)
863
864   return None
865
866
867 def _MergeAndVerifyDiskState(op_input, obj_input):
868   """Combines the disk state from an opcode with the one of the object
869
870   @param op_input: The input dict from the opcode
871   @param obj_input: The input dict from the objects
872   @return: The verified and updated dict
873   """
874   if op_input:
875     invalid_dst = set(op_input) - constants.DS_VALID_TYPES
876     if invalid_dst:
877       raise errors.OpPrereqError("Invalid storage type(s) in disk state: %s" %
878                                  utils.CommaJoin(invalid_dst),
879                                  errors.ECODE_INVAL)
880     type_check = constants.DSS_PARAMETER_TYPES
881     if obj_input is None:
882       obj_input = {}
883     return dict((key, _UpdateAndVerifySubDict(obj_input.get(key, {}), value,
884                                               type_check))
885                 for key, value in op_input.items())
886
887   return None
888
889
890 def _ReleaseLocks(lu, level, names=None, keep=None):
891   """Releases locks owned by an LU.
892
893   @type lu: L{LogicalUnit}
894   @param level: Lock level
895   @type names: list or None
896   @param names: Names of locks to release
897   @type keep: list or None
898   @param keep: Names of locks to retain
899
900   """
901   assert not (keep is not None and names is not None), \
902          "Only one of the 'names' and the 'keep' parameters can be given"
903
904   if names is not None:
905     should_release = names.__contains__
906   elif keep:
907     should_release = lambda name: name not in keep
908   else:
909     should_release = None
910
911   owned = lu.owned_locks(level)
912   if not owned:
913     # Not owning any lock at this level, do nothing
914     pass
915
916   elif should_release:
917     retain = []
918     release = []
919
920     # Determine which locks to release
921     for name in owned:
922       if should_release(name):
923         release.append(name)
924       else:
925         retain.append(name)
926
927     assert len(lu.owned_locks(level)) == (len(retain) + len(release))
928
929     # Release just some locks
930     lu.glm.release(level, names=release)
931
932     assert frozenset(lu.owned_locks(level)) == frozenset(retain)
933   else:
934     # Release everything
935     lu.glm.release(level)
936
937     assert not lu.glm.is_owned(level), "No locks should be owned"
938
939
940 def _MapInstanceDisksToNodes(instances):
941   """Creates a map from (node, volume) to instance name.
942
943   @type instances: list of L{objects.Instance}
944   @rtype: dict; tuple of (node name, volume name) as key, instance name as value
945
946   """
947   return dict(((node, vol), inst.name)
948               for inst in instances
949               for (node, vols) in inst.MapLVsByNode().items()
950               for vol in vols)
951
952
953 def _RunPostHook(lu, node_name):
954   """Runs the post-hook for an opcode on a single node.
955
956   """
957   hm = lu.proc.BuildHooksManager(lu)
958   try:
959     hm.RunPhase(constants.HOOKS_PHASE_POST, nodes=[node_name])
960   except:
961     # pylint: disable=W0702
962     lu.LogWarning("Errors occurred running hooks on %s" % node_name)
963
964
965 def _CheckOutputFields(static, dynamic, selected):
966   """Checks whether all selected fields are valid.
967
968   @type static: L{utils.FieldSet}
969   @param static: static fields set
970   @type dynamic: L{utils.FieldSet}
971   @param dynamic: dynamic fields set
972
973   """
974   f = utils.FieldSet()
975   f.Extend(static)
976   f.Extend(dynamic)
977
978   delta = f.NonMatching(selected)
979   if delta:
980     raise errors.OpPrereqError("Unknown output fields selected: %s"
981                                % ",".join(delta), errors.ECODE_INVAL)
982
983
984 def _CheckGlobalHvParams(params):
985   """Validates that given hypervisor params are not global ones.
986
987   This will ensure that instances don't get customised versions of
988   global params.
989
990   """
991   used_globals = constants.HVC_GLOBALS.intersection(params)
992   if used_globals:
993     msg = ("The following hypervisor parameters are global and cannot"
994            " be customized at instance level, please modify them at"
995            " cluster level: %s" % utils.CommaJoin(used_globals))
996     raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
997
998
999 def _CheckNodeOnline(lu, node, msg=None):
1000   """Ensure that a given node is online.
1001
1002   @param lu: the LU on behalf of which we make the check
1003   @param node: the node to check
1004   @param msg: if passed, should be a message to replace the default one
1005   @raise errors.OpPrereqError: if the node is offline
1006
1007   """
1008   if msg is None:
1009     msg = "Can't use offline node"
1010   if lu.cfg.GetNodeInfo(node).offline:
1011     raise errors.OpPrereqError("%s: %s" % (msg, node), errors.ECODE_STATE)
1012
1013
1014 def _CheckNodeNotDrained(lu, node):
1015   """Ensure that a given node is not drained.
1016
1017   @param lu: the LU on behalf of which we make the check
1018   @param node: the node to check
1019   @raise errors.OpPrereqError: if the node is drained
1020
1021   """
1022   if lu.cfg.GetNodeInfo(node).drained:
1023     raise errors.OpPrereqError("Can't use drained node %s" % node,
1024                                errors.ECODE_STATE)
1025
1026
1027 def _CheckNodeVmCapable(lu, node):
1028   """Ensure that a given node is vm capable.
1029
1030   @param lu: the LU on behalf of which we make the check
1031   @param node: the node to check
1032   @raise errors.OpPrereqError: if the node is not vm capable
1033
1034   """
1035   if not lu.cfg.GetNodeInfo(node).vm_capable:
1036     raise errors.OpPrereqError("Can't use non-vm_capable node %s" % node,
1037                                errors.ECODE_STATE)
1038
1039
1040 def _CheckNodeHasOS(lu, node, os_name, force_variant):
1041   """Ensure that a node supports a given OS.
1042
1043   @param lu: the LU on behalf of which we make the check
1044   @param node: the node to check
1045   @param os_name: the OS to query about
1046   @param force_variant: whether to ignore variant errors
1047   @raise errors.OpPrereqError: if the node is not supporting the OS
1048
1049   """
1050   result = lu.rpc.call_os_get(node, os_name)
1051   result.Raise("OS '%s' not in supported OS list for node %s" %
1052                (os_name, node),
1053                prereq=True, ecode=errors.ECODE_INVAL)
1054   if not force_variant:
1055     _CheckOSVariant(result.payload, os_name)
1056
1057
1058 def _CheckNodeHasSecondaryIP(lu, node, secondary_ip, prereq):
1059   """Ensure that a node has the given secondary ip.
1060
1061   @type lu: L{LogicalUnit}
1062   @param lu: the LU on behalf of which we make the check
1063   @type node: string
1064   @param node: the node to check
1065   @type secondary_ip: string
1066   @param secondary_ip: the ip to check
1067   @type prereq: boolean
1068   @param prereq: whether to throw a prerequisite or an execute error
1069   @raise errors.OpPrereqError: if the node doesn't have the ip, and prereq=True
1070   @raise errors.OpExecError: if the node doesn't have the ip, and prereq=False
1071
1072   """
1073   result = lu.rpc.call_node_has_ip_address(node, secondary_ip)
1074   result.Raise("Failure checking secondary ip on node %s" % node,
1075                prereq=prereq, ecode=errors.ECODE_ENVIRON)
1076   if not result.payload:
1077     msg = ("Node claims it doesn't have the secondary ip you gave (%s),"
1078            " please fix and re-run this command" % secondary_ip)
1079     if prereq:
1080       raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
1081     else:
1082       raise errors.OpExecError(msg)
1083
1084
1085 def _GetClusterDomainSecret():
1086   """Reads the cluster domain secret.
1087
1088   """
1089   return utils.ReadOneLineFile(constants.CLUSTER_DOMAIN_SECRET_FILE,
1090                                strict=True)
1091
1092
1093 def _CheckInstanceState(lu, instance, req_states, msg=None):
1094   """Ensure that an instance is in one of the required states.
1095
1096   @param lu: the LU on behalf of which we make the check
1097   @param instance: the instance to check
1098   @param msg: if passed, should be a message to replace the default one
1099   @raise errors.OpPrereqError: if the instance is not in the required state
1100
1101   """
1102   if msg is None:
1103     msg = "can't use instance from outside %s states" % ", ".join(req_states)
1104   if instance.admin_state not in req_states:
1105     raise errors.OpPrereqError("Instance '%s' is marked to be %s, %s" %
1106                                (instance.name, instance.admin_state, msg),
1107                                errors.ECODE_STATE)
1108
1109   if constants.ADMINST_UP not in req_states:
1110     pnode = instance.primary_node
1111     ins_l = lu.rpc.call_instance_list([pnode], [instance.hypervisor])[pnode]
1112     ins_l.Raise("Can't contact node %s for instance information" % pnode,
1113                 prereq=True, ecode=errors.ECODE_ENVIRON)
1114
1115     if instance.name in ins_l.payload:
1116       raise errors.OpPrereqError("Instance %s is running, %s" %
1117                                  (instance.name, msg), errors.ECODE_STATE)
1118
1119
1120 def _ComputeMinMaxSpec(name, qualifier, ipolicy, value):
1121   """Computes if value is in the desired range.
1122
1123   @param name: name of the parameter for which we perform the check
1124   @param qualifier: a qualifier used in the error message (e.g. 'disk/1',
1125       not just 'disk')
1126   @param ipolicy: dictionary containing min, max and std values
1127   @param value: actual value that we want to use
1128   @return: None or element not meeting the criteria
1129
1130
1131   """
1132   if value in [None, constants.VALUE_AUTO]:
1133     return None
1134   max_v = ipolicy[constants.ISPECS_MAX].get(name, value)
1135   min_v = ipolicy[constants.ISPECS_MIN].get(name, value)
1136   if value > max_v or min_v > value:
1137     if qualifier:
1138       fqn = "%s/%s" % (name, qualifier)
1139     else:
1140       fqn = name
1141     return ("%s value %s is not in range [%s, %s]" %
1142             (fqn, value, min_v, max_v))
1143   return None
1144
1145
1146 def _ComputeIPolicySpecViolation(ipolicy, mem_size, cpu_count, disk_count,
1147                                  nic_count, disk_sizes, spindle_use,
1148                                  _compute_fn=_ComputeMinMaxSpec):
1149   """Verifies ipolicy against provided specs.
1150
1151   @type ipolicy: dict
1152   @param ipolicy: The ipolicy
1153   @type mem_size: int
1154   @param mem_size: The memory size
1155   @type cpu_count: int
1156   @param cpu_count: Used cpu cores
1157   @type disk_count: int
1158   @param disk_count: Number of disks used
1159   @type nic_count: int
1160   @param nic_count: Number of nics used
1161   @type disk_sizes: list of ints
1162   @param disk_sizes: Disk sizes of used disk (len must match C{disk_count})
1163   @type spindle_use: int
1164   @param spindle_use: The number of spindles this instance uses
1165   @param _compute_fn: The compute function (unittest only)
1166   @return: A list of violations, or an empty list of no violations are found
1167
1168   """
1169   assert disk_count == len(disk_sizes)
1170
1171   test_settings = [
1172     (constants.ISPEC_MEM_SIZE, "", mem_size),
1173     (constants.ISPEC_CPU_COUNT, "", cpu_count),
1174     (constants.ISPEC_DISK_COUNT, "", disk_count),
1175     (constants.ISPEC_NIC_COUNT, "", nic_count),
1176     (constants.ISPEC_SPINDLE_USE, "", spindle_use),
1177     ] + [(constants.ISPEC_DISK_SIZE, str(idx), d)
1178          for idx, d in enumerate(disk_sizes)]
1179
1180   return filter(None,
1181                 (_compute_fn(name, qualifier, ipolicy, value)
1182                  for (name, qualifier, value) in test_settings))
1183
1184
1185 def _ComputeIPolicyInstanceViolation(ipolicy, instance,
1186                                      _compute_fn=_ComputeIPolicySpecViolation):
1187   """Compute if instance meets the specs of ipolicy.
1188
1189   @type ipolicy: dict
1190   @param ipolicy: The ipolicy to verify against
1191   @type instance: L{objects.Instance}
1192   @param instance: The instance to verify
1193   @param _compute_fn: The function to verify ipolicy (unittest only)
1194   @see: L{_ComputeIPolicySpecViolation}
1195
1196   """
1197   mem_size = instance.beparams.get(constants.BE_MAXMEM, None)
1198   cpu_count = instance.beparams.get(constants.BE_VCPUS, None)
1199   spindle_use = instance.beparams.get(constants.BE_SPINDLE_USE, None)
1200   disk_count = len(instance.disks)
1201   disk_sizes = [disk.size for disk in instance.disks]
1202   nic_count = len(instance.nics)
1203
1204   return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
1205                      disk_sizes, spindle_use)
1206
1207
1208 def _ComputeIPolicyInstanceSpecViolation(ipolicy, instance_spec,
1209     _compute_fn=_ComputeIPolicySpecViolation):
1210   """Compute if instance specs meets the specs of ipolicy.
1211
1212   @type ipolicy: dict
1213   @param ipolicy: The ipolicy to verify against
1214   @param instance_spec: dict
1215   @param instance_spec: The instance spec to verify
1216   @param _compute_fn: The function to verify ipolicy (unittest only)
1217   @see: L{_ComputeIPolicySpecViolation}
1218
1219   """
1220   mem_size = instance_spec.get(constants.ISPEC_MEM_SIZE, None)
1221   cpu_count = instance_spec.get(constants.ISPEC_CPU_COUNT, None)
1222   disk_count = instance_spec.get(constants.ISPEC_DISK_COUNT, 0)
1223   disk_sizes = instance_spec.get(constants.ISPEC_DISK_SIZE, [])
1224   nic_count = instance_spec.get(constants.ISPEC_NIC_COUNT, 0)
1225   spindle_use = instance_spec.get(constants.ISPEC_SPINDLE_USE, None)
1226
1227   return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
1228                      disk_sizes, spindle_use)
1229
1230
1231 def _ComputeIPolicyNodeViolation(ipolicy, instance, current_group,
1232                                  target_group,
1233                                  _compute_fn=_ComputeIPolicyInstanceViolation):
1234   """Compute if instance meets the specs of the new target group.
1235
1236   @param ipolicy: The ipolicy to verify
1237   @param instance: The instance object to verify
1238   @param current_group: The current group of the instance
1239   @param target_group: The new group of the instance
1240   @param _compute_fn: The function to verify ipolicy (unittest only)
1241   @see: L{_ComputeIPolicySpecViolation}
1242
1243   """
1244   if current_group == target_group:
1245     return []
1246   else:
1247     return _compute_fn(ipolicy, instance)
1248
1249
1250 def _CheckTargetNodeIPolicy(lu, ipolicy, instance, node, ignore=False,
1251                             _compute_fn=_ComputeIPolicyNodeViolation):
1252   """Checks that the target node is correct in terms of instance policy.
1253
1254   @param ipolicy: The ipolicy to verify
1255   @param instance: The instance object to verify
1256   @param node: The new node to relocate
1257   @param ignore: Ignore violations of the ipolicy
1258   @param _compute_fn: The function to verify ipolicy (unittest only)
1259   @see: L{_ComputeIPolicySpecViolation}
1260
1261   """
1262   primary_node = lu.cfg.GetNodeInfo(instance.primary_node)
1263   res = _compute_fn(ipolicy, instance, primary_node.group, node.group)
1264
1265   if res:
1266     msg = ("Instance does not meet target node group's (%s) instance"
1267            " policy: %s") % (node.group, utils.CommaJoin(res))
1268     if ignore:
1269       lu.LogWarning(msg)
1270     else:
1271       raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
1272
1273
1274 def _ComputeNewInstanceViolations(old_ipolicy, new_ipolicy, instances):
1275   """Computes a set of any instances that would violate the new ipolicy.
1276
1277   @param old_ipolicy: The current (still in-place) ipolicy
1278   @param new_ipolicy: The new (to become) ipolicy
1279   @param instances: List of instances to verify
1280   @return: A list of instances which violates the new ipolicy but
1281       did not before
1282
1283   """
1284   return (_ComputeViolatingInstances(new_ipolicy, instances) -
1285           _ComputeViolatingInstances(old_ipolicy, instances))
1286
1287
1288 def _ExpandItemName(fn, name, kind):
1289   """Expand an item name.
1290
1291   @param fn: the function to use for expansion
1292   @param name: requested item name
1293   @param kind: text description ('Node' or 'Instance')
1294   @return: the resolved (full) name
1295   @raise errors.OpPrereqError: if the item is not found
1296
1297   """
1298   full_name = fn(name)
1299   if full_name is None:
1300     raise errors.OpPrereqError("%s '%s' not known" % (kind, name),
1301                                errors.ECODE_NOENT)
1302   return full_name
1303
1304
1305 def _ExpandNodeName(cfg, name):
1306   """Wrapper over L{_ExpandItemName} for nodes."""
1307   return _ExpandItemName(cfg.ExpandNodeName, name, "Node")
1308
1309
1310 def _ExpandInstanceName(cfg, name):
1311   """Wrapper over L{_ExpandItemName} for instance."""
1312   return _ExpandItemName(cfg.ExpandInstanceName, name, "Instance")
1313
1314
1315 def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
1316                           minmem, maxmem, vcpus, nics, disk_template, disks,
1317                           bep, hvp, hypervisor_name, tags):
1318   """Builds instance related env variables for hooks
1319
1320   This builds the hook environment from individual variables.
1321
1322   @type name: string
1323   @param name: the name of the instance
1324   @type primary_node: string
1325   @param primary_node: the name of the instance's primary node
1326   @type secondary_nodes: list
1327   @param secondary_nodes: list of secondary nodes as strings
1328   @type os_type: string
1329   @param os_type: the name of the instance's OS
1330   @type status: string
1331   @param status: the desired status of the instance
1332   @type minmem: string
1333   @param minmem: the minimum memory size of the instance
1334   @type maxmem: string
1335   @param maxmem: the maximum memory size of the instance
1336   @type vcpus: string
1337   @param vcpus: the count of VCPUs the instance has
1338   @type nics: list
1339   @param nics: list of tuples (ip, mac, mode, link) representing
1340       the NICs the instance has
1341   @type disk_template: string
1342   @param disk_template: the disk template of the instance
1343   @type disks: list
1344   @param disks: the list of (size, mode) pairs
1345   @type bep: dict
1346   @param bep: the backend parameters for the instance
1347   @type hvp: dict
1348   @param hvp: the hypervisor parameters for the instance
1349   @type hypervisor_name: string
1350   @param hypervisor_name: the hypervisor for the instance
1351   @type tags: list
1352   @param tags: list of instance tags as strings
1353   @rtype: dict
1354   @return: the hook environment for this instance
1355
1356   """
1357   env = {
1358     "OP_TARGET": name,
1359     "INSTANCE_NAME": name,
1360     "INSTANCE_PRIMARY": primary_node,
1361     "INSTANCE_SECONDARIES": " ".join(secondary_nodes),
1362     "INSTANCE_OS_TYPE": os_type,
1363     "INSTANCE_STATUS": status,
1364     "INSTANCE_MINMEM": minmem,
1365     "INSTANCE_MAXMEM": maxmem,
1366     # TODO(2.7) remove deprecated "memory" value
1367     "INSTANCE_MEMORY": maxmem,
1368     "INSTANCE_VCPUS": vcpus,
1369     "INSTANCE_DISK_TEMPLATE": disk_template,
1370     "INSTANCE_HYPERVISOR": hypervisor_name,
1371   }
1372   if nics:
1373     nic_count = len(nics)
1374     for idx, (ip, mac, mode, link) in enumerate(nics):
1375       if ip is None:
1376         ip = ""
1377       env["INSTANCE_NIC%d_IP" % idx] = ip
1378       env["INSTANCE_NIC%d_MAC" % idx] = mac
1379       env["INSTANCE_NIC%d_MODE" % idx] = mode
1380       env["INSTANCE_NIC%d_LINK" % idx] = link
1381       if mode == constants.NIC_MODE_BRIDGED:
1382         env["INSTANCE_NIC%d_BRIDGE" % idx] = link
1383   else:
1384     nic_count = 0
1385
1386   env["INSTANCE_NIC_COUNT"] = nic_count
1387
1388   if disks:
1389     disk_count = len(disks)
1390     for idx, (size, mode) in enumerate(disks):
1391       env["INSTANCE_DISK%d_SIZE" % idx] = size
1392       env["INSTANCE_DISK%d_MODE" % idx] = mode
1393   else:
1394     disk_count = 0
1395
1396   env["INSTANCE_DISK_COUNT"] = disk_count
1397
1398   if not tags:
1399     tags = []
1400
1401   env["INSTANCE_TAGS"] = " ".join(tags)
1402
1403   for source, kind in [(bep, "BE"), (hvp, "HV")]:
1404     for key, value in source.items():
1405       env["INSTANCE_%s_%s" % (kind, key)] = value
1406
1407   return env
1408
1409
1410 def _NICListToTuple(lu, nics):
1411   """Build a list of nic information tuples.
1412
1413   This list is suitable to be passed to _BuildInstanceHookEnv or as a return
1414   value in LUInstanceQueryData.
1415
1416   @type lu:  L{LogicalUnit}
1417   @param lu: the logical unit on whose behalf we execute
1418   @type nics: list of L{objects.NIC}
1419   @param nics: list of nics to convert to hooks tuples
1420
1421   """
1422   hooks_nics = []
1423   cluster = lu.cfg.GetClusterInfo()
1424   for nic in nics:
1425     ip = nic.ip
1426     mac = nic.mac
1427     filled_params = cluster.SimpleFillNIC(nic.nicparams)
1428     mode = filled_params[constants.NIC_MODE]
1429     link = filled_params[constants.NIC_LINK]
1430     hooks_nics.append((ip, mac, mode, link))
1431   return hooks_nics
1432
1433
1434 def _BuildInstanceHookEnvByObject(lu, instance, override=None):
1435   """Builds instance related env variables for hooks from an object.
1436
1437   @type lu: L{LogicalUnit}
1438   @param lu: the logical unit on whose behalf we execute
1439   @type instance: L{objects.Instance}
1440   @param instance: the instance for which we should build the
1441       environment
1442   @type override: dict
1443   @param override: dictionary with key/values that will override
1444       our values
1445   @rtype: dict
1446   @return: the hook environment dictionary
1447
1448   """
1449   cluster = lu.cfg.GetClusterInfo()
1450   bep = cluster.FillBE(instance)
1451   hvp = cluster.FillHV(instance)
1452   args = {
1453     "name": instance.name,
1454     "primary_node": instance.primary_node,
1455     "secondary_nodes": instance.secondary_nodes,
1456     "os_type": instance.os,
1457     "status": instance.admin_state,
1458     "maxmem": bep[constants.BE_MAXMEM],
1459     "minmem": bep[constants.BE_MINMEM],
1460     "vcpus": bep[constants.BE_VCPUS],
1461     "nics": _NICListToTuple(lu, instance.nics),
1462     "disk_template": instance.disk_template,
1463     "disks": [(disk.size, disk.mode) for disk in instance.disks],
1464     "bep": bep,
1465     "hvp": hvp,
1466     "hypervisor_name": instance.hypervisor,
1467     "tags": instance.tags,
1468   }
1469   if override:
1470     args.update(override)
1471   return _BuildInstanceHookEnv(**args) # pylint: disable=W0142
1472
1473
1474 def _AdjustCandidatePool(lu, exceptions):
1475   """Adjust the candidate pool after node operations.
1476
1477   """
1478   mod_list = lu.cfg.MaintainCandidatePool(exceptions)
1479   if mod_list:
1480     lu.LogInfo("Promoted nodes to master candidate role: %s",
1481                utils.CommaJoin(node.name for node in mod_list))
1482     for name in mod_list:
1483       lu.context.ReaddNode(name)
1484   mc_now, mc_max, _ = lu.cfg.GetMasterCandidateStats(exceptions)
1485   if mc_now > mc_max:
1486     lu.LogInfo("Note: more nodes are candidates (%d) than desired (%d)" %
1487                (mc_now, mc_max))
1488
1489
1490 def _DecideSelfPromotion(lu, exceptions=None):
1491   """Decide whether I should promote myself as a master candidate.
1492
1493   """
1494   cp_size = lu.cfg.GetClusterInfo().candidate_pool_size
1495   mc_now, mc_should, _ = lu.cfg.GetMasterCandidateStats(exceptions)
1496   # the new node will increase mc_max with one, so:
1497   mc_should = min(mc_should + 1, cp_size)
1498   return mc_now < mc_should
1499
1500
1501 def _CalculateGroupIPolicy(cluster, group):
1502   """Calculate instance policy for group.
1503
1504   """
1505   return cluster.SimpleFillIPolicy(group.ipolicy)
1506
1507
1508 def _ComputeViolatingInstances(ipolicy, instances):
1509   """Computes a set of instances who violates given ipolicy.
1510
1511   @param ipolicy: The ipolicy to verify
1512   @type instances: object.Instance
1513   @param instances: List of instances to verify
1514   @return: A frozenset of instance names violating the ipolicy
1515
1516   """
1517   return frozenset([inst.name for inst in instances
1518                     if _ComputeIPolicyInstanceViolation(ipolicy, inst)])
1519
1520
1521 def _CheckNicsBridgesExist(lu, target_nics, target_node):
1522   """Check that the brigdes needed by a list of nics exist.
1523
1524   """
1525   cluster = lu.cfg.GetClusterInfo()
1526   paramslist = [cluster.SimpleFillNIC(nic.nicparams) for nic in target_nics]
1527   brlist = [params[constants.NIC_LINK] for params in paramslist
1528             if params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED]
1529   if brlist:
1530     result = lu.rpc.call_bridges_exist(target_node, brlist)
1531     result.Raise("Error checking bridges on destination node '%s'" %
1532                  target_node, prereq=True, ecode=errors.ECODE_ENVIRON)
1533
1534
1535 def _CheckInstanceBridgesExist(lu, instance, node=None):
1536   """Check that the brigdes needed by an instance exist.
1537
1538   """
1539   if node is None:
1540     node = instance.primary_node
1541   _CheckNicsBridgesExist(lu, instance.nics, node)
1542
1543
1544 def _CheckOSVariant(os_obj, name):
1545   """Check whether an OS name conforms to the os variants specification.
1546
1547   @type os_obj: L{objects.OS}
1548   @param os_obj: OS object to check
1549   @type name: string
1550   @param name: OS name passed by the user, to check for validity
1551
1552   """
1553   variant = objects.OS.GetVariant(name)
1554   if not os_obj.supported_variants:
1555     if variant:
1556       raise errors.OpPrereqError("OS '%s' doesn't support variants ('%s'"
1557                                  " passed)" % (os_obj.name, variant),
1558                                  errors.ECODE_INVAL)
1559     return
1560   if not variant:
1561     raise errors.OpPrereqError("OS name must include a variant",
1562                                errors.ECODE_INVAL)
1563
1564   if variant not in os_obj.supported_variants:
1565     raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
1566
1567
1568 def _GetNodeInstancesInner(cfg, fn):
1569   return [i for i in cfg.GetAllInstancesInfo().values() if fn(i)]
1570
1571
1572 def _GetNodeInstances(cfg, node_name):
1573   """Returns a list of all primary and secondary instances on a node.
1574
1575   """
1576
1577   return _GetNodeInstancesInner(cfg, lambda inst: node_name in inst.all_nodes)
1578
1579
1580 def _GetNodePrimaryInstances(cfg, node_name):
1581   """Returns primary instances on a node.
1582
1583   """
1584   return _GetNodeInstancesInner(cfg,
1585                                 lambda inst: node_name == inst.primary_node)
1586
1587
1588 def _GetNodeSecondaryInstances(cfg, node_name):
1589   """Returns secondary instances on a node.
1590
1591   """
1592   return _GetNodeInstancesInner(cfg,
1593                                 lambda inst: node_name in inst.secondary_nodes)
1594
1595
1596 def _GetStorageTypeArgs(cfg, storage_type):
1597   """Returns the arguments for a storage type.
1598
1599   """
1600   # Special case for file storage
1601   if storage_type == constants.ST_FILE:
1602     # storage.FileStorage wants a list of storage directories
1603     return [[cfg.GetFileStorageDir(), cfg.GetSharedFileStorageDir()]]
1604
1605   return []
1606
1607
1608 def _FindFaultyInstanceDisks(cfg, rpc_runner, instance, node_name, prereq):
1609   faulty = []
1610
1611   for dev in instance.disks:
1612     cfg.SetDiskID(dev, node_name)
1613
1614   result = rpc_runner.call_blockdev_getmirrorstatus(node_name, (instance.disks,
1615                                                                 instance))
1616   result.Raise("Failed to get disk status from node %s" % node_name,
1617                prereq=prereq, ecode=errors.ECODE_ENVIRON)
1618
1619   for idx, bdev_status in enumerate(result.payload):
1620     if bdev_status and bdev_status.ldisk_status == constants.LDS_FAULTY:
1621       faulty.append(idx)
1622
1623   return faulty
1624
1625
1626 def _CheckIAllocatorOrNode(lu, iallocator_slot, node_slot):
1627   """Check the sanity of iallocator and node arguments and use the
1628   cluster-wide iallocator if appropriate.
1629
1630   Check that at most one of (iallocator, node) is specified. If none is
1631   specified, then the LU's opcode's iallocator slot is filled with the
1632   cluster-wide default iallocator.
1633
1634   @type iallocator_slot: string
1635   @param iallocator_slot: the name of the opcode iallocator slot
1636   @type node_slot: string
1637   @param node_slot: the name of the opcode target node slot
1638
1639   """
1640   node = getattr(lu.op, node_slot, None)
1641   iallocator = getattr(lu.op, iallocator_slot, None)
1642
1643   if node is not None and iallocator is not None:
1644     raise errors.OpPrereqError("Do not specify both, iallocator and node",
1645                                errors.ECODE_INVAL)
1646   elif node is None and iallocator is None:
1647     default_iallocator = lu.cfg.GetDefaultIAllocator()
1648     if default_iallocator:
1649       setattr(lu.op, iallocator_slot, default_iallocator)
1650     else:
1651       raise errors.OpPrereqError("No iallocator or node given and no"
1652                                  " cluster-wide default iallocator found;"
1653                                  " please specify either an iallocator or a"
1654                                  " node, or set a cluster-wide default"
1655                                  " iallocator")
1656
1657
1658 def _GetDefaultIAllocator(cfg, iallocator):
1659   """Decides on which iallocator to use.
1660
1661   @type cfg: L{config.ConfigWriter}
1662   @param cfg: Cluster configuration object
1663   @type iallocator: string or None
1664   @param iallocator: Iallocator specified in opcode
1665   @rtype: string
1666   @return: Iallocator name
1667
1668   """
1669   if not iallocator:
1670     # Use default iallocator
1671     iallocator = cfg.GetDefaultIAllocator()
1672
1673   if not iallocator:
1674     raise errors.OpPrereqError("No iallocator was specified, neither in the"
1675                                " opcode nor as a cluster-wide default",
1676                                errors.ECODE_INVAL)
1677
1678   return iallocator
1679
1680
1681 class LUClusterPostInit(LogicalUnit):
1682   """Logical unit for running hooks after cluster initialization.
1683
1684   """
1685   HPATH = "cluster-init"
1686   HTYPE = constants.HTYPE_CLUSTER
1687
1688   def BuildHooksEnv(self):
1689     """Build hooks env.
1690
1691     """
1692     return {
1693       "OP_TARGET": self.cfg.GetClusterName(),
1694       }
1695
1696   def BuildHooksNodes(self):
1697     """Build hooks nodes.
1698
1699     """
1700     return ([], [self.cfg.GetMasterNode()])
1701
1702   def Exec(self, feedback_fn):
1703     """Nothing to do.
1704
1705     """
1706     return True
1707
1708
1709 class LUClusterDestroy(LogicalUnit):
1710   """Logical unit for destroying the cluster.
1711
1712   """
1713   HPATH = "cluster-destroy"
1714   HTYPE = constants.HTYPE_CLUSTER
1715
1716   def BuildHooksEnv(self):
1717     """Build hooks env.
1718
1719     """
1720     return {
1721       "OP_TARGET": self.cfg.GetClusterName(),
1722       }
1723
1724   def BuildHooksNodes(self):
1725     """Build hooks nodes.
1726
1727     """
1728     return ([], [])
1729
1730   def CheckPrereq(self):
1731     """Check prerequisites.
1732
1733     This checks whether the cluster is empty.
1734
1735     Any errors are signaled by raising errors.OpPrereqError.
1736
1737     """
1738     master = self.cfg.GetMasterNode()
1739
1740     nodelist = self.cfg.GetNodeList()
1741     if len(nodelist) != 1 or nodelist[0] != master:
1742       raise errors.OpPrereqError("There are still %d node(s) in"
1743                                  " this cluster." % (len(nodelist) - 1),
1744                                  errors.ECODE_INVAL)
1745     instancelist = self.cfg.GetInstanceList()
1746     if instancelist:
1747       raise errors.OpPrereqError("There are still %d instance(s) in"
1748                                  " this cluster." % len(instancelist),
1749                                  errors.ECODE_INVAL)
1750
1751   def Exec(self, feedback_fn):
1752     """Destroys the cluster.
1753
1754     """
1755     master_params = self.cfg.GetMasterNetworkParameters()
1756
1757     # Run post hooks on master node before it's removed
1758     _RunPostHook(self, master_params.name)
1759
1760     ems = self.cfg.GetUseExternalMipScript()
1761     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
1762                                                      master_params, ems)
1763     if result.fail_msg:
1764       self.LogWarning("Error disabling the master IP address: %s",
1765                       result.fail_msg)
1766
1767     return master_params.name
1768
1769
1770 def _VerifyCertificate(filename):
1771   """Verifies a certificate for L{LUClusterVerifyConfig}.
1772
1773   @type filename: string
1774   @param filename: Path to PEM file
1775
1776   """
1777   try:
1778     cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
1779                                            utils.ReadFile(filename))
1780   except Exception, err: # pylint: disable=W0703
1781     return (LUClusterVerifyConfig.ETYPE_ERROR,
1782             "Failed to load X509 certificate %s: %s" % (filename, err))
1783
1784   (errcode, msg) = \
1785     utils.VerifyX509Certificate(cert, constants.SSL_CERT_EXPIRATION_WARN,
1786                                 constants.SSL_CERT_EXPIRATION_ERROR)
1787
1788   if msg:
1789     fnamemsg = "While verifying %s: %s" % (filename, msg)
1790   else:
1791     fnamemsg = None
1792
1793   if errcode is None:
1794     return (None, fnamemsg)
1795   elif errcode == utils.CERT_WARNING:
1796     return (LUClusterVerifyConfig.ETYPE_WARNING, fnamemsg)
1797   elif errcode == utils.CERT_ERROR:
1798     return (LUClusterVerifyConfig.ETYPE_ERROR, fnamemsg)
1799
1800   raise errors.ProgrammerError("Unhandled certificate error code %r" % errcode)
1801
1802
1803 def _GetAllHypervisorParameters(cluster, instances):
1804   """Compute the set of all hypervisor parameters.
1805
1806   @type cluster: L{objects.Cluster}
1807   @param cluster: the cluster object
1808   @param instances: list of L{objects.Instance}
1809   @param instances: additional instances from which to obtain parameters
1810   @rtype: list of (origin, hypervisor, parameters)
1811   @return: a list with all parameters found, indicating the hypervisor they
1812        apply to, and the origin (can be "cluster", "os X", or "instance Y")
1813
1814   """
1815   hvp_data = []
1816
1817   for hv_name in cluster.enabled_hypervisors:
1818     hvp_data.append(("cluster", hv_name, cluster.GetHVDefaults(hv_name)))
1819
1820   for os_name, os_hvp in cluster.os_hvp.items():
1821     for hv_name, hv_params in os_hvp.items():
1822       if hv_params:
1823         full_params = cluster.GetHVDefaults(hv_name, os_name=os_name)
1824         hvp_data.append(("os %s" % os_name, hv_name, full_params))
1825
1826   # TODO: collapse identical parameter values in a single one
1827   for instance in instances:
1828     if instance.hvparams:
1829       hvp_data.append(("instance %s" % instance.name, instance.hypervisor,
1830                        cluster.FillHV(instance)))
1831
1832   return hvp_data
1833
1834
1835 class _VerifyErrors(object):
1836   """Mix-in for cluster/group verify LUs.
1837
1838   It provides _Error and _ErrorIf, and updates the self.bad boolean. (Expects
1839   self.op and self._feedback_fn to be available.)
1840
1841   """
1842
1843   ETYPE_FIELD = "code"
1844   ETYPE_ERROR = "ERROR"
1845   ETYPE_WARNING = "WARNING"
1846
1847   def _Error(self, ecode, item, msg, *args, **kwargs):
1848     """Format an error message.
1849
1850     Based on the opcode's error_codes parameter, either format a
1851     parseable error code, or a simpler error string.
1852
1853     This must be called only from Exec and functions called from Exec.
1854
1855     """
1856     ltype = kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR)
1857     itype, etxt, _ = ecode
1858     # first complete the msg
1859     if args:
1860       msg = msg % args
1861     # then format the whole message
1862     if self.op.error_codes: # This is a mix-in. pylint: disable=E1101
1863       msg = "%s:%s:%s:%s:%s" % (ltype, etxt, itype, item, msg)
1864     else:
1865       if item:
1866         item = " " + item
1867       else:
1868         item = ""
1869       msg = "%s: %s%s: %s" % (ltype, itype, item, msg)
1870     # and finally report it via the feedback_fn
1871     self._feedback_fn("  - %s" % msg) # Mix-in. pylint: disable=E1101
1872
1873   def _ErrorIf(self, cond, ecode, *args, **kwargs):
1874     """Log an error message if the passed condition is True.
1875
1876     """
1877     cond = (bool(cond)
1878             or self.op.debug_simulate_errors) # pylint: disable=E1101
1879
1880     # If the error code is in the list of ignored errors, demote the error to a
1881     # warning
1882     (_, etxt, _) = ecode
1883     if etxt in self.op.ignore_errors:     # pylint: disable=E1101
1884       kwargs[self.ETYPE_FIELD] = self.ETYPE_WARNING
1885
1886     if cond:
1887       self._Error(ecode, *args, **kwargs)
1888
1889     # do not mark the operation as failed for WARN cases only
1890     if kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR) == self.ETYPE_ERROR:
1891       self.bad = self.bad or cond
1892
1893
1894 class LUClusterVerify(NoHooksLU):
1895   """Submits all jobs necessary to verify the cluster.
1896
1897   """
1898   REQ_BGL = False
1899
1900   def ExpandNames(self):
1901     self.needed_locks = {}
1902
1903   def Exec(self, feedback_fn):
1904     jobs = []
1905
1906     if self.op.group_name:
1907       groups = [self.op.group_name]
1908       depends_fn = lambda: None
1909     else:
1910       groups = self.cfg.GetNodeGroupList()
1911
1912       # Verify global configuration
1913       jobs.append([
1914         opcodes.OpClusterVerifyConfig(ignore_errors=self.op.ignore_errors)
1915         ])
1916
1917       # Always depend on global verification
1918       depends_fn = lambda: [(-len(jobs), [])]
1919
1920     jobs.extend([opcodes.OpClusterVerifyGroup(group_name=group,
1921                                             ignore_errors=self.op.ignore_errors,
1922                                             depends=depends_fn())]
1923                 for group in groups)
1924
1925     # Fix up all parameters
1926     for op in itertools.chain(*jobs): # pylint: disable=W0142
1927       op.debug_simulate_errors = self.op.debug_simulate_errors
1928       op.verbose = self.op.verbose
1929       op.error_codes = self.op.error_codes
1930       try:
1931         op.skip_checks = self.op.skip_checks
1932       except AttributeError:
1933         assert not isinstance(op, opcodes.OpClusterVerifyGroup)
1934
1935     return ResultWithJobs(jobs)
1936
1937
1938 class LUClusterVerifyConfig(NoHooksLU, _VerifyErrors):
1939   """Verifies the cluster config.
1940
1941   """
1942   REQ_BGL = False
1943
1944   def _VerifyHVP(self, hvp_data):
1945     """Verifies locally the syntax of the hypervisor parameters.
1946
1947     """
1948     for item, hv_name, hv_params in hvp_data:
1949       msg = ("hypervisor %s parameters syntax check (source %s): %%s" %
1950              (item, hv_name))
1951       try:
1952         hv_class = hypervisor.GetHypervisor(hv_name)
1953         utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
1954         hv_class.CheckParameterSyntax(hv_params)
1955       except errors.GenericError, err:
1956         self._ErrorIf(True, constants.CV_ECLUSTERCFG, None, msg % str(err))
1957
1958   def ExpandNames(self):
1959     self.needed_locks = dict.fromkeys(locking.LEVELS, locking.ALL_SET)
1960     self.share_locks = _ShareAll()
1961
1962   def CheckPrereq(self):
1963     """Check prerequisites.
1964
1965     """
1966     # Retrieve all information
1967     self.all_group_info = self.cfg.GetAllNodeGroupsInfo()
1968     self.all_node_info = self.cfg.GetAllNodesInfo()
1969     self.all_inst_info = self.cfg.GetAllInstancesInfo()
1970
1971   def Exec(self, feedback_fn):
1972     """Verify integrity of cluster, performing various test on nodes.
1973
1974     """
1975     self.bad = False
1976     self._feedback_fn = feedback_fn
1977
1978     feedback_fn("* Verifying cluster config")
1979
1980     for msg in self.cfg.VerifyConfig():
1981       self._ErrorIf(True, constants.CV_ECLUSTERCFG, None, msg)
1982
1983     feedback_fn("* Verifying cluster certificate files")
1984
1985     for cert_filename in constants.ALL_CERT_FILES:
1986       (errcode, msg) = _VerifyCertificate(cert_filename)
1987       self._ErrorIf(errcode, constants.CV_ECLUSTERCERT, None, msg, code=errcode)
1988
1989     feedback_fn("* Verifying hypervisor parameters")
1990
1991     self._VerifyHVP(_GetAllHypervisorParameters(self.cfg.GetClusterInfo(),
1992                                                 self.all_inst_info.values()))
1993
1994     feedback_fn("* Verifying all nodes belong to an existing group")
1995
1996     # We do this verification here because, should this bogus circumstance
1997     # occur, it would never be caught by VerifyGroup, which only acts on
1998     # nodes/instances reachable from existing node groups.
1999
2000     dangling_nodes = set(node.name for node in self.all_node_info.values()
2001                          if node.group not in self.all_group_info)
2002
2003     dangling_instances = {}
2004     no_node_instances = []
2005
2006     for inst in self.all_inst_info.values():
2007       if inst.primary_node in dangling_nodes:
2008         dangling_instances.setdefault(inst.primary_node, []).append(inst.name)
2009       elif inst.primary_node not in self.all_node_info:
2010         no_node_instances.append(inst.name)
2011
2012     pretty_dangling = [
2013         "%s (%s)" %
2014         (node.name,
2015          utils.CommaJoin(dangling_instances.get(node.name,
2016                                                 ["no instances"])))
2017         for node in dangling_nodes]
2018
2019     self._ErrorIf(bool(dangling_nodes), constants.CV_ECLUSTERDANGLINGNODES,
2020                   None,
2021                   "the following nodes (and their instances) belong to a non"
2022                   " existing group: %s", utils.CommaJoin(pretty_dangling))
2023
2024     self._ErrorIf(bool(no_node_instances), constants.CV_ECLUSTERDANGLINGINST,
2025                   None,
2026                   "the following instances have a non-existing primary-node:"
2027                   " %s", utils.CommaJoin(no_node_instances))
2028
2029     return not self.bad
2030
2031
2032 class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
2033   """Verifies the status of a node group.
2034
2035   """
2036   HPATH = "cluster-verify"
2037   HTYPE = constants.HTYPE_CLUSTER
2038   REQ_BGL = False
2039
2040   _HOOKS_INDENT_RE = re.compile("^", re.M)
2041
2042   class NodeImage(object):
2043     """A class representing the logical and physical status of a node.
2044
2045     @type name: string
2046     @ivar name: the node name to which this object refers
2047     @ivar volumes: a structure as returned from
2048         L{ganeti.backend.GetVolumeList} (runtime)
2049     @ivar instances: a list of running instances (runtime)
2050     @ivar pinst: list of configured primary instances (config)
2051     @ivar sinst: list of configured secondary instances (config)
2052     @ivar sbp: dictionary of {primary-node: list of instances} for all
2053         instances for which this node is secondary (config)
2054     @ivar mfree: free memory, as reported by hypervisor (runtime)
2055     @ivar dfree: free disk, as reported by the node (runtime)
2056     @ivar offline: the offline status (config)
2057     @type rpc_fail: boolean
2058     @ivar rpc_fail: whether the RPC verify call was successfull (overall,
2059         not whether the individual keys were correct) (runtime)
2060     @type lvm_fail: boolean
2061     @ivar lvm_fail: whether the RPC call didn't return valid LVM data
2062     @type hyp_fail: boolean
2063     @ivar hyp_fail: whether the RPC call didn't return the instance list
2064     @type ghost: boolean
2065     @ivar ghost: whether this is a known node or not (config)
2066     @type os_fail: boolean
2067     @ivar os_fail: whether the RPC call didn't return valid OS data
2068     @type oslist: list
2069     @ivar oslist: list of OSes as diagnosed by DiagnoseOS
2070     @type vm_capable: boolean
2071     @ivar vm_capable: whether the node can host instances
2072
2073     """
2074     def __init__(self, offline=False, name=None, vm_capable=True):
2075       self.name = name
2076       self.volumes = {}
2077       self.instances = []
2078       self.pinst = []
2079       self.sinst = []
2080       self.sbp = {}
2081       self.mfree = 0
2082       self.dfree = 0
2083       self.offline = offline
2084       self.vm_capable = vm_capable
2085       self.rpc_fail = False
2086       self.lvm_fail = False
2087       self.hyp_fail = False
2088       self.ghost = False
2089       self.os_fail = False
2090       self.oslist = {}
2091
2092   def ExpandNames(self):
2093     # This raises errors.OpPrereqError on its own:
2094     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
2095
2096     # Get instances in node group; this is unsafe and needs verification later
2097     inst_names = \
2098       self.cfg.GetNodeGroupInstances(self.group_uuid, primary_only=True)
2099
2100     self.needed_locks = {
2101       locking.LEVEL_INSTANCE: inst_names,
2102       locking.LEVEL_NODEGROUP: [self.group_uuid],
2103       locking.LEVEL_NODE: [],
2104       }
2105
2106     self.share_locks = _ShareAll()
2107
2108   def DeclareLocks(self, level):
2109     if level == locking.LEVEL_NODE:
2110       # Get members of node group; this is unsafe and needs verification later
2111       nodes = set(self.cfg.GetNodeGroup(self.group_uuid).members)
2112
2113       all_inst_info = self.cfg.GetAllInstancesInfo()
2114
2115       # In Exec(), we warn about mirrored instances that have primary and
2116       # secondary living in separate node groups. To fully verify that
2117       # volumes for these instances are healthy, we will need to do an
2118       # extra call to their secondaries. We ensure here those nodes will
2119       # be locked.
2120       for inst in self.owned_locks(locking.LEVEL_INSTANCE):
2121         # Important: access only the instances whose lock is owned
2122         if all_inst_info[inst].disk_template in constants.DTS_INT_MIRROR:
2123           nodes.update(all_inst_info[inst].secondary_nodes)
2124
2125       self.needed_locks[locking.LEVEL_NODE] = nodes
2126
2127   def CheckPrereq(self):
2128     assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
2129     self.group_info = self.cfg.GetNodeGroup(self.group_uuid)
2130
2131     group_nodes = set(self.group_info.members)
2132     group_instances = \
2133       self.cfg.GetNodeGroupInstances(self.group_uuid, primary_only=True)
2134
2135     unlocked_nodes = \
2136         group_nodes.difference(self.owned_locks(locking.LEVEL_NODE))
2137
2138     unlocked_instances = \
2139         group_instances.difference(self.owned_locks(locking.LEVEL_INSTANCE))
2140
2141     if unlocked_nodes:
2142       raise errors.OpPrereqError("Missing lock for nodes: %s" %
2143                                  utils.CommaJoin(unlocked_nodes),
2144                                  errors.ECODE_STATE)
2145
2146     if unlocked_instances:
2147       raise errors.OpPrereqError("Missing lock for instances: %s" %
2148                                  utils.CommaJoin(unlocked_instances),
2149                                  errors.ECODE_STATE)
2150
2151     self.all_node_info = self.cfg.GetAllNodesInfo()
2152     self.all_inst_info = self.cfg.GetAllInstancesInfo()
2153
2154     self.my_node_names = utils.NiceSort(group_nodes)
2155     self.my_inst_names = utils.NiceSort(group_instances)
2156
2157     self.my_node_info = dict((name, self.all_node_info[name])
2158                              for name in self.my_node_names)
2159
2160     self.my_inst_info = dict((name, self.all_inst_info[name])
2161                              for name in self.my_inst_names)
2162
2163     # We detect here the nodes that will need the extra RPC calls for verifying
2164     # split LV volumes; they should be locked.
2165     extra_lv_nodes = set()
2166
2167     for inst in self.my_inst_info.values():
2168       if inst.disk_template in constants.DTS_INT_MIRROR:
2169         for nname in inst.all_nodes:
2170           if self.all_node_info[nname].group != self.group_uuid:
2171             extra_lv_nodes.add(nname)
2172
2173     unlocked_lv_nodes = \
2174         extra_lv_nodes.difference(self.owned_locks(locking.LEVEL_NODE))
2175
2176     if unlocked_lv_nodes:
2177       raise errors.OpPrereqError("Missing node locks for LV check: %s" %
2178                                  utils.CommaJoin(unlocked_lv_nodes),
2179                                  errors.ECODE_STATE)
2180     self.extra_lv_nodes = list(extra_lv_nodes)
2181
2182   def _VerifyNode(self, ninfo, nresult):
2183     """Perform some basic validation on data returned from a node.
2184
2185       - check the result data structure is well formed and has all the
2186         mandatory fields
2187       - check ganeti version
2188
2189     @type ninfo: L{objects.Node}
2190     @param ninfo: the node to check
2191     @param nresult: the results from the node
2192     @rtype: boolean
2193     @return: whether overall this call was successful (and we can expect
2194          reasonable values in the respose)
2195
2196     """
2197     node = ninfo.name
2198     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2199
2200     # main result, nresult should be a non-empty dict
2201     test = not nresult or not isinstance(nresult, dict)
2202     _ErrorIf(test, constants.CV_ENODERPC, node,
2203                   "unable to verify node: no data returned")
2204     if test:
2205       return False
2206
2207     # compares ganeti version
2208     local_version = constants.PROTOCOL_VERSION
2209     remote_version = nresult.get("version", None)
2210     test = not (remote_version and
2211                 isinstance(remote_version, (list, tuple)) and
2212                 len(remote_version) == 2)
2213     _ErrorIf(test, constants.CV_ENODERPC, node,
2214              "connection to node returned invalid data")
2215     if test:
2216       return False
2217
2218     test = local_version != remote_version[0]
2219     _ErrorIf(test, constants.CV_ENODEVERSION, node,
2220              "incompatible protocol versions: master %s,"
2221              " node %s", local_version, remote_version[0])
2222     if test:
2223       return False
2224
2225     # node seems compatible, we can actually try to look into its results
2226
2227     # full package version
2228     self._ErrorIf(constants.RELEASE_VERSION != remote_version[1],
2229                   constants.CV_ENODEVERSION, node,
2230                   "software version mismatch: master %s, node %s",
2231                   constants.RELEASE_VERSION, remote_version[1],
2232                   code=self.ETYPE_WARNING)
2233
2234     hyp_result = nresult.get(constants.NV_HYPERVISOR, None)
2235     if ninfo.vm_capable and isinstance(hyp_result, dict):
2236       for hv_name, hv_result in hyp_result.iteritems():
2237         test = hv_result is not None
2238         _ErrorIf(test, constants.CV_ENODEHV, node,
2239                  "hypervisor %s verify failure: '%s'", hv_name, hv_result)
2240
2241     hvp_result = nresult.get(constants.NV_HVPARAMS, None)
2242     if ninfo.vm_capable and isinstance(hvp_result, list):
2243       for item, hv_name, hv_result in hvp_result:
2244         _ErrorIf(True, constants.CV_ENODEHV, node,
2245                  "hypervisor %s parameter verify failure (source %s): %s",
2246                  hv_name, item, hv_result)
2247
2248     test = nresult.get(constants.NV_NODESETUP,
2249                        ["Missing NODESETUP results"])
2250     _ErrorIf(test, constants.CV_ENODESETUP, node, "node setup error: %s",
2251              "; ".join(test))
2252
2253     return True
2254
2255   def _VerifyNodeTime(self, ninfo, nresult,
2256                       nvinfo_starttime, nvinfo_endtime):
2257     """Check the node time.
2258
2259     @type ninfo: L{objects.Node}
2260     @param ninfo: the node to check
2261     @param nresult: the remote results for the node
2262     @param nvinfo_starttime: the start time of the RPC call
2263     @param nvinfo_endtime: the end time of the RPC call
2264
2265     """
2266     node = ninfo.name
2267     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2268
2269     ntime = nresult.get(constants.NV_TIME, None)
2270     try:
2271       ntime_merged = utils.MergeTime(ntime)
2272     except (ValueError, TypeError):
2273       _ErrorIf(True, constants.CV_ENODETIME, node, "Node returned invalid time")
2274       return
2275
2276     if ntime_merged < (nvinfo_starttime - constants.NODE_MAX_CLOCK_SKEW):
2277       ntime_diff = "%.01fs" % abs(nvinfo_starttime - ntime_merged)
2278     elif ntime_merged > (nvinfo_endtime + constants.NODE_MAX_CLOCK_SKEW):
2279       ntime_diff = "%.01fs" % abs(ntime_merged - nvinfo_endtime)
2280     else:
2281       ntime_diff = None
2282
2283     _ErrorIf(ntime_diff is not None, constants.CV_ENODETIME, node,
2284              "Node time diverges by at least %s from master node time",
2285              ntime_diff)
2286
2287   def _VerifyNodeLVM(self, ninfo, nresult, vg_name):
2288     """Check the node LVM results.
2289
2290     @type ninfo: L{objects.Node}
2291     @param ninfo: the node to check
2292     @param nresult: the remote results for the node
2293     @param vg_name: the configured VG name
2294
2295     """
2296     if vg_name is None:
2297       return
2298
2299     node = ninfo.name
2300     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2301
2302     # checks vg existence and size > 20G
2303     vglist = nresult.get(constants.NV_VGLIST, None)
2304     test = not vglist
2305     _ErrorIf(test, constants.CV_ENODELVM, node, "unable to check volume groups")
2306     if not test:
2307       vgstatus = utils.CheckVolumeGroupSize(vglist, vg_name,
2308                                             constants.MIN_VG_SIZE)
2309       _ErrorIf(vgstatus, constants.CV_ENODELVM, node, vgstatus)
2310
2311     # check pv names
2312     pvlist = nresult.get(constants.NV_PVLIST, None)
2313     test = pvlist is None
2314     _ErrorIf(test, constants.CV_ENODELVM, node, "Can't get PV list from node")
2315     if not test:
2316       # check that ':' is not present in PV names, since it's a
2317       # special character for lvcreate (denotes the range of PEs to
2318       # use on the PV)
2319       for _, pvname, owner_vg in pvlist:
2320         test = ":" in pvname
2321         _ErrorIf(test, constants.CV_ENODELVM, node,
2322                  "Invalid character ':' in PV '%s' of VG '%s'",
2323                  pvname, owner_vg)
2324
2325   def _VerifyNodeBridges(self, ninfo, nresult, bridges):
2326     """Check the node bridges.
2327
2328     @type ninfo: L{objects.Node}
2329     @param ninfo: the node to check
2330     @param nresult: the remote results for the node
2331     @param bridges: the expected list of bridges
2332
2333     """
2334     if not bridges:
2335       return
2336
2337     node = ninfo.name
2338     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2339
2340     missing = nresult.get(constants.NV_BRIDGES, None)
2341     test = not isinstance(missing, list)
2342     _ErrorIf(test, constants.CV_ENODENET, node,
2343              "did not return valid bridge information")
2344     if not test:
2345       _ErrorIf(bool(missing), constants.CV_ENODENET, node,
2346                "missing bridges: %s" % utils.CommaJoin(sorted(missing)))
2347
2348   def _VerifyNodeUserScripts(self, ninfo, nresult):
2349     """Check the results of user scripts presence and executability on the node
2350
2351     @type ninfo: L{objects.Node}
2352     @param ninfo: the node to check
2353     @param nresult: the remote results for the node
2354
2355     """
2356     node = ninfo.name
2357
2358     test = not constants.NV_USERSCRIPTS in nresult
2359     self._ErrorIf(test, constants.CV_ENODEUSERSCRIPTS, node,
2360                   "did not return user scripts information")
2361
2362     broken_scripts = nresult.get(constants.NV_USERSCRIPTS, None)
2363     if not test:
2364       self._ErrorIf(broken_scripts, constants.CV_ENODEUSERSCRIPTS, node,
2365                     "user scripts not present or not executable: %s" %
2366                     utils.CommaJoin(sorted(broken_scripts)))
2367
2368   def _VerifyNodeNetwork(self, ninfo, nresult):
2369     """Check the node network connectivity results.
2370
2371     @type ninfo: L{objects.Node}
2372     @param ninfo: the node to check
2373     @param nresult: the remote results for the node
2374
2375     """
2376     node = ninfo.name
2377     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2378
2379     test = constants.NV_NODELIST not in nresult
2380     _ErrorIf(test, constants.CV_ENODESSH, node,
2381              "node hasn't returned node ssh connectivity data")
2382     if not test:
2383       if nresult[constants.NV_NODELIST]:
2384         for a_node, a_msg in nresult[constants.NV_NODELIST].items():
2385           _ErrorIf(True, constants.CV_ENODESSH, node,
2386                    "ssh communication with node '%s': %s", a_node, a_msg)
2387
2388     test = constants.NV_NODENETTEST not in nresult
2389     _ErrorIf(test, constants.CV_ENODENET, node,
2390              "node hasn't returned node tcp connectivity data")
2391     if not test:
2392       if nresult[constants.NV_NODENETTEST]:
2393         nlist = utils.NiceSort(nresult[constants.NV_NODENETTEST].keys())
2394         for anode in nlist:
2395           _ErrorIf(True, constants.CV_ENODENET, node,
2396                    "tcp communication with node '%s': %s",
2397                    anode, nresult[constants.NV_NODENETTEST][anode])
2398
2399     test = constants.NV_MASTERIP not in nresult
2400     _ErrorIf(test, constants.CV_ENODENET, node,
2401              "node hasn't returned node master IP reachability data")
2402     if not test:
2403       if not nresult[constants.NV_MASTERIP]:
2404         if node == self.master_node:
2405           msg = "the master node cannot reach the master IP (not configured?)"
2406         else:
2407           msg = "cannot reach the master IP"
2408         _ErrorIf(True, constants.CV_ENODENET, node, msg)
2409
2410   def _VerifyInstance(self, instance, instanceconfig, node_image,
2411                       diskstatus):
2412     """Verify an instance.
2413
2414     This function checks to see if the required block devices are
2415     available on the instance's node.
2416
2417     """
2418     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2419     node_current = instanceconfig.primary_node
2420
2421     node_vol_should = {}
2422     instanceconfig.MapLVsByNode(node_vol_should)
2423
2424     ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(), self.group_info)
2425     err = _ComputeIPolicyInstanceViolation(ipolicy, instanceconfig)
2426     _ErrorIf(err, constants.CV_EINSTANCEPOLICY, instance, utils.CommaJoin(err))
2427
2428     for node in node_vol_should:
2429       n_img = node_image[node]
2430       if n_img.offline or n_img.rpc_fail or n_img.lvm_fail:
2431         # ignore missing volumes on offline or broken nodes
2432         continue
2433       for volume in node_vol_should[node]:
2434         test = volume not in n_img.volumes
2435         _ErrorIf(test, constants.CV_EINSTANCEMISSINGDISK, instance,
2436                  "volume %s missing on node %s", volume, node)
2437
2438     if instanceconfig.admin_state == constants.ADMINST_UP:
2439       pri_img = node_image[node_current]
2440       test = instance not in pri_img.instances and not pri_img.offline
2441       _ErrorIf(test, constants.CV_EINSTANCEDOWN, instance,
2442                "instance not running on its primary node %s",
2443                node_current)
2444
2445     diskdata = [(nname, success, status, idx)
2446                 for (nname, disks) in diskstatus.items()
2447                 for idx, (success, status) in enumerate(disks)]
2448
2449     for nname, success, bdev_status, idx in diskdata:
2450       # the 'ghost node' construction in Exec() ensures that we have a
2451       # node here
2452       snode = node_image[nname]
2453       bad_snode = snode.ghost or snode.offline
2454       _ErrorIf(instanceconfig.admin_state == constants.ADMINST_UP and
2455                not success and not bad_snode,
2456                constants.CV_EINSTANCEFAULTYDISK, instance,
2457                "couldn't retrieve status for disk/%s on %s: %s",
2458                idx, nname, bdev_status)
2459       _ErrorIf((instanceconfig.admin_state == constants.ADMINST_UP and
2460                 success and bdev_status.ldisk_status == constants.LDS_FAULTY),
2461                constants.CV_EINSTANCEFAULTYDISK, instance,
2462                "disk/%s on %s is faulty", idx, nname)
2463
2464   def _VerifyOrphanVolumes(self, node_vol_should, node_image, reserved):
2465     """Verify if there are any unknown volumes in the cluster.
2466
2467     The .os, .swap and backup volumes are ignored. All other volumes are
2468     reported as unknown.
2469
2470     @type reserved: L{ganeti.utils.FieldSet}
2471     @param reserved: a FieldSet of reserved volume names
2472
2473     """
2474     for node, n_img in node_image.items():
2475       if (n_img.offline or n_img.rpc_fail or n_img.lvm_fail or
2476           self.all_node_info[node].group != self.group_uuid):
2477         # skip non-healthy nodes
2478         continue
2479       for volume in n_img.volumes:
2480         test = ((node not in node_vol_should or
2481                 volume not in node_vol_should[node]) and
2482                 not reserved.Matches(volume))
2483         self._ErrorIf(test, constants.CV_ENODEORPHANLV, node,
2484                       "volume %s is unknown", volume)
2485
2486   def _VerifyNPlusOneMemory(self, node_image, instance_cfg):
2487     """Verify N+1 Memory Resilience.
2488
2489     Check that if one single node dies we can still start all the
2490     instances it was primary for.
2491
2492     """
2493     cluster_info = self.cfg.GetClusterInfo()
2494     for node, n_img in node_image.items():
2495       # This code checks that every node which is now listed as
2496       # secondary has enough memory to host all instances it is
2497       # supposed to should a single other node in the cluster fail.
2498       # FIXME: not ready for failover to an arbitrary node
2499       # FIXME: does not support file-backed instances
2500       # WARNING: we currently take into account down instances as well
2501       # as up ones, considering that even if they're down someone
2502       # might want to start them even in the event of a node failure.
2503       if n_img.offline or self.all_node_info[node].group != self.group_uuid:
2504         # we're skipping nodes marked offline and nodes in other groups from
2505         # the N+1 warning, since most likely we don't have good memory
2506         # infromation from them; we already list instances living on such
2507         # nodes, and that's enough warning
2508         continue
2509       #TODO(dynmem): also consider ballooning out other instances
2510       for prinode, instances in n_img.sbp.items():
2511         needed_mem = 0
2512         for instance in instances:
2513           bep = cluster_info.FillBE(instance_cfg[instance])
2514           if bep[constants.BE_AUTO_BALANCE]:
2515             needed_mem += bep[constants.BE_MINMEM]
2516         test = n_img.mfree < needed_mem
2517         self._ErrorIf(test, constants.CV_ENODEN1, node,
2518                       "not enough memory to accomodate instance failovers"
2519                       " should node %s fail (%dMiB needed, %dMiB available)",
2520                       prinode, needed_mem, n_img.mfree)
2521
2522   @classmethod
2523   def _VerifyFiles(cls, errorif, nodeinfo, master_node, all_nvinfo,
2524                    (files_all, files_opt, files_mc, files_vm)):
2525     """Verifies file checksums collected from all nodes.
2526
2527     @param errorif: Callback for reporting errors
2528     @param nodeinfo: List of L{objects.Node} objects
2529     @param master_node: Name of master node
2530     @param all_nvinfo: RPC results
2531
2532     """
2533     # Define functions determining which nodes to consider for a file
2534     files2nodefn = [
2535       (files_all, None),
2536       (files_mc, lambda node: (node.master_candidate or
2537                                node.name == master_node)),
2538       (files_vm, lambda node: node.vm_capable),
2539       ]
2540
2541     # Build mapping from filename to list of nodes which should have the file
2542     nodefiles = {}
2543     for (files, fn) in files2nodefn:
2544       if fn is None:
2545         filenodes = nodeinfo
2546       else:
2547         filenodes = filter(fn, nodeinfo)
2548       nodefiles.update((filename,
2549                         frozenset(map(operator.attrgetter("name"), filenodes)))
2550                        for filename in files)
2551
2552     assert set(nodefiles) == (files_all | files_mc | files_vm)
2553
2554     fileinfo = dict((filename, {}) for filename in nodefiles)
2555     ignore_nodes = set()
2556
2557     for node in nodeinfo:
2558       if node.offline:
2559         ignore_nodes.add(node.name)
2560         continue
2561
2562       nresult = all_nvinfo[node.name]
2563
2564       if nresult.fail_msg or not nresult.payload:
2565         node_files = None
2566       else:
2567         node_files = nresult.payload.get(constants.NV_FILELIST, None)
2568
2569       test = not (node_files and isinstance(node_files, dict))
2570       errorif(test, constants.CV_ENODEFILECHECK, node.name,
2571               "Node did not return file checksum data")
2572       if test:
2573         ignore_nodes.add(node.name)
2574         continue
2575
2576       # Build per-checksum mapping from filename to nodes having it
2577       for (filename, checksum) in node_files.items():
2578         assert filename in nodefiles
2579         fileinfo[filename].setdefault(checksum, set()).add(node.name)
2580
2581     for (filename, checksums) in fileinfo.items():
2582       assert compat.all(len(i) > 10 for i in checksums), "Invalid checksum"
2583
2584       # Nodes having the file
2585       with_file = frozenset(node_name
2586                             for nodes in fileinfo[filename].values()
2587                             for node_name in nodes) - ignore_nodes
2588
2589       expected_nodes = nodefiles[filename] - ignore_nodes
2590
2591       # Nodes missing file
2592       missing_file = expected_nodes - with_file
2593
2594       if filename in files_opt:
2595         # All or no nodes
2596         errorif(missing_file and missing_file != expected_nodes,
2597                 constants.CV_ECLUSTERFILECHECK, None,
2598                 "File %s is optional, but it must exist on all or no"
2599                 " nodes (not found on %s)",
2600                 filename, utils.CommaJoin(utils.NiceSort(missing_file)))
2601       else:
2602         errorif(missing_file, constants.CV_ECLUSTERFILECHECK, None,
2603                 "File %s is missing from node(s) %s", filename,
2604                 utils.CommaJoin(utils.NiceSort(missing_file)))
2605
2606         # Warn if a node has a file it shouldn't
2607         unexpected = with_file - expected_nodes
2608         errorif(unexpected,
2609                 constants.CV_ECLUSTERFILECHECK, None,
2610                 "File %s should not exist on node(s) %s",
2611                 filename, utils.CommaJoin(utils.NiceSort(unexpected)))
2612
2613       # See if there are multiple versions of the file
2614       test = len(checksums) > 1
2615       if test:
2616         variants = ["variant %s on %s" %
2617                     (idx + 1, utils.CommaJoin(utils.NiceSort(nodes)))
2618                     for (idx, (checksum, nodes)) in
2619                       enumerate(sorted(checksums.items()))]
2620       else:
2621         variants = []
2622
2623       errorif(test, constants.CV_ECLUSTERFILECHECK, None,
2624               "File %s found with %s different checksums (%s)",
2625               filename, len(checksums), "; ".join(variants))
2626
2627   def _VerifyNodeDrbd(self, ninfo, nresult, instanceinfo, drbd_helper,
2628                       drbd_map):
2629     """Verifies and the node DRBD status.
2630
2631     @type ninfo: L{objects.Node}
2632     @param ninfo: the node to check
2633     @param nresult: the remote results for the node
2634     @param instanceinfo: the dict of instances
2635     @param drbd_helper: the configured DRBD usermode helper
2636     @param drbd_map: the DRBD map as returned by
2637         L{ganeti.config.ConfigWriter.ComputeDRBDMap}
2638
2639     """
2640     node = ninfo.name
2641     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2642
2643     if drbd_helper:
2644       helper_result = nresult.get(constants.NV_DRBDHELPER, None)
2645       test = (helper_result == None)
2646       _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2647                "no drbd usermode helper returned")
2648       if helper_result:
2649         status, payload = helper_result
2650         test = not status
2651         _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2652                  "drbd usermode helper check unsuccessful: %s", payload)
2653         test = status and (payload != drbd_helper)
2654         _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2655                  "wrong drbd usermode helper: %s", payload)
2656
2657     # compute the DRBD minors
2658     node_drbd = {}
2659     for minor, instance in drbd_map[node].items():
2660       test = instance not in instanceinfo
2661       _ErrorIf(test, constants.CV_ECLUSTERCFG, None,
2662                "ghost instance '%s' in temporary DRBD map", instance)
2663         # ghost instance should not be running, but otherwise we
2664         # don't give double warnings (both ghost instance and
2665         # unallocated minor in use)
2666       if test:
2667         node_drbd[minor] = (instance, False)
2668       else:
2669         instance = instanceinfo[instance]
2670         node_drbd[minor] = (instance.name,
2671                             instance.admin_state == constants.ADMINST_UP)
2672
2673     # and now check them
2674     used_minors = nresult.get(constants.NV_DRBDLIST, [])
2675     test = not isinstance(used_minors, (tuple, list))
2676     _ErrorIf(test, constants.CV_ENODEDRBD, node,
2677              "cannot parse drbd status file: %s", str(used_minors))
2678     if test:
2679       # we cannot check drbd status
2680       return
2681
2682     for minor, (iname, must_exist) in node_drbd.items():
2683       test = minor not in used_minors and must_exist
2684       _ErrorIf(test, constants.CV_ENODEDRBD, node,
2685                "drbd minor %d of instance %s is not active", minor, iname)
2686     for minor in used_minors:
2687       test = minor not in node_drbd
2688       _ErrorIf(test, constants.CV_ENODEDRBD, node,
2689                "unallocated drbd minor %d is in use", minor)
2690
2691   def _UpdateNodeOS(self, ninfo, nresult, nimg):
2692     """Builds the node OS structures.
2693
2694     @type ninfo: L{objects.Node}
2695     @param ninfo: the node to check
2696     @param nresult: the remote results for the node
2697     @param nimg: the node image object
2698
2699     """
2700     node = ninfo.name
2701     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2702
2703     remote_os = nresult.get(constants.NV_OSLIST, None)
2704     test = (not isinstance(remote_os, list) or
2705             not compat.all(isinstance(v, list) and len(v) == 7
2706                            for v in remote_os))
2707
2708     _ErrorIf(test, constants.CV_ENODEOS, node,
2709              "node hasn't returned valid OS data")
2710
2711     nimg.os_fail = test
2712
2713     if test:
2714       return
2715
2716     os_dict = {}
2717
2718     for (name, os_path, status, diagnose,
2719          variants, parameters, api_ver) in nresult[constants.NV_OSLIST]:
2720
2721       if name not in os_dict:
2722         os_dict[name] = []
2723
2724       # parameters is a list of lists instead of list of tuples due to
2725       # JSON lacking a real tuple type, fix it:
2726       parameters = [tuple(v) for v in parameters]
2727       os_dict[name].append((os_path, status, diagnose,
2728                             set(variants), set(parameters), set(api_ver)))
2729
2730     nimg.oslist = os_dict
2731
2732   def _VerifyNodeOS(self, ninfo, nimg, base):
2733     """Verifies the node OS list.
2734
2735     @type ninfo: L{objects.Node}
2736     @param ninfo: the node to check
2737     @param nimg: the node image object
2738     @param base: the 'template' node we match against (e.g. from the master)
2739
2740     """
2741     node = ninfo.name
2742     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2743
2744     assert not nimg.os_fail, "Entered _VerifyNodeOS with failed OS rpc?"
2745
2746     beautify_params = lambda l: ["%s: %s" % (k, v) for (k, v) in l]
2747     for os_name, os_data in nimg.oslist.items():
2748       assert os_data, "Empty OS status for OS %s?!" % os_name
2749       f_path, f_status, f_diag, f_var, f_param, f_api = os_data[0]
2750       _ErrorIf(not f_status, constants.CV_ENODEOS, node,
2751                "Invalid OS %s (located at %s): %s", os_name, f_path, f_diag)
2752       _ErrorIf(len(os_data) > 1, constants.CV_ENODEOS, node,
2753                "OS '%s' has multiple entries (first one shadows the rest): %s",
2754                os_name, utils.CommaJoin([v[0] for v in os_data]))
2755       # comparisons with the 'base' image
2756       test = os_name not in base.oslist
2757       _ErrorIf(test, constants.CV_ENODEOS, node,
2758                "Extra OS %s not present on reference node (%s)",
2759                os_name, base.name)
2760       if test:
2761         continue
2762       assert base.oslist[os_name], "Base node has empty OS status?"
2763       _, b_status, _, b_var, b_param, b_api = base.oslist[os_name][0]
2764       if not b_status:
2765         # base OS is invalid, skipping
2766         continue
2767       for kind, a, b in [("API version", f_api, b_api),
2768                          ("variants list", f_var, b_var),
2769                          ("parameters", beautify_params(f_param),
2770                           beautify_params(b_param))]:
2771         _ErrorIf(a != b, constants.CV_ENODEOS, node,
2772                  "OS %s for %s differs from reference node %s: [%s] vs. [%s]",
2773                  kind, os_name, base.name,
2774                  utils.CommaJoin(sorted(a)), utils.CommaJoin(sorted(b)))
2775
2776     # check any missing OSes
2777     missing = set(base.oslist.keys()).difference(nimg.oslist.keys())
2778     _ErrorIf(missing, constants.CV_ENODEOS, node,
2779              "OSes present on reference node %s but missing on this node: %s",
2780              base.name, utils.CommaJoin(missing))
2781
2782   def _VerifyOob(self, ninfo, nresult):
2783     """Verifies out of band functionality of a node.
2784
2785     @type ninfo: L{objects.Node}
2786     @param ninfo: the node to check
2787     @param nresult: the remote results for the node
2788
2789     """
2790     node = ninfo.name
2791     # We just have to verify the paths on master and/or master candidates
2792     # as the oob helper is invoked on the master
2793     if ((ninfo.master_candidate or ninfo.master_capable) and
2794         constants.NV_OOB_PATHS in nresult):
2795       for path_result in nresult[constants.NV_OOB_PATHS]:
2796         self._ErrorIf(path_result, constants.CV_ENODEOOBPATH, node, path_result)
2797
2798   def _UpdateNodeVolumes(self, ninfo, nresult, nimg, vg_name):
2799     """Verifies and updates the node volume data.
2800
2801     This function will update a L{NodeImage}'s internal structures
2802     with data from the remote call.
2803
2804     @type ninfo: L{objects.Node}
2805     @param ninfo: the node to check
2806     @param nresult: the remote results for the node
2807     @param nimg: the node image object
2808     @param vg_name: the configured VG name
2809
2810     """
2811     node = ninfo.name
2812     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2813
2814     nimg.lvm_fail = True
2815     lvdata = nresult.get(constants.NV_LVLIST, "Missing LV data")
2816     if vg_name is None:
2817       pass
2818     elif isinstance(lvdata, basestring):
2819       _ErrorIf(True, constants.CV_ENODELVM, node, "LVM problem on node: %s",
2820                utils.SafeEncode(lvdata))
2821     elif not isinstance(lvdata, dict):
2822       _ErrorIf(True, constants.CV_ENODELVM, node,
2823                "rpc call to node failed (lvlist)")
2824     else:
2825       nimg.volumes = lvdata
2826       nimg.lvm_fail = False
2827
2828   def _UpdateNodeInstances(self, ninfo, nresult, nimg):
2829     """Verifies and updates the node instance list.
2830
2831     If the listing was successful, then updates this node's instance
2832     list. Otherwise, it marks the RPC call as failed for the instance
2833     list key.
2834
2835     @type ninfo: L{objects.Node}
2836     @param ninfo: the node to check
2837     @param nresult: the remote results for the node
2838     @param nimg: the node image object
2839
2840     """
2841     idata = nresult.get(constants.NV_INSTANCELIST, None)
2842     test = not isinstance(idata, list)
2843     self._ErrorIf(test, constants.CV_ENODEHV, ninfo.name,
2844                   "rpc call to node failed (instancelist): %s",
2845                   utils.SafeEncode(str(idata)))
2846     if test:
2847       nimg.hyp_fail = True
2848     else:
2849       nimg.instances = idata
2850
2851   def _UpdateNodeInfo(self, ninfo, nresult, nimg, vg_name):
2852     """Verifies and computes a node information map
2853
2854     @type ninfo: L{objects.Node}
2855     @param ninfo: the node to check
2856     @param nresult: the remote results for the node
2857     @param nimg: the node image object
2858     @param vg_name: the configured VG name
2859
2860     """
2861     node = ninfo.name
2862     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2863
2864     # try to read free memory (from the hypervisor)
2865     hv_info = nresult.get(constants.NV_HVINFO, None)
2866     test = not isinstance(hv_info, dict) or "memory_free" not in hv_info
2867     _ErrorIf(test, constants.CV_ENODEHV, node,
2868              "rpc call to node failed (hvinfo)")
2869     if not test:
2870       try:
2871         nimg.mfree = int(hv_info["memory_free"])
2872       except (ValueError, TypeError):
2873         _ErrorIf(True, constants.CV_ENODERPC, node,
2874                  "node returned invalid nodeinfo, check hypervisor")
2875
2876     # FIXME: devise a free space model for file based instances as well
2877     if vg_name is not None:
2878       test = (constants.NV_VGLIST not in nresult or
2879               vg_name not in nresult[constants.NV_VGLIST])
2880       _ErrorIf(test, constants.CV_ENODELVM, node,
2881                "node didn't return data for the volume group '%s'"
2882                " - it is either missing or broken", vg_name)
2883       if not test:
2884         try:
2885           nimg.dfree = int(nresult[constants.NV_VGLIST][vg_name])
2886         except (ValueError, TypeError):
2887           _ErrorIf(True, constants.CV_ENODERPC, node,
2888                    "node returned invalid LVM info, check LVM status")
2889
2890   def _CollectDiskInfo(self, nodelist, node_image, instanceinfo):
2891     """Gets per-disk status information for all instances.
2892
2893     @type nodelist: list of strings
2894     @param nodelist: Node names
2895     @type node_image: dict of (name, L{objects.Node})
2896     @param node_image: Node objects
2897     @type instanceinfo: dict of (name, L{objects.Instance})
2898     @param instanceinfo: Instance objects
2899     @rtype: {instance: {node: [(succes, payload)]}}
2900     @return: a dictionary of per-instance dictionaries with nodes as
2901         keys and disk information as values; the disk information is a
2902         list of tuples (success, payload)
2903
2904     """
2905     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2906
2907     node_disks = {}
2908     node_disks_devonly = {}
2909     diskless_instances = set()
2910     diskless = constants.DT_DISKLESS
2911
2912     for nname in nodelist:
2913       node_instances = list(itertools.chain(node_image[nname].pinst,
2914                                             node_image[nname].sinst))
2915       diskless_instances.update(inst for inst in node_instances
2916                                 if instanceinfo[inst].disk_template == diskless)
2917       disks = [(inst, disk)
2918                for inst in node_instances
2919                for disk in instanceinfo[inst].disks]
2920
2921       if not disks:
2922         # No need to collect data
2923         continue
2924
2925       node_disks[nname] = disks
2926
2927       # _AnnotateDiskParams makes already copies of the disks
2928       devonly = []
2929       for (inst, dev) in disks:
2930         (anno_disk,) = _AnnotateDiskParams(instanceinfo[inst], [dev], self.cfg)
2931         self.cfg.SetDiskID(anno_disk, nname)
2932         devonly.append(anno_disk)
2933
2934       node_disks_devonly[nname] = devonly
2935
2936     assert len(node_disks) == len(node_disks_devonly)
2937
2938     # Collect data from all nodes with disks
2939     result = self.rpc.call_blockdev_getmirrorstatus_multi(node_disks.keys(),
2940                                                           node_disks_devonly)
2941
2942     assert len(result) == len(node_disks)
2943
2944     instdisk = {}
2945
2946     for (nname, nres) in result.items():
2947       disks = node_disks[nname]
2948
2949       if nres.offline:
2950         # No data from this node
2951         data = len(disks) * [(False, "node offline")]
2952       else:
2953         msg = nres.fail_msg
2954         _ErrorIf(msg, constants.CV_ENODERPC, nname,
2955                  "while getting disk information: %s", msg)
2956         if msg:
2957           # No data from this node
2958           data = len(disks) * [(False, msg)]
2959         else:
2960           data = []
2961           for idx, i in enumerate(nres.payload):
2962             if isinstance(i, (tuple, list)) and len(i) == 2:
2963               data.append(i)
2964             else:
2965               logging.warning("Invalid result from node %s, entry %d: %s",
2966                               nname, idx, i)
2967               data.append((False, "Invalid result from the remote node"))
2968
2969       for ((inst, _), status) in zip(disks, data):
2970         instdisk.setdefault(inst, {}).setdefault(nname, []).append(status)
2971
2972     # Add empty entries for diskless instances.
2973     for inst in diskless_instances:
2974       assert inst not in instdisk
2975       instdisk[inst] = {}
2976
2977     assert compat.all(len(statuses) == len(instanceinfo[inst].disks) and
2978                       len(nnames) <= len(instanceinfo[inst].all_nodes) and
2979                       compat.all(isinstance(s, (tuple, list)) and
2980                                  len(s) == 2 for s in statuses)
2981                       for inst, nnames in instdisk.items()
2982                       for nname, statuses in nnames.items())
2983     assert set(instdisk) == set(instanceinfo), "instdisk consistency failure"
2984
2985     return instdisk
2986
2987   @staticmethod
2988   def _SshNodeSelector(group_uuid, all_nodes):
2989     """Create endless iterators for all potential SSH check hosts.
2990
2991     """
2992     nodes = [node for node in all_nodes
2993              if (node.group != group_uuid and
2994                  not node.offline)]
2995     keyfunc = operator.attrgetter("group")
2996
2997     return map(itertools.cycle,
2998                [sorted(map(operator.attrgetter("name"), names))
2999                 for _, names in itertools.groupby(sorted(nodes, key=keyfunc),
3000                                                   keyfunc)])
3001
3002   @classmethod
3003   def _SelectSshCheckNodes(cls, group_nodes, group_uuid, all_nodes):
3004     """Choose which nodes should talk to which other nodes.
3005
3006     We will make nodes contact all nodes in their group, and one node from
3007     every other group.
3008
3009     @warning: This algorithm has a known issue if one node group is much
3010       smaller than others (e.g. just one node). In such a case all other
3011       nodes will talk to the single node.
3012
3013     """
3014     online_nodes = sorted(node.name for node in group_nodes if not node.offline)
3015     sel = cls._SshNodeSelector(group_uuid, all_nodes)
3016
3017     return (online_nodes,
3018             dict((name, sorted([i.next() for i in sel]))
3019                  for name in online_nodes))
3020
3021   def BuildHooksEnv(self):
3022     """Build hooks env.
3023
3024     Cluster-Verify hooks just ran in the post phase and their failure makes
3025     the output be logged in the verify output and the verification to fail.
3026
3027     """
3028     env = {
3029       "CLUSTER_TAGS": " ".join(self.cfg.GetClusterInfo().GetTags())
3030       }
3031
3032     env.update(("NODE_TAGS_%s" % node.name, " ".join(node.GetTags()))
3033                for node in self.my_node_info.values())
3034
3035     return env
3036
3037   def BuildHooksNodes(self):
3038     """Build hooks nodes.
3039
3040     """
3041     return ([], self.my_node_names)
3042
3043   def Exec(self, feedback_fn):
3044     """Verify integrity of the node group, performing various test on nodes.
3045
3046     """
3047     # This method has too many local variables. pylint: disable=R0914
3048     feedback_fn("* Verifying group '%s'" % self.group_info.name)
3049
3050     if not self.my_node_names:
3051       # empty node group
3052       feedback_fn("* Empty node group, skipping verification")
3053       return True
3054
3055     self.bad = False
3056     _ErrorIf = self._ErrorIf # pylint: disable=C0103
3057     verbose = self.op.verbose
3058     self._feedback_fn = feedback_fn
3059
3060     vg_name = self.cfg.GetVGName()
3061     drbd_helper = self.cfg.GetDRBDHelper()
3062     cluster = self.cfg.GetClusterInfo()
3063     groupinfo = self.cfg.GetAllNodeGroupsInfo()
3064     hypervisors = cluster.enabled_hypervisors
3065     node_data_list = [self.my_node_info[name] for name in self.my_node_names]
3066
3067     i_non_redundant = [] # Non redundant instances
3068     i_non_a_balanced = [] # Non auto-balanced instances
3069     i_offline = 0 # Count of offline instances
3070     n_offline = 0 # Count of offline nodes
3071     n_drained = 0 # Count of nodes being drained
3072     node_vol_should = {}
3073
3074     # FIXME: verify OS list
3075
3076     # File verification
3077     filemap = _ComputeAncillaryFiles(cluster, False)
3078
3079     # do local checksums
3080     master_node = self.master_node = self.cfg.GetMasterNode()
3081     master_ip = self.cfg.GetMasterIP()
3082
3083     feedback_fn("* Gathering data (%d nodes)" % len(self.my_node_names))
3084
3085     user_scripts = []
3086     if self.cfg.GetUseExternalMipScript():
3087       user_scripts.append(constants.EXTERNAL_MASTER_SETUP_SCRIPT)
3088
3089     node_verify_param = {
3090       constants.NV_FILELIST:
3091         utils.UniqueSequence(filename
3092                              for files in filemap
3093                              for filename in files),
3094       constants.NV_NODELIST:
3095         self._SelectSshCheckNodes(node_data_list, self.group_uuid,
3096                                   self.all_node_info.values()),
3097       constants.NV_HYPERVISOR: hypervisors,
3098       constants.NV_HVPARAMS:
3099         _GetAllHypervisorParameters(cluster, self.all_inst_info.values()),
3100       constants.NV_NODENETTEST: [(node.name, node.primary_ip, node.secondary_ip)
3101                                  for node in node_data_list
3102                                  if not node.offline],
3103       constants.NV_INSTANCELIST: hypervisors,
3104       constants.NV_VERSION: None,
3105       constants.NV_HVINFO: self.cfg.GetHypervisorType(),
3106       constants.NV_NODESETUP: None,
3107       constants.NV_TIME: None,
3108       constants.NV_MASTERIP: (master_node, master_ip),
3109       constants.NV_OSLIST: None,
3110       constants.NV_VMNODES: self.cfg.GetNonVmCapableNodeList(),
3111       constants.NV_USERSCRIPTS: user_scripts,
3112       }
3113
3114     if vg_name is not None:
3115       node_verify_param[constants.NV_VGLIST] = None
3116       node_verify_param[constants.NV_LVLIST] = vg_name
3117       node_verify_param[constants.NV_PVLIST] = [vg_name]
3118       node_verify_param[constants.NV_DRBDLIST] = None
3119
3120     if drbd_helper:
3121       node_verify_param[constants.NV_DRBDHELPER] = drbd_helper
3122
3123     # bridge checks
3124     # FIXME: this needs to be changed per node-group, not cluster-wide
3125     bridges = set()
3126     default_nicpp = cluster.nicparams[constants.PP_DEFAULT]
3127     if default_nicpp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
3128       bridges.add(default_nicpp[constants.NIC_LINK])
3129     for instance in self.my_inst_info.values():
3130       for nic in instance.nics:
3131         full_nic = cluster.SimpleFillNIC(nic.nicparams)
3132         if full_nic[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
3133           bridges.add(full_nic[constants.NIC_LINK])
3134
3135     if bridges:
3136       node_verify_param[constants.NV_BRIDGES] = list(bridges)
3137
3138     # Build our expected cluster state
3139     node_image = dict((node.name, self.NodeImage(offline=node.offline,
3140                                                  name=node.name,
3141                                                  vm_capable=node.vm_capable))
3142                       for node in node_data_list)
3143
3144     # Gather OOB paths
3145     oob_paths = []
3146     for node in self.all_node_info.values():
3147       path = _SupportsOob(self.cfg, node)
3148       if path and path not in oob_paths:
3149         oob_paths.append(path)
3150
3151     if oob_paths:
3152       node_verify_param[constants.NV_OOB_PATHS] = oob_paths
3153
3154     for instance in self.my_inst_names:
3155       inst_config = self.my_inst_info[instance]
3156       if inst_config.admin_state == constants.ADMINST_OFFLINE:
3157         i_offline += 1
3158
3159       for nname in inst_config.all_nodes:
3160         if nname not in node_image:
3161           gnode = self.NodeImage(name=nname)
3162           gnode.ghost = (nname not in self.all_node_info)
3163           node_image[nname] = gnode
3164
3165       inst_config.MapLVsByNode(node_vol_should)
3166
3167       pnode = inst_config.primary_node
3168       node_image[pnode].pinst.append(instance)
3169
3170       for snode in inst_config.secondary_nodes:
3171         nimg = node_image[snode]
3172         nimg.sinst.append(instance)
3173         if pnode not in nimg.sbp:
3174           nimg.sbp[pnode] = []
3175         nimg.sbp[pnode].append(instance)
3176
3177     # At this point, we have the in-memory data structures complete,
3178     # except for the runtime information, which we'll gather next
3179
3180     # Due to the way our RPC system works, exact response times cannot be
3181     # guaranteed (e.g. a broken node could run into a timeout). By keeping the
3182     # time before and after executing the request, we can at least have a time
3183     # window.
3184     nvinfo_starttime = time.time()
3185     all_nvinfo = self.rpc.call_node_verify(self.my_node_names,
3186                                            node_verify_param,
3187                                            self.cfg.GetClusterName())
3188     nvinfo_endtime = time.time()
3189
3190     if self.extra_lv_nodes and vg_name is not None:
3191       extra_lv_nvinfo = \
3192           self.rpc.call_node_verify(self.extra_lv_nodes,
3193                                     {constants.NV_LVLIST: vg_name},
3194                                     self.cfg.GetClusterName())
3195     else:
3196       extra_lv_nvinfo = {}
3197
3198     all_drbd_map = self.cfg.ComputeDRBDMap()
3199
3200     feedback_fn("* Gathering disk information (%s nodes)" %
3201                 len(self.my_node_names))
3202     instdisk = self._CollectDiskInfo(self.my_node_names, node_image,
3203                                      self.my_inst_info)
3204
3205     feedback_fn("* Verifying configuration file consistency")
3206
3207     # If not all nodes are being checked, we need to make sure the master node
3208     # and a non-checked vm_capable node are in the list.
3209     absent_nodes = set(self.all_node_info).difference(self.my_node_info)
3210     if absent_nodes:
3211       vf_nvinfo = all_nvinfo.copy()
3212       vf_node_info = list(self.my_node_info.values())
3213       additional_nodes = []
3214       if master_node not in self.my_node_info:
3215         additional_nodes.append(master_node)
3216         vf_node_info.append(self.all_node_info[master_node])
3217       # Add the first vm_capable node we find which is not included
3218       for node in absent_nodes:
3219         nodeinfo = self.all_node_info[node]
3220         if nodeinfo.vm_capable and not nodeinfo.offline:
3221           additional_nodes.append(node)
3222           vf_node_info.append(self.all_node_info[node])
3223           break
3224       key = constants.NV_FILELIST
3225       vf_nvinfo.update(self.rpc.call_node_verify(additional_nodes,
3226                                                  {key: node_verify_param[key]},
3227                                                  self.cfg.GetClusterName()))
3228     else:
3229       vf_nvinfo = all_nvinfo
3230       vf_node_info = self.my_node_info.values()
3231
3232     self._VerifyFiles(_ErrorIf, vf_node_info, master_node, vf_nvinfo, filemap)
3233
3234     feedback_fn("* Verifying node status")
3235
3236     refos_img = None
3237
3238     for node_i in node_data_list:
3239       node = node_i.name
3240       nimg = node_image[node]
3241
3242       if node_i.offline:
3243         if verbose:
3244           feedback_fn("* Skipping offline node %s" % (node,))
3245         n_offline += 1
3246         continue
3247
3248       if node == master_node:
3249         ntype = "master"
3250       elif node_i.master_candidate:
3251         ntype = "master candidate"
3252       elif node_i.drained:
3253         ntype = "drained"
3254         n_drained += 1
3255       else:
3256         ntype = "regular"
3257       if verbose:
3258         feedback_fn("* Verifying node %s (%s)" % (node, ntype))
3259
3260       msg = all_nvinfo[node].fail_msg
3261       _ErrorIf(msg, constants.CV_ENODERPC, node, "while contacting node: %s",
3262                msg)
3263       if msg:
3264         nimg.rpc_fail = True
3265         continue
3266
3267       nresult = all_nvinfo[node].payload
3268
3269       nimg.call_ok = self._VerifyNode(node_i, nresult)
3270       self._VerifyNodeTime(node_i, nresult, nvinfo_starttime, nvinfo_endtime)
3271       self._VerifyNodeNetwork(node_i, nresult)
3272       self._VerifyNodeUserScripts(node_i, nresult)
3273       self._VerifyOob(node_i, nresult)
3274
3275       if nimg.vm_capable:
3276         self._VerifyNodeLVM(node_i, nresult, vg_name)
3277         self._VerifyNodeDrbd(node_i, nresult, self.all_inst_info, drbd_helper,
3278                              all_drbd_map)
3279
3280         self._UpdateNodeVolumes(node_i, nresult, nimg, vg_name)
3281         self._UpdateNodeInstances(node_i, nresult, nimg)
3282         self._UpdateNodeInfo(node_i, nresult, nimg, vg_name)
3283         self._UpdateNodeOS(node_i, nresult, nimg)
3284
3285         if not nimg.os_fail:
3286           if refos_img is None:
3287             refos_img = nimg
3288           self._VerifyNodeOS(node_i, nimg, refos_img)
3289         self._VerifyNodeBridges(node_i, nresult, bridges)
3290
3291         # Check whether all running instancies are primary for the node. (This
3292         # can no longer be done from _VerifyInstance below, since some of the
3293         # wrong instances could be from other node groups.)
3294         non_primary_inst = set(nimg.instances).difference(nimg.pinst)
3295
3296         for inst in non_primary_inst:
3297           test = inst in self.all_inst_info
3298           _ErrorIf(test, constants.CV_EINSTANCEWRONGNODE, inst,
3299                    "instance should not run on node %s", node_i.name)
3300           _ErrorIf(not test, constants.CV_ENODEORPHANINSTANCE, node_i.name,
3301                    "node is running unknown instance %s", inst)
3302
3303     for node, result in extra_lv_nvinfo.items():
3304       self._UpdateNodeVolumes(self.all_node_info[node], result.payload,
3305                               node_image[node], vg_name)
3306
3307     feedback_fn("* Verifying instance status")
3308     for instance in self.my_inst_names:
3309       if verbose:
3310         feedback_fn("* Verifying instance %s" % instance)
3311       inst_config = self.my_inst_info[instance]
3312       self._VerifyInstance(instance, inst_config, node_image,
3313                            instdisk[instance])
3314       inst_nodes_offline = []
3315
3316       pnode = inst_config.primary_node
3317       pnode_img = node_image[pnode]
3318       _ErrorIf(pnode_img.rpc_fail and not pnode_img.offline,
3319                constants.CV_ENODERPC, pnode, "instance %s, connection to"
3320                " primary node failed", instance)
3321
3322       _ErrorIf(inst_config.admin_state == constants.ADMINST_UP and
3323                pnode_img.offline,
3324                constants.CV_EINSTANCEBADNODE, instance,
3325                "instance is marked as running and lives on offline node %s",
3326                inst_config.primary_node)
3327
3328       # If the instance is non-redundant we cannot survive losing its primary
3329       # node, so we are not N+1 compliant. On the other hand we have no disk
3330       # templates with more than one secondary so that situation is not well
3331       # supported either.
3332       # FIXME: does not support file-backed instances
3333       if not inst_config.secondary_nodes:
3334         i_non_redundant.append(instance)
3335
3336       _ErrorIf(len(inst_config.secondary_nodes) > 1,
3337                constants.CV_EINSTANCELAYOUT,
3338                instance, "instance has multiple secondary nodes: %s",
3339                utils.CommaJoin(inst_config.secondary_nodes),
3340                code=self.ETYPE_WARNING)
3341
3342       if inst_config.disk_template in constants.DTS_INT_MIRROR:
3343         pnode = inst_config.primary_node
3344         instance_nodes = utils.NiceSort(inst_config.all_nodes)
3345         instance_groups = {}
3346
3347         for node in instance_nodes:
3348           instance_groups.setdefault(self.all_node_info[node].group,
3349                                      []).append(node)
3350
3351         pretty_list = [
3352           "%s (group %s)" % (utils.CommaJoin(nodes), groupinfo[group].name)
3353           # Sort so that we always list the primary node first.
3354           for group, nodes in sorted(instance_groups.items(),
3355                                      key=lambda (_, nodes): pnode in nodes,
3356                                      reverse=True)]
3357
3358         self._ErrorIf(len(instance_groups) > 1,
3359                       constants.CV_EINSTANCESPLITGROUPS,
3360                       instance, "instance has primary and secondary nodes in"
3361                       " different groups: %s", utils.CommaJoin(pretty_list),
3362                       code=self.ETYPE_WARNING)
3363
3364       if not cluster.FillBE(inst_config)[constants.BE_AUTO_BALANCE]:
3365         i_non_a_balanced.append(instance)
3366
3367       for snode in inst_config.secondary_nodes:
3368         s_img = node_image[snode]
3369         _ErrorIf(s_img.rpc_fail and not s_img.offline, constants.CV_ENODERPC,
3370                  snode, "instance %s, connection to secondary node failed",
3371                  instance)
3372
3373         if s_img.offline:
3374           inst_nodes_offline.append(snode)
3375
3376       # warn that the instance lives on offline nodes
3377       _ErrorIf(inst_nodes_offline, constants.CV_EINSTANCEBADNODE, instance,
3378                "instance has offline secondary node(s) %s",
3379                utils.CommaJoin(inst_nodes_offline))
3380       # ... or ghost/non-vm_capable nodes
3381       for node in inst_config.all_nodes:
3382         _ErrorIf(node_image[node].ghost, constants.CV_EINSTANCEBADNODE,
3383                  instance, "instance lives on ghost node %s", node)
3384         _ErrorIf(not node_image[node].vm_capable, constants.CV_EINSTANCEBADNODE,
3385                  instance, "instance lives on non-vm_capable node %s", node)
3386
3387     feedback_fn("* Verifying orphan volumes")
3388     reserved = utils.FieldSet(*cluster.reserved_lvs)
3389
3390     # We will get spurious "unknown volume" warnings if any node of this group
3391     # is secondary for an instance whose primary is in another group. To avoid
3392     # them, we find these instances and add their volumes to node_vol_should.
3393     for inst in self.all_inst_info.values():
3394       for secondary in inst.secondary_nodes:
3395         if (secondary in self.my_node_info
3396             and inst.name not in self.my_inst_info):
3397           inst.MapLVsByNode(node_vol_should)
3398           break
3399
3400     self._VerifyOrphanVolumes(node_vol_should, node_image, reserved)
3401
3402     if constants.VERIFY_NPLUSONE_MEM not in self.op.skip_checks:
3403       feedback_fn("* Verifying N+1 Memory redundancy")
3404       self._VerifyNPlusOneMemory(node_image, self.my_inst_info)
3405
3406     feedback_fn("* Other Notes")
3407     if i_non_redundant:
3408       feedback_fn("  - NOTICE: %d non-redundant instance(s) found."
3409                   % len(i_non_redundant))
3410
3411     if i_non_a_balanced:
3412       feedback_fn("  - NOTICE: %d non-auto-balanced instance(s) found."
3413                   % len(i_non_a_balanced))
3414
3415     if i_offline:
3416       feedback_fn("  - NOTICE: %d offline instance(s) found." % i_offline)
3417
3418     if n_offline:
3419       feedback_fn("  - NOTICE: %d offline node(s) found." % n_offline)
3420
3421     if n_drained:
3422       feedback_fn("  - NOTICE: %d drained node(s) found." % n_drained)
3423
3424     return not self.bad
3425
3426   def HooksCallBack(self, phase, hooks_results, feedback_fn, lu_result):
3427     """Analyze the post-hooks' result
3428
3429     This method analyses the hook result, handles it, and sends some
3430     nicely-formatted feedback back to the user.
3431
3432     @param phase: one of L{constants.HOOKS_PHASE_POST} or
3433         L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
3434     @param hooks_results: the results of the multi-node hooks rpc call
3435     @param feedback_fn: function used send feedback back to the caller
3436     @param lu_result: previous Exec result
3437     @return: the new Exec result, based on the previous result
3438         and hook results
3439
3440     """
3441     # We only really run POST phase hooks, only for non-empty groups,
3442     # and are only interested in their results
3443     if not self.my_node_names:
3444       # empty node group
3445       pass
3446     elif phase == constants.HOOKS_PHASE_POST:
3447       # Used to change hooks' output to proper indentation
3448       feedback_fn("* Hooks Results")
3449       assert hooks_results, "invalid result from hooks"
3450
3451       for node_name in hooks_results:
3452         res = hooks_results[node_name]
3453         msg = res.fail_msg
3454         test = msg and not res.offline
3455         self._ErrorIf(test, constants.CV_ENODEHOOKS, node_name,
3456                       "Communication failure in hooks execution: %s", msg)
3457         if res.offline or msg:
3458           # No need to investigate payload if node is offline or gave
3459           # an error.
3460           continue
3461         for script, hkr, output in res.payload:
3462           test = hkr == constants.HKR_FAIL
3463           self._ErrorIf(test, constants.CV_ENODEHOOKS, node_name,
3464                         "Script %s failed, output:", script)
3465           if test:
3466             output = self._HOOKS_INDENT_RE.sub("      ", output)
3467             feedback_fn("%s" % output)
3468             lu_result = False
3469
3470     return lu_result
3471
3472
3473 class LUClusterVerifyDisks(NoHooksLU):
3474   """Verifies the cluster disks status.
3475
3476   """
3477   REQ_BGL = False
3478
3479   def ExpandNames(self):
3480     self.share_locks = _ShareAll()
3481     self.needed_locks = {
3482       locking.LEVEL_NODEGROUP: locking.ALL_SET,
3483       }
3484
3485   def Exec(self, feedback_fn):
3486     group_names = self.owned_locks(locking.LEVEL_NODEGROUP)
3487
3488     # Submit one instance of L{opcodes.OpGroupVerifyDisks} per node group
3489     return ResultWithJobs([[opcodes.OpGroupVerifyDisks(group_name=group)]
3490                            for group in group_names])
3491
3492
3493 class LUGroupVerifyDisks(NoHooksLU):
3494   """Verifies the status of all disks in a node group.
3495
3496   """
3497   REQ_BGL = False
3498
3499   def ExpandNames(self):
3500     # Raises errors.OpPrereqError on its own if group can't be found
3501     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
3502
3503     self.share_locks = _ShareAll()
3504     self.needed_locks = {
3505       locking.LEVEL_INSTANCE: [],
3506       locking.LEVEL_NODEGROUP: [],
3507       locking.LEVEL_NODE: [],
3508       }
3509
3510   def DeclareLocks(self, level):
3511     if level == locking.LEVEL_INSTANCE:
3512       assert not self.needed_locks[locking.LEVEL_INSTANCE]
3513
3514       # Lock instances optimistically, needs verification once node and group
3515       # locks have been acquired
3516       self.needed_locks[locking.LEVEL_INSTANCE] = \
3517         self.cfg.GetNodeGroupInstances(self.group_uuid)
3518
3519     elif level == locking.LEVEL_NODEGROUP:
3520       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
3521
3522       self.needed_locks[locking.LEVEL_NODEGROUP] = \
3523         set([self.group_uuid] +
3524             # Lock all groups used by instances optimistically; this requires
3525             # going via the node before it's locked, requiring verification
3526             # later on
3527             [group_uuid
3528              for instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
3529              for group_uuid in self.cfg.GetInstanceNodeGroups(instance_name)])
3530
3531     elif level == locking.LEVEL_NODE:
3532       # This will only lock the nodes in the group to be verified which contain
3533       # actual instances
3534       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
3535       self._LockInstancesNodes()
3536
3537       # Lock all nodes in group to be verified
3538       assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
3539       member_nodes = self.cfg.GetNodeGroup(self.group_uuid).members
3540       self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
3541
3542   def CheckPrereq(self):
3543     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
3544     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
3545     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
3546
3547     assert self.group_uuid in owned_groups
3548
3549     # Check if locked instances are still correct
3550     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
3551
3552     # Get instance information
3553     self.instances = dict(self.cfg.GetMultiInstanceInfo(owned_instances))
3554
3555     # Check if node groups for locked instances are still correct
3556     _CheckInstancesNodeGroups(self.cfg, self.instances,
3557                               owned_groups, owned_nodes, self.group_uuid)
3558
3559   def Exec(self, feedback_fn):
3560     """Verify integrity of cluster disks.
3561
3562     @rtype: tuple of three items
3563     @return: a tuple of (dict of node-to-node_error, list of instances
3564         which need activate-disks, dict of instance: (node, volume) for
3565         missing volumes
3566
3567     """
3568     res_nodes = {}
3569     res_instances = set()
3570     res_missing = {}
3571
3572     nv_dict = _MapInstanceDisksToNodes([inst
3573             for inst in self.instances.values()
3574             if inst.admin_state == constants.ADMINST_UP])
3575
3576     if nv_dict:
3577       nodes = utils.NiceSort(set(self.owned_locks(locking.LEVEL_NODE)) &
3578                              set(self.cfg.GetVmCapableNodeList()))
3579
3580       node_lvs = self.rpc.call_lv_list(nodes, [])
3581
3582       for (node, node_res) in node_lvs.items():
3583         if node_res.offline:
3584           continue
3585
3586         msg = node_res.fail_msg
3587         if msg:
3588           logging.warning("Error enumerating LVs on node %s: %s", node, msg)
3589           res_nodes[node] = msg
3590           continue
3591
3592         for lv_name, (_, _, lv_online) in node_res.payload.items():
3593           inst = nv_dict.pop((node, lv_name), None)
3594           if not (lv_online or inst is None):
3595             res_instances.add(inst)
3596
3597       # any leftover items in nv_dict are missing LVs, let's arrange the data
3598       # better
3599       for key, inst in nv_dict.iteritems():
3600         res_missing.setdefault(inst, []).append(list(key))
3601
3602     return (res_nodes, list(res_instances), res_missing)
3603
3604
3605 class LUClusterRepairDiskSizes(NoHooksLU):
3606   """Verifies the cluster disks sizes.
3607
3608   """
3609   REQ_BGL = False
3610
3611   def ExpandNames(self):
3612     if self.op.instances:
3613       self.wanted_names = _GetWantedInstances(self, self.op.instances)
3614       self.needed_locks = {
3615         locking.LEVEL_NODE_RES: [],
3616         locking.LEVEL_INSTANCE: self.wanted_names,
3617         }
3618       self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
3619     else:
3620       self.wanted_names = None
3621       self.needed_locks = {
3622         locking.LEVEL_NODE_RES: locking.ALL_SET,
3623         locking.LEVEL_INSTANCE: locking.ALL_SET,
3624         }
3625     self.share_locks = {
3626       locking.LEVEL_NODE_RES: 1,
3627       locking.LEVEL_INSTANCE: 0,
3628       }
3629
3630   def DeclareLocks(self, level):
3631     if level == locking.LEVEL_NODE_RES and self.wanted_names is not None:
3632       self._LockInstancesNodes(primary_only=True, level=level)
3633
3634   def CheckPrereq(self):
3635     """Check prerequisites.
3636
3637     This only checks the optional instance list against the existing names.
3638
3639     """
3640     if self.wanted_names is None:
3641       self.wanted_names = self.owned_locks(locking.LEVEL_INSTANCE)
3642
3643     self.wanted_instances = \
3644         map(compat.snd, self.cfg.GetMultiInstanceInfo(self.wanted_names))
3645
3646   def _EnsureChildSizes(self, disk):
3647     """Ensure children of the disk have the needed disk size.
3648
3649     This is valid mainly for DRBD8 and fixes an issue where the
3650     children have smaller disk size.
3651
3652     @param disk: an L{ganeti.objects.Disk} object
3653
3654     """
3655     if disk.dev_type == constants.LD_DRBD8:
3656       assert disk.children, "Empty children for DRBD8?"
3657       fchild = disk.children[0]
3658       mismatch = fchild.size < disk.size
3659       if mismatch:
3660         self.LogInfo("Child disk has size %d, parent %d, fixing",
3661                      fchild.size, disk.size)
3662         fchild.size = disk.size
3663
3664       # and we recurse on this child only, not on the metadev
3665       return self._EnsureChildSizes(fchild) or mismatch
3666     else:
3667       return False
3668
3669   def Exec(self, feedback_fn):
3670     """Verify the size of cluster disks.
3671
3672     """
3673     # TODO: check child disks too
3674     # TODO: check differences in size between primary/secondary nodes
3675     per_node_disks = {}
3676     for instance in self.wanted_instances:
3677       pnode = instance.primary_node
3678       if pnode not in per_node_disks:
3679         per_node_disks[pnode] = []
3680       for idx, disk in enumerate(instance.disks):
3681         per_node_disks[pnode].append((instance, idx, disk))
3682
3683     assert not (frozenset(per_node_disks.keys()) -
3684                 self.owned_locks(locking.LEVEL_NODE_RES)), \
3685       "Not owning correct locks"
3686     assert not self.owned_locks(locking.LEVEL_NODE)
3687
3688     changed = []
3689     for node, dskl in per_node_disks.items():
3690       newl = [v[2].Copy() for v in dskl]
3691       for dsk in newl:
3692         self.cfg.SetDiskID(dsk, node)
3693       result = self.rpc.call_blockdev_getsize(node, newl)
3694       if result.fail_msg:
3695         self.LogWarning("Failure in blockdev_getsize call to node"
3696                         " %s, ignoring", node)
3697         continue
3698       if len(result.payload) != len(dskl):
3699         logging.warning("Invalid result from node %s: len(dksl)=%d,"
3700                         " result.payload=%s", node, len(dskl), result.payload)
3701         self.LogWarning("Invalid result from node %s, ignoring node results",
3702                         node)
3703         continue
3704       for ((instance, idx, disk), size) in zip(dskl, result.payload):
3705         if size is None:
3706           self.LogWarning("Disk %d of instance %s did not return size"
3707                           " information, ignoring", idx, instance.name)
3708           continue
3709         if not isinstance(size, (int, long)):
3710           self.LogWarning("Disk %d of instance %s did not return valid"
3711                           " size information, ignoring", idx, instance.name)
3712           continue
3713         size = size >> 20
3714         if size != disk.size:
3715           self.LogInfo("Disk %d of instance %s has mismatched size,"
3716                        " correcting: recorded %d, actual %d", idx,
3717                        instance.name, disk.size, size)
3718           disk.size = size
3719           self.cfg.Update(instance, feedback_fn)
3720           changed.append((instance.name, idx, size))
3721         if self._EnsureChildSizes(disk):
3722           self.cfg.Update(instance, feedback_fn)
3723           changed.append((instance.name, idx, disk.size))
3724     return changed
3725
3726
3727 class LUClusterRename(LogicalUnit):
3728   """Rename the cluster.
3729
3730   """
3731   HPATH = "cluster-rename"
3732   HTYPE = constants.HTYPE_CLUSTER
3733
3734   def BuildHooksEnv(self):
3735     """Build hooks env.
3736
3737     """
3738     return {
3739       "OP_TARGET": self.cfg.GetClusterName(),
3740       "NEW_NAME": self.op.name,
3741       }
3742
3743   def BuildHooksNodes(self):
3744     """Build hooks nodes.
3745
3746     """
3747     return ([self.cfg.GetMasterNode()], self.cfg.GetNodeList())
3748
3749   def CheckPrereq(self):
3750     """Verify that the passed name is a valid one.
3751
3752     """
3753     hostname = netutils.GetHostname(name=self.op.name,
3754                                     family=self.cfg.GetPrimaryIPFamily())
3755
3756     new_name = hostname.name
3757     self.ip = new_ip = hostname.ip
3758     old_name = self.cfg.GetClusterName()
3759     old_ip = self.cfg.GetMasterIP()
3760     if new_name == old_name and new_ip == old_ip:
3761       raise errors.OpPrereqError("Neither the name nor the IP address of the"
3762                                  " cluster has changed",
3763                                  errors.ECODE_INVAL)
3764     if new_ip != old_ip:
3765       if netutils.TcpPing(new_ip, constants.DEFAULT_NODED_PORT):
3766         raise errors.OpPrereqError("The given cluster IP address (%s) is"
3767                                    " reachable on the network" %
3768                                    new_ip, errors.ECODE_NOTUNIQUE)
3769
3770     self.op.name = new_name
3771
3772   def Exec(self, feedback_fn):
3773     """Rename the cluster.
3774
3775     """
3776     clustername = self.op.name
3777     new_ip = self.ip
3778
3779     # shutdown the master IP
3780     master_params = self.cfg.GetMasterNetworkParameters()
3781     ems = self.cfg.GetUseExternalMipScript()
3782     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
3783                                                      master_params, ems)
3784     result.Raise("Could not disable the master role")
3785
3786     try:
3787       cluster = self.cfg.GetClusterInfo()
3788       cluster.cluster_name = clustername
3789       cluster.master_ip = new_ip
3790       self.cfg.Update(cluster, feedback_fn)
3791
3792       # update the known hosts file
3793       ssh.WriteKnownHostsFile(self.cfg, constants.SSH_KNOWN_HOSTS_FILE)
3794       node_list = self.cfg.GetOnlineNodeList()
3795       try:
3796         node_list.remove(master_params.name)
3797       except ValueError:
3798         pass
3799       _UploadHelper(self, node_list, constants.SSH_KNOWN_HOSTS_FILE)
3800     finally:
3801       master_params.ip = new_ip
3802       result = self.rpc.call_node_activate_master_ip(master_params.name,
3803                                                      master_params, ems)
3804       msg = result.fail_msg
3805       if msg:
3806         self.LogWarning("Could not re-enable the master role on"
3807                         " the master, please restart manually: %s", msg)
3808
3809     return clustername
3810
3811
3812 def _ValidateNetmask(cfg, netmask):
3813   """Checks if a netmask is valid.
3814
3815   @type cfg: L{config.ConfigWriter}
3816   @param cfg: The cluster configuration
3817   @type netmask: int
3818   @param netmask: the netmask to be verified
3819   @raise errors.OpPrereqError: if the validation fails
3820
3821   """
3822   ip_family = cfg.GetPrimaryIPFamily()
3823   try:
3824     ipcls = netutils.IPAddress.GetClassFromIpFamily(ip_family)
3825   except errors.ProgrammerError:
3826     raise errors.OpPrereqError("Invalid primary ip family: %s." %
3827                                ip_family)
3828   if not ipcls.ValidateNetmask(netmask):
3829     raise errors.OpPrereqError("CIDR netmask (%s) not valid" %
3830                                 (netmask))
3831
3832
3833 class LUClusterSetParams(LogicalUnit):
3834   """Change the parameters of the cluster.
3835
3836   """
3837   HPATH = "cluster-modify"
3838   HTYPE = constants.HTYPE_CLUSTER
3839   REQ_BGL = False
3840
3841   def CheckArguments(self):
3842     """Check parameters
3843
3844     """
3845     if self.op.uid_pool:
3846       uidpool.CheckUidPool(self.op.uid_pool)
3847
3848     if self.op.add_uids:
3849       uidpool.CheckUidPool(self.op.add_uids)
3850
3851     if self.op.remove_uids:
3852       uidpool.CheckUidPool(self.op.remove_uids)
3853
3854     if self.op.master_netmask is not None:
3855       _ValidateNetmask(self.cfg, self.op.master_netmask)
3856
3857     if self.op.diskparams:
3858       for dt_params in self.op.diskparams.values():
3859         utils.ForceDictType(dt_params, constants.DISK_DT_TYPES)
3860       try:
3861         utils.VerifyDictOptions(self.op.diskparams, constants.DISK_DT_DEFAULTS)
3862       except errors.OpPrereqError, err:
3863         raise errors.OpPrereqError("While verify diskparams options: %s" % err,
3864                                    errors.ECODE_INVAL)
3865
3866   def ExpandNames(self):
3867     # FIXME: in the future maybe other cluster params won't require checking on
3868     # all nodes to be modified.
3869     self.needed_locks = {
3870       locking.LEVEL_NODE: locking.ALL_SET,
3871       locking.LEVEL_INSTANCE: locking.ALL_SET,
3872       locking.LEVEL_NODEGROUP: locking.ALL_SET,
3873     }
3874     self.share_locks = {
3875         locking.LEVEL_NODE: 1,
3876         locking.LEVEL_INSTANCE: 1,
3877         locking.LEVEL_NODEGROUP: 1,
3878     }
3879
3880   def BuildHooksEnv(self):
3881     """Build hooks env.
3882
3883     """
3884     return {
3885       "OP_TARGET": self.cfg.GetClusterName(),
3886       "NEW_VG_NAME": self.op.vg_name,
3887       }
3888
3889   def BuildHooksNodes(self):
3890     """Build hooks nodes.
3891
3892     """
3893     mn = self.cfg.GetMasterNode()
3894     return ([mn], [mn])
3895
3896   def CheckPrereq(self):
3897     """Check prerequisites.
3898
3899     This checks whether the given params don't conflict and
3900     if the given volume group is valid.
3901
3902     """
3903     if self.op.vg_name is not None and not self.op.vg_name:
3904       if self.cfg.HasAnyDiskOfType(constants.LD_LV):
3905         raise errors.OpPrereqError("Cannot disable lvm storage while lvm-based"
3906                                    " instances exist", errors.ECODE_INVAL)
3907
3908     if self.op.drbd_helper is not None and not self.op.drbd_helper:
3909       if self.cfg.HasAnyDiskOfType(constants.LD_DRBD8):
3910         raise errors.OpPrereqError("Cannot disable drbd helper while"
3911                                    " drbd-based instances exist",
3912                                    errors.ECODE_INVAL)
3913
3914     node_list = self.owned_locks(locking.LEVEL_NODE)
3915
3916     # if vg_name not None, checks given volume group on all nodes
3917     if self.op.vg_name:
3918       vglist = self.rpc.call_vg_list(node_list)
3919       for node in node_list:
3920         msg = vglist[node].fail_msg
3921         if msg:
3922           # ignoring down node
3923           self.LogWarning("Error while gathering data on node %s"
3924                           " (ignoring node): %s", node, msg)
3925           continue
3926         vgstatus = utils.CheckVolumeGroupSize(vglist[node].payload,
3927                                               self.op.vg_name,
3928                                               constants.MIN_VG_SIZE)
3929         if vgstatus:
3930           raise errors.OpPrereqError("Error on node '%s': %s" %
3931                                      (node, vgstatus), errors.ECODE_ENVIRON)
3932
3933     if self.op.drbd_helper:
3934       # checks given drbd helper on all nodes
3935       helpers = self.rpc.call_drbd_helper(node_list)
3936       for (node, ninfo) in self.cfg.GetMultiNodeInfo(node_list):
3937         if ninfo.offline:
3938           self.LogInfo("Not checking drbd helper on offline node %s", node)
3939           continue
3940         msg = helpers[node].fail_msg
3941         if msg:
3942           raise errors.OpPrereqError("Error checking drbd helper on node"
3943                                      " '%s': %s" % (node, msg),
3944                                      errors.ECODE_ENVIRON)
3945         node_helper = helpers[node].payload
3946         if node_helper != self.op.drbd_helper:
3947           raise errors.OpPrereqError("Error on node '%s': drbd helper is %s" %
3948                                      (node, node_helper), errors.ECODE_ENVIRON)
3949
3950     self.cluster = cluster = self.cfg.GetClusterInfo()
3951     # validate params changes
3952     if self.op.beparams:
3953       objects.UpgradeBeParams(self.op.beparams)
3954       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
3955       self.new_beparams = cluster.SimpleFillBE(self.op.beparams)
3956
3957     if self.op.ndparams:
3958       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
3959       self.new_ndparams = cluster.SimpleFillND(self.op.ndparams)
3960
3961       # TODO: we need a more general way to handle resetting
3962       # cluster-level parameters to default values
3963       if self.new_ndparams["oob_program"] == "":
3964         self.new_ndparams["oob_program"] = \
3965             constants.NDC_DEFAULTS[constants.ND_OOB_PROGRAM]
3966
3967     if self.op.hv_state:
3968       new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
3969                                             self.cluster.hv_state_static)
3970       self.new_hv_state = dict((hv, cluster.SimpleFillHvState(values))
3971                                for hv, values in new_hv_state.items())
3972
3973     if self.op.disk_state:
3974       new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state,
3975                                                 self.cluster.disk_state_static)
3976       self.new_disk_state = \
3977         dict((storage, dict((name, cluster.SimpleFillDiskState(values))
3978                             for name, values in svalues.items()))
3979              for storage, svalues in new_disk_state.items())
3980
3981     if self.op.ipolicy:
3982       self.new_ipolicy = _GetUpdatedIPolicy(cluster.ipolicy, self.op.ipolicy,
3983                                             group_policy=False)
3984
3985       all_instances = self.cfg.GetAllInstancesInfo().values()
3986       violations = set()
3987       for group in self.cfg.GetAllNodeGroupsInfo().values():
3988         instances = frozenset([inst for inst in all_instances
3989                                if compat.any(node in group.members
3990                                              for node in inst.all_nodes)])
3991         new_ipolicy = objects.FillIPolicy(self.new_ipolicy, group.ipolicy)
3992         new = _ComputeNewInstanceViolations(_CalculateGroupIPolicy(cluster,
3993                                                                    group),
3994                                             new_ipolicy, instances)
3995         if new:
3996           violations.update(new)
3997
3998       if violations:
3999         self.LogWarning("After the ipolicy change the following instances"
4000                         " violate them: %s",
4001                         utils.CommaJoin(utils.NiceSort(violations)))
4002
4003     if self.op.nicparams:
4004       utils.ForceDictType(self.op.nicparams, constants.NICS_PARAMETER_TYPES)
4005       self.new_nicparams = cluster.SimpleFillNIC(self.op.nicparams)
4006       objects.NIC.CheckParameterSyntax(self.new_nicparams)
4007       nic_errors = []
4008
4009       # check all instances for consistency
4010       for instance in self.cfg.GetAllInstancesInfo().values():
4011         for nic_idx, nic in enumerate(instance.nics):
4012           params_copy = copy.deepcopy(nic.nicparams)
4013           params_filled = objects.FillDict(self.new_nicparams, params_copy)
4014
4015           # check parameter syntax
4016           try:
4017             objects.NIC.CheckParameterSyntax(params_filled)
4018           except errors.ConfigurationError, err:
4019             nic_errors.append("Instance %s, nic/%d: %s" %
4020                               (instance.name, nic_idx, err))
4021
4022           # if we're moving instances to routed, check that they have an ip
4023           target_mode = params_filled[constants.NIC_MODE]
4024           if target_mode == constants.NIC_MODE_ROUTED and not nic.ip:
4025             nic_errors.append("Instance %s, nic/%d: routed NIC with no ip"
4026                               " address" % (instance.name, nic_idx))
4027       if nic_errors:
4028         raise errors.OpPrereqError("Cannot apply the change, errors:\n%s" %
4029                                    "\n".join(nic_errors))
4030
4031     # hypervisor list/parameters
4032     self.new_hvparams = new_hvp = objects.FillDict(cluster.hvparams, {})
4033     if self.op.hvparams:
4034       for hv_name, hv_dict in self.op.hvparams.items():
4035         if hv_name not in self.new_hvparams:
4036           self.new_hvparams[hv_name] = hv_dict
4037         else:
4038           self.new_hvparams[hv_name].update(hv_dict)
4039
4040     # disk template parameters
4041     self.new_diskparams = objects.FillDict(cluster.diskparams, {})
4042     if self.op.diskparams:
4043       for dt_name, dt_params in self.op.diskparams.items():
4044         if dt_name not in self.op.diskparams:
4045           self.new_diskparams[dt_name] = dt_params
4046         else:
4047           self.new_diskparams[dt_name].update(dt_params)
4048
4049     # os hypervisor parameters
4050     self.new_os_hvp = objects.FillDict(cluster.os_hvp, {})
4051     if self.op.os_hvp:
4052       for os_name, hvs in self.op.os_hvp.items():
4053         if os_name not in self.new_os_hvp:
4054           self.new_os_hvp[os_name] = hvs
4055         else:
4056           for hv_name, hv_dict in hvs.items():
4057             if hv_name not in self.new_os_hvp[os_name]:
4058               self.new_os_hvp[os_name][hv_name] = hv_dict
4059             else:
4060               self.new_os_hvp[os_name][hv_name].update(hv_dict)
4061
4062     # os parameters
4063     self.new_osp = objects.FillDict(cluster.osparams, {})
4064     if self.op.osparams:
4065       for os_name, osp in self.op.osparams.items():
4066         if os_name not in self.new_osp:
4067           self.new_osp[os_name] = {}
4068
4069         self.new_osp[os_name] = _GetUpdatedParams(self.new_osp[os_name], osp,
4070                                                   use_none=True)
4071
4072         if not self.new_osp[os_name]:
4073           # we removed all parameters
4074           del self.new_osp[os_name]
4075         else:
4076           # check the parameter validity (remote check)
4077           _CheckOSParams(self, False, [self.cfg.GetMasterNode()],
4078                          os_name, self.new_osp[os_name])
4079
4080     # changes to the hypervisor list
4081     if self.op.enabled_hypervisors is not None:
4082       self.hv_list = self.op.enabled_hypervisors
4083       for hv in self.hv_list:
4084         # if the hypervisor doesn't already exist in the cluster
4085         # hvparams, we initialize it to empty, and then (in both
4086         # cases) we make sure to fill the defaults, as we might not
4087         # have a complete defaults list if the hypervisor wasn't
4088         # enabled before
4089         if hv not in new_hvp:
4090           new_hvp[hv] = {}
4091         new_hvp[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], new_hvp[hv])
4092         utils.ForceDictType(new_hvp[hv], constants.HVS_PARAMETER_TYPES)
4093     else:
4094       self.hv_list = cluster.enabled_hypervisors
4095
4096     if self.op.hvparams or self.op.enabled_hypervisors is not None:
4097       # either the enabled list has changed, or the parameters have, validate
4098       for hv_name, hv_params in self.new_hvparams.items():
4099         if ((self.op.hvparams and hv_name in self.op.hvparams) or
4100             (self.op.enabled_hypervisors and
4101              hv_name in self.op.enabled_hypervisors)):
4102           # either this is a new hypervisor, or its parameters have changed
4103           hv_class = hypervisor.GetHypervisor(hv_name)
4104           utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
4105           hv_class.CheckParameterSyntax(hv_params)
4106           _CheckHVParams(self, node_list, hv_name, hv_params)
4107
4108     if self.op.os_hvp:
4109       # no need to check any newly-enabled hypervisors, since the
4110       # defaults have already been checked in the above code-block
4111       for os_name, os_hvp in self.new_os_hvp.items():
4112         for hv_name, hv_params in os_hvp.items():
4113           utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
4114           # we need to fill in the new os_hvp on top of the actual hv_p
4115           cluster_defaults = self.new_hvparams.get(hv_name, {})
4116           new_osp = objects.FillDict(cluster_defaults, hv_params)
4117           hv_class = hypervisor.GetHypervisor(hv_name)
4118           hv_class.CheckParameterSyntax(new_osp)
4119           _CheckHVParams(self, node_list, hv_name, new_osp)
4120
4121     if self.op.default_iallocator:
4122       alloc_script = utils.FindFile(self.op.default_iallocator,
4123                                     constants.IALLOCATOR_SEARCH_PATH,
4124                                     os.path.isfile)
4125       if alloc_script is None:
4126         raise errors.OpPrereqError("Invalid default iallocator script '%s'"
4127                                    " specified" % self.op.default_iallocator,
4128                                    errors.ECODE_INVAL)
4129
4130   def Exec(self, feedback_fn):
4131     """Change the parameters of the cluster.
4132
4133     """
4134     if self.op.vg_name is not None:
4135       new_volume = self.op.vg_name
4136       if not new_volume:
4137         new_volume = None
4138       if new_volume != self.cfg.GetVGName():
4139         self.cfg.SetVGName(new_volume)
4140       else:
4141         feedback_fn("Cluster LVM configuration already in desired"
4142                     " state, not changing")
4143     if self.op.drbd_helper is not None:
4144       new_helper = self.op.drbd_helper
4145       if not new_helper:
4146         new_helper = None
4147       if new_helper != self.cfg.GetDRBDHelper():
4148         self.cfg.SetDRBDHelper(new_helper)
4149       else:
4150         feedback_fn("Cluster DRBD helper already in desired state,"
4151                     " not changing")
4152     if self.op.hvparams:
4153       self.cluster.hvparams = self.new_hvparams
4154     if self.op.os_hvp:
4155       self.cluster.os_hvp = self.new_os_hvp
4156     if self.op.enabled_hypervisors is not None:
4157       self.cluster.hvparams = self.new_hvparams
4158       self.cluster.enabled_hypervisors = self.op.enabled_hypervisors
4159     if self.op.beparams:
4160       self.cluster.beparams[constants.PP_DEFAULT] = self.new_beparams
4161     if self.op.nicparams:
4162       self.cluster.nicparams[constants.PP_DEFAULT] = self.new_nicparams
4163     if self.op.ipolicy:
4164       self.cluster.ipolicy = self.new_ipolicy
4165     if self.op.osparams:
4166       self.cluster.osparams = self.new_osp
4167     if self.op.ndparams:
4168       self.cluster.ndparams = self.new_ndparams
4169     if self.op.diskparams:
4170       self.cluster.diskparams = self.new_diskparams
4171     if self.op.hv_state:
4172       self.cluster.hv_state_static = self.new_hv_state
4173     if self.op.disk_state:
4174       self.cluster.disk_state_static = self.new_disk_state
4175
4176     if self.op.candidate_pool_size is not None:
4177       self.cluster.candidate_pool_size = self.op.candidate_pool_size
4178       # we need to update the pool size here, otherwise the save will fail
4179       _AdjustCandidatePool(self, [])
4180
4181     if self.op.maintain_node_health is not None:
4182       if self.op.maintain_node_health and not constants.ENABLE_CONFD:
4183         feedback_fn("Note: CONFD was disabled at build time, node health"
4184                     " maintenance is not useful (still enabling it)")
4185       self.cluster.maintain_node_health = self.op.maintain_node_health
4186
4187     if self.op.prealloc_wipe_disks is not None:
4188       self.cluster.prealloc_wipe_disks = self.op.prealloc_wipe_disks
4189
4190     if self.op.add_uids is not None:
4191       uidpool.AddToUidPool(self.cluster.uid_pool, self.op.add_uids)
4192
4193     if self.op.remove_uids is not None:
4194       uidpool.RemoveFromUidPool(self.cluster.uid_pool, self.op.remove_uids)
4195
4196     if self.op.uid_pool is not None:
4197       self.cluster.uid_pool = self.op.uid_pool
4198
4199     if self.op.default_iallocator is not None:
4200       self.cluster.default_iallocator = self.op.default_iallocator
4201
4202     if self.op.reserved_lvs is not None:
4203       self.cluster.reserved_lvs = self.op.reserved_lvs
4204
4205     if self.op.use_external_mip_script is not None:
4206       self.cluster.use_external_mip_script = self.op.use_external_mip_script
4207
4208     def helper_os(aname, mods, desc):
4209       desc += " OS list"
4210       lst = getattr(self.cluster, aname)
4211       for key, val in mods:
4212         if key == constants.DDM_ADD:
4213           if val in lst:
4214             feedback_fn("OS %s already in %s, ignoring" % (val, desc))
4215           else:
4216             lst.append(val)
4217         elif key == constants.DDM_REMOVE:
4218           if val in lst:
4219             lst.remove(val)
4220           else:
4221             feedback_fn("OS %s not found in %s, ignoring" % (val, desc))
4222         else:
4223           raise errors.ProgrammerError("Invalid modification '%s'" % key)
4224
4225     if self.op.hidden_os:
4226       helper_os("hidden_os", self.op.hidden_os, "hidden")
4227
4228     if self.op.blacklisted_os:
4229       helper_os("blacklisted_os", self.op.blacklisted_os, "blacklisted")
4230
4231     if self.op.master_netdev:
4232       master_params = self.cfg.GetMasterNetworkParameters()
4233       ems = self.cfg.GetUseExternalMipScript()
4234       feedback_fn("Shutting down master ip on the current netdev (%s)" %
4235                   self.cluster.master_netdev)
4236       result = self.rpc.call_node_deactivate_master_ip(master_params.name,
4237                                                        master_params, ems)
4238       result.Raise("Could not disable the master ip")
4239       feedback_fn("Changing master_netdev from %s to %s" %
4240                   (master_params.netdev, self.op.master_netdev))
4241       self.cluster.master_netdev = self.op.master_netdev
4242
4243     if self.op.master_netmask:
4244       master_params = self.cfg.GetMasterNetworkParameters()
4245       feedback_fn("Changing master IP netmask to %s" % self.op.master_netmask)
4246       result = self.rpc.call_node_change_master_netmask(master_params.name,
4247                                                         master_params.netmask,
4248                                                         self.op.master_netmask,
4249                                                         master_params.ip,
4250                                                         master_params.netdev)
4251       if result.fail_msg:
4252         msg = "Could not change the master IP netmask: %s" % result.fail_msg
4253         feedback_fn(msg)
4254
4255       self.cluster.master_netmask = self.op.master_netmask
4256
4257     self.cfg.Update(self.cluster, feedback_fn)
4258
4259     if self.op.master_netdev:
4260       master_params = self.cfg.GetMasterNetworkParameters()
4261       feedback_fn("Starting the master ip on the new master netdev (%s)" %
4262                   self.op.master_netdev)
4263       ems = self.cfg.GetUseExternalMipScript()
4264       result = self.rpc.call_node_activate_master_ip(master_params.name,
4265                                                      master_params, ems)
4266       if result.fail_msg:
4267         self.LogWarning("Could not re-enable the master ip on"
4268                         " the master, please restart manually: %s",
4269                         result.fail_msg)
4270
4271
4272 def _UploadHelper(lu, nodes, fname):
4273   """Helper for uploading a file and showing warnings.
4274
4275   """
4276   if os.path.exists(fname):
4277     result = lu.rpc.call_upload_file(nodes, fname)
4278     for to_node, to_result in result.items():
4279       msg = to_result.fail_msg
4280       if msg:
4281         msg = ("Copy of file %s to node %s failed: %s" %
4282                (fname, to_node, msg))
4283         lu.proc.LogWarning(msg)
4284
4285
4286 def _ComputeAncillaryFiles(cluster, redist):
4287   """Compute files external to Ganeti which need to be consistent.
4288
4289   @type redist: boolean
4290   @param redist: Whether to include files which need to be redistributed
4291
4292   """
4293   # Compute files for all nodes
4294   files_all = set([
4295     constants.SSH_KNOWN_HOSTS_FILE,
4296     constants.CONFD_HMAC_KEY,
4297     constants.CLUSTER_DOMAIN_SECRET_FILE,
4298     constants.SPICE_CERT_FILE,
4299     constants.SPICE_CACERT_FILE,
4300     constants.RAPI_USERS_FILE,
4301     ])
4302
4303   if not redist:
4304     files_all.update(constants.ALL_CERT_FILES)
4305     files_all.update(ssconf.SimpleStore().GetFileList())
4306   else:
4307     # we need to ship at least the RAPI certificate
4308     files_all.add(constants.RAPI_CERT_FILE)
4309
4310   if cluster.modify_etc_hosts:
4311     files_all.add(constants.ETC_HOSTS)
4312
4313   if cluster.use_external_mip_script:
4314     files_all.add(constants.EXTERNAL_MASTER_SETUP_SCRIPT)
4315
4316   # Files which are optional, these must:
4317   # - be present in one other category as well
4318   # - either exist or not exist on all nodes of that category (mc, vm all)
4319   files_opt = set([
4320     constants.RAPI_USERS_FILE,
4321     ])
4322
4323   # Files which should only be on master candidates
4324   files_mc = set()
4325
4326   if not redist:
4327     files_mc.add(constants.CLUSTER_CONF_FILE)
4328
4329   # Files which should only be on VM-capable nodes
4330   files_vm = set(filename
4331     for hv_name in cluster.enabled_hypervisors
4332     for filename in hypervisor.GetHypervisor(hv_name).GetAncillaryFiles()[0])
4333
4334   files_opt |= set(filename
4335     for hv_name in cluster.enabled_hypervisors
4336     for filename in hypervisor.GetHypervisor(hv_name).GetAncillaryFiles()[1])
4337
4338   # Filenames in each category must be unique
4339   all_files_set = files_all | files_mc | files_vm
4340   assert (len(all_files_set) ==
4341           sum(map(len, [files_all, files_mc, files_vm]))), \
4342          "Found file listed in more than one file list"
4343
4344   # Optional files must be present in one other category
4345   assert all_files_set.issuperset(files_opt), \
4346          "Optional file not in a different required list"
4347
4348   return (files_all, files_opt, files_mc, files_vm)
4349
4350
4351 def _RedistributeAncillaryFiles(lu, additional_nodes=None, additional_vm=True):
4352   """Distribute additional files which are part of the cluster configuration.
4353
4354   ConfigWriter takes care of distributing the config and ssconf files, but
4355   there are more files which should be distributed to all nodes. This function
4356   makes sure those are copied.
4357
4358   @param lu: calling logical unit
4359   @param additional_nodes: list of nodes not in the config to distribute to
4360   @type additional_vm: boolean
4361   @param additional_vm: whether the additional nodes are vm-capable or not
4362
4363   """
4364   # Gather target nodes
4365   cluster = lu.cfg.GetClusterInfo()
4366   master_info = lu.cfg.GetNodeInfo(lu.cfg.GetMasterNode())
4367
4368   online_nodes = lu.cfg.GetOnlineNodeList()
4369   online_set = frozenset(online_nodes)
4370   vm_nodes = list(online_set.intersection(lu.cfg.GetVmCapableNodeList()))
4371
4372   if additional_nodes is not None:
4373     online_nodes.extend(additional_nodes)
4374     if additional_vm:
4375       vm_nodes.extend(additional_nodes)
4376
4377   # Never distribute to master node
4378   for nodelist in [online_nodes, vm_nodes]:
4379     if master_info.name in nodelist:
4380       nodelist.remove(master_info.name)
4381
4382   # Gather file lists
4383   (files_all, _, files_mc, files_vm) = \
4384     _ComputeAncillaryFiles(cluster, True)
4385
4386   # Never re-distribute configuration file from here
4387   assert not (constants.CLUSTER_CONF_FILE in files_all or
4388               constants.CLUSTER_CONF_FILE in files_vm)
4389   assert not files_mc, "Master candidates not handled in this function"
4390
4391   filemap = [
4392     (online_nodes, files_all),
4393     (vm_nodes, files_vm),
4394     ]
4395
4396   # Upload the files
4397   for (node_list, files) in filemap:
4398     for fname in files:
4399       _UploadHelper(lu, node_list, fname)
4400
4401
4402 class LUClusterRedistConf(NoHooksLU):
4403   """Force the redistribution of cluster configuration.
4404
4405   This is a very simple LU.
4406
4407   """
4408   REQ_BGL = False
4409
4410   def ExpandNames(self):
4411     self.needed_locks = {
4412       locking.LEVEL_NODE: locking.ALL_SET,
4413     }
4414     self.share_locks[locking.LEVEL_NODE] = 1
4415
4416   def Exec(self, feedback_fn):
4417     """Redistribute the configuration.
4418
4419     """
4420     self.cfg.Update(self.cfg.GetClusterInfo(), feedback_fn)
4421     _RedistributeAncillaryFiles(self)
4422
4423
4424 class LUClusterActivateMasterIp(NoHooksLU):
4425   """Activate the master IP on the master node.
4426
4427   """
4428   def Exec(self, feedback_fn):
4429     """Activate the master IP.
4430
4431     """
4432     master_params = self.cfg.GetMasterNetworkParameters()
4433     ems = self.cfg.GetUseExternalMipScript()
4434     result = self.rpc.call_node_activate_master_ip(master_params.name,
4435                                                    master_params, ems)
4436     result.Raise("Could not activate the master IP")
4437
4438
4439 class LUClusterDeactivateMasterIp(NoHooksLU):
4440   """Deactivate the master IP on the master node.
4441
4442   """
4443   def Exec(self, feedback_fn):
4444     """Deactivate the master IP.
4445
4446     """
4447     master_params = self.cfg.GetMasterNetworkParameters()
4448     ems = self.cfg.GetUseExternalMipScript()
4449     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
4450                                                      master_params, ems)
4451     result.Raise("Could not deactivate the master IP")
4452
4453
4454 def _WaitForSync(lu, instance, disks=None, oneshot=False):
4455   """Sleep and poll for an instance's disk to sync.
4456
4457   """
4458   if not instance.disks or disks is not None and not disks:
4459     return True
4460
4461   disks = _ExpandCheckDisks(instance, disks)
4462
4463   if not oneshot:
4464     lu.proc.LogInfo("Waiting for instance %s to sync disks." % instance.name)
4465
4466   node = instance.primary_node
4467
4468   for dev in disks:
4469     lu.cfg.SetDiskID(dev, node)
4470
4471   # TODO: Convert to utils.Retry
4472
4473   retries = 0
4474   degr_retries = 10 # in seconds, as we sleep 1 second each time
4475   while True:
4476     max_time = 0
4477     done = True
4478     cumul_degraded = False
4479     rstats = lu.rpc.call_blockdev_getmirrorstatus(node, (disks, instance))
4480     msg = rstats.fail_msg
4481     if msg:
4482       lu.LogWarning("Can't get any data from node %s: %s", node, msg)
4483       retries += 1
4484       if retries >= 10:
4485         raise errors.RemoteError("Can't contact node %s for mirror data,"
4486                                  " aborting." % node)
4487       time.sleep(6)
4488       continue
4489     rstats = rstats.payload
4490     retries = 0
4491     for i, mstat in enumerate(rstats):
4492       if mstat is None:
4493         lu.LogWarning("Can't compute data for node %s/%s",
4494                            node, disks[i].iv_name)
4495         continue
4496
4497       cumul_degraded = (cumul_degraded or
4498                         (mstat.is_degraded and mstat.sync_percent is None))
4499       if mstat.sync_percent is not None:
4500         done = False
4501         if mstat.estimated_time is not None:
4502           rem_time = ("%s remaining (estimated)" %
4503                       utils.FormatSeconds(mstat.estimated_time))
4504           max_time = mstat.estimated_time
4505         else:
4506           rem_time = "no time estimate"
4507         lu.proc.LogInfo("- device %s: %5.2f%% done, %s" %
4508                         (disks[i].iv_name, mstat.sync_percent, rem_time))
4509
4510     # if we're done but degraded, let's do a few small retries, to
4511     # make sure we see a stable and not transient situation; therefore
4512     # we force restart of the loop
4513     if (done or oneshot) and cumul_degraded and degr_retries > 0:
4514       logging.info("Degraded disks found, %d retries left", degr_retries)
4515       degr_retries -= 1
4516       time.sleep(1)
4517       continue
4518
4519     if done or oneshot:
4520       break
4521
4522     time.sleep(min(60, max_time))
4523
4524   if done:
4525     lu.proc.LogInfo("Instance %s's disks are in sync." % instance.name)
4526   return not cumul_degraded
4527
4528
4529 def _BlockdevFind(lu, node, dev, instance):
4530   """Wrapper around call_blockdev_find to annotate diskparams.
4531
4532   @param lu: A reference to the lu object
4533   @param node: The node to call out
4534   @param dev: The device to find
4535   @param instance: The instance object the device belongs to
4536   @returns The result of the rpc call
4537
4538   """
4539   (disk,) = _AnnotateDiskParams(instance, [dev], lu.cfg)
4540   return lu.rpc.call_blockdev_find(node, disk)
4541
4542
4543 def _CheckDiskConsistency(lu, instance, dev, node, on_primary, ldisk=False):
4544   """Wrapper around L{_CheckDiskConsistencyInner}.
4545
4546   """
4547   (disk,) = _AnnotateDiskParams(instance, [dev], lu.cfg)
4548   return _CheckDiskConsistencyInner(lu, instance, disk, node, on_primary,
4549                                     ldisk=ldisk)
4550
4551
4552 def _CheckDiskConsistencyInner(lu, instance, dev, node, on_primary,
4553                                ldisk=False):
4554   """Check that mirrors are not degraded.
4555
4556   @attention: The device has to be annotated already.
4557
4558   The ldisk parameter, if True, will change the test from the
4559   is_degraded attribute (which represents overall non-ok status for
4560   the device(s)) to the ldisk (representing the local storage status).
4561
4562   """
4563   lu.cfg.SetDiskID(dev, node)
4564
4565   result = True
4566
4567   if on_primary or dev.AssembleOnSecondary():
4568     rstats = lu.rpc.call_blockdev_find(node, dev)
4569     msg = rstats.fail_msg
4570     if msg:
4571       lu.LogWarning("Can't find disk on node %s: %s", node, msg)
4572       result = False
4573     elif not rstats.payload:
4574       lu.LogWarning("Can't find disk on node %s", node)
4575       result = False
4576     else:
4577       if ldisk:
4578         result = result and rstats.payload.ldisk_status == constants.LDS_OKAY
4579       else:
4580         result = result and not rstats.payload.is_degraded
4581
4582   if dev.children:
4583     for child in dev.children:
4584       result = result and _CheckDiskConsistencyInner(lu, instance, child, node,
4585                                                      on_primary)
4586
4587   return result
4588
4589
4590 class LUOobCommand(NoHooksLU):
4591   """Logical unit for OOB handling.
4592
4593   """
4594   REQ_BGL = False
4595   _SKIP_MASTER = (constants.OOB_POWER_OFF, constants.OOB_POWER_CYCLE)
4596
4597   def ExpandNames(self):
4598     """Gather locks we need.
4599
4600     """
4601     if self.op.node_names:
4602       self.op.node_names = _GetWantedNodes(self, self.op.node_names)
4603       lock_names = self.op.node_names
4604     else:
4605       lock_names = locking.ALL_SET
4606
4607     self.needed_locks = {
4608       locking.LEVEL_NODE: lock_names,
4609       }
4610
4611   def CheckPrereq(self):
4612     """Check prerequisites.
4613
4614     This checks:
4615      - the node exists in the configuration
4616      - OOB is supported
4617
4618     Any errors are signaled by raising errors.OpPrereqError.
4619
4620     """
4621     self.nodes = []
4622     self.master_node = self.cfg.GetMasterNode()
4623
4624     assert self.op.power_delay >= 0.0
4625
4626     if self.op.node_names:
4627       if (self.op.command in self._SKIP_MASTER and
4628           self.master_node in self.op.node_names):
4629         master_node_obj = self.cfg.GetNodeInfo(self.master_node)
4630         master_oob_handler = _SupportsOob(self.cfg, master_node_obj)
4631
4632         if master_oob_handler:
4633           additional_text = ("run '%s %s %s' if you want to operate on the"
4634                              " master regardless") % (master_oob_handler,
4635                                                       self.op.command,
4636                                                       self.master_node)
4637         else:
4638           additional_text = "it does not support out-of-band operations"
4639
4640         raise errors.OpPrereqError(("Operating on the master node %s is not"
4641                                     " allowed for %s; %s") %
4642                                    (self.master_node, self.op.command,
4643                                     additional_text), errors.ECODE_INVAL)
4644     else:
4645       self.op.node_names = self.cfg.GetNodeList()
4646       if self.op.command in self._SKIP_MASTER:
4647         self.op.node_names.remove(self.master_node)
4648
4649     if self.op.command in self._SKIP_MASTER:
4650       assert self.master_node not in self.op.node_names
4651
4652     for (node_name, node) in self.cfg.GetMultiNodeInfo(self.op.node_names):
4653       if node is None:
4654         raise errors.OpPrereqError("Node %s not found" % node_name,
4655                                    errors.ECODE_NOENT)
4656       else:
4657         self.nodes.append(node)
4658
4659       if (not self.op.ignore_status and
4660           (self.op.command == constants.OOB_POWER_OFF and not node.offline)):
4661         raise errors.OpPrereqError(("Cannot power off node %s because it is"
4662                                     " not marked offline") % node_name,
4663                                    errors.ECODE_STATE)
4664
4665   def Exec(self, feedback_fn):
4666     """Execute OOB and return result if we expect any.
4667
4668     """
4669     master_node = self.master_node
4670     ret = []
4671
4672     for idx, node in enumerate(utils.NiceSort(self.nodes,
4673                                               key=lambda node: node.name)):
4674       node_entry = [(constants.RS_NORMAL, node.name)]
4675       ret.append(node_entry)
4676
4677       oob_program = _SupportsOob(self.cfg, node)
4678
4679       if not oob_program:
4680         node_entry.append((constants.RS_UNAVAIL, None))
4681         continue
4682
4683       logging.info("Executing out-of-band command '%s' using '%s' on %s",
4684                    self.op.command, oob_program, node.name)
4685       result = self.rpc.call_run_oob(master_node, oob_program,
4686                                      self.op.command, node.name,
4687                                      self.op.timeout)
4688
4689       if result.fail_msg:
4690         self.LogWarning("Out-of-band RPC failed on node '%s': %s",
4691                         node.name, result.fail_msg)
4692         node_entry.append((constants.RS_NODATA, None))
4693       else:
4694         try:
4695           self._CheckPayload(result)
4696         except errors.OpExecError, err:
4697           self.LogWarning("Payload returned by node '%s' is not valid: %s",
4698                           node.name, err)
4699           node_entry.append((constants.RS_NODATA, None))
4700         else:
4701           if self.op.command == constants.OOB_HEALTH:
4702             # For health we should log important events
4703             for item, status in result.payload:
4704               if status in [constants.OOB_STATUS_WARNING,
4705                             constants.OOB_STATUS_CRITICAL]:
4706                 self.LogWarning("Item '%s' on node '%s' has status '%s'",
4707                                 item, node.name, status)
4708
4709           if self.op.command == constants.OOB_POWER_ON:
4710             node.powered = True
4711           elif self.op.command == constants.OOB_POWER_OFF:
4712             node.powered = False
4713           elif self.op.command == constants.OOB_POWER_STATUS:
4714             powered = result.payload[constants.OOB_POWER_STATUS_POWERED]
4715             if powered != node.powered:
4716               logging.warning(("Recorded power state (%s) of node '%s' does not"
4717                                " match actual power state (%s)"), node.powered,
4718                               node.name, powered)
4719
4720           # For configuration changing commands we should update the node
4721           if self.op.command in (constants.OOB_POWER_ON,
4722                                  constants.OOB_POWER_OFF):
4723             self.cfg.Update(node, feedback_fn)
4724
4725           node_entry.append((constants.RS_NORMAL, result.payload))
4726
4727           if (self.op.command == constants.OOB_POWER_ON and
4728               idx < len(self.nodes) - 1):
4729             time.sleep(self.op.power_delay)
4730
4731     return ret
4732
4733   def _CheckPayload(self, result):
4734     """Checks if the payload is valid.
4735
4736     @param result: RPC result
4737     @raises errors.OpExecError: If payload is not valid
4738
4739     """
4740     errs = []
4741     if self.op.command == constants.OOB_HEALTH:
4742       if not isinstance(result.payload, list):
4743         errs.append("command 'health' is expected to return a list but got %s" %
4744                     type(result.payload))
4745       else:
4746         for item, status in result.payload:
4747           if status not in constants.OOB_STATUSES:
4748             errs.append("health item '%s' has invalid status '%s'" %
4749                         (item, status))
4750
4751     if self.op.command == constants.OOB_POWER_STATUS:
4752       if not isinstance(result.payload, dict):
4753         errs.append("power-status is expected to return a dict but got %s" %
4754                     type(result.payload))
4755
4756     if self.op.command in [
4757         constants.OOB_POWER_ON,
4758         constants.OOB_POWER_OFF,
4759         constants.OOB_POWER_CYCLE,
4760         ]:
4761       if result.payload is not None:
4762         errs.append("%s is expected to not return payload but got '%s'" %
4763                     (self.op.command, result.payload))
4764
4765     if errs:
4766       raise errors.OpExecError("Check of out-of-band payload failed due to %s" %
4767                                utils.CommaJoin(errs))
4768
4769
4770 class _OsQuery(_QueryBase):
4771   FIELDS = query.OS_FIELDS
4772
4773   def ExpandNames(self, lu):
4774     # Lock all nodes in shared mode
4775     # Temporary removal of locks, should be reverted later
4776     # TODO: reintroduce locks when they are lighter-weight
4777     lu.needed_locks = {}
4778     #self.share_locks[locking.LEVEL_NODE] = 1
4779     #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
4780
4781     # The following variables interact with _QueryBase._GetNames
4782     if self.names:
4783       self.wanted = self.names
4784     else:
4785       self.wanted = locking.ALL_SET
4786
4787     self.do_locking = self.use_locking
4788
4789   def DeclareLocks(self, lu, level):
4790     pass
4791
4792   @staticmethod
4793   def _DiagnoseByOS(rlist):
4794     """Remaps a per-node return list into an a per-os per-node dictionary
4795
4796     @param rlist: a map with node names as keys and OS objects as values
4797
4798     @rtype: dict
4799     @return: a dictionary with osnames as keys and as value another
4800         map, with nodes as keys and tuples of (path, status, diagnose,
4801         variants, parameters, api_versions) as values, eg::
4802
4803           {"debian-etch": {"node1": [(/usr/lib/..., True, "", [], []),
4804                                      (/srv/..., False, "invalid api")],
4805                            "node2": [(/srv/..., True, "", [], [])]}
4806           }
4807
4808     """
4809     all_os = {}
4810     # we build here the list of nodes that didn't fail the RPC (at RPC
4811     # level), so that nodes with a non-responding node daemon don't
4812     # make all OSes invalid
4813     good_nodes = [node_name for node_name in rlist
4814                   if not rlist[node_name].fail_msg]
4815     for node_name, nr in rlist.items():
4816       if nr.fail_msg or not nr.payload:
4817         continue
4818       for (name, path, status, diagnose, variants,
4819            params, api_versions) in nr.payload:
4820         if name not in all_os:
4821           # build a list of nodes for this os containing empty lists
4822           # for each node in node_list
4823           all_os[name] = {}
4824           for nname in good_nodes:
4825             all_os[name][nname] = []
4826         # convert params from [name, help] to (name, help)
4827         params = [tuple(v) for v in params]
4828         all_os[name][node_name].append((path, status, diagnose,
4829                                         variants, params, api_versions))
4830     return all_os
4831
4832   def _GetQueryData(self, lu):
4833     """Computes the list of nodes and their attributes.
4834
4835     """
4836     # Locking is not used
4837     assert not (compat.any(lu.glm.is_owned(level)
4838                            for level in locking.LEVELS
4839                            if level != locking.LEVEL_CLUSTER) or
4840                 self.do_locking or self.use_locking)
4841
4842     valid_nodes = [node.name
4843                    for node in lu.cfg.GetAllNodesInfo().values()
4844                    if not node.offline and node.vm_capable]
4845     pol = self._DiagnoseByOS(lu.rpc.call_os_diagnose(valid_nodes))
4846     cluster = lu.cfg.GetClusterInfo()
4847
4848     data = {}
4849
4850     for (os_name, os_data) in pol.items():
4851       info = query.OsInfo(name=os_name, valid=True, node_status=os_data,
4852                           hidden=(os_name in cluster.hidden_os),
4853                           blacklisted=(os_name in cluster.blacklisted_os))
4854
4855       variants = set()
4856       parameters = set()
4857       api_versions = set()
4858
4859       for idx, osl in enumerate(os_data.values()):
4860         info.valid = bool(info.valid and osl and osl[0][1])
4861         if not info.valid:
4862           break
4863
4864         (node_variants, node_params, node_api) = osl[0][3:6]
4865         if idx == 0:
4866           # First entry
4867           variants.update(node_variants)
4868           parameters.update(node_params)
4869           api_versions.update(node_api)
4870         else:
4871           # Filter out inconsistent values
4872           variants.intersection_update(node_variants)
4873           parameters.intersection_update(node_params)
4874           api_versions.intersection_update(node_api)
4875
4876       info.variants = list(variants)
4877       info.parameters = list(parameters)
4878       info.api_versions = list(api_versions)
4879
4880       data[os_name] = info
4881
4882     # Prepare data in requested order
4883     return [data[name] for name in self._GetNames(lu, pol.keys(), None)
4884             if name in data]
4885
4886
4887 class LUOsDiagnose(NoHooksLU):
4888   """Logical unit for OS diagnose/query.
4889
4890   """
4891   REQ_BGL = False
4892
4893   @staticmethod
4894   def _BuildFilter(fields, names):
4895     """Builds a filter for querying OSes.
4896
4897     """
4898     name_filter = qlang.MakeSimpleFilter("name", names)
4899
4900     # Legacy behaviour: Hide hidden, blacklisted or invalid OSes if the
4901     # respective field is not requested
4902     status_filter = [[qlang.OP_NOT, [qlang.OP_TRUE, fname]]
4903                      for fname in ["hidden", "blacklisted"]
4904                      if fname not in fields]
4905     if "valid" not in fields:
4906       status_filter.append([qlang.OP_TRUE, "valid"])
4907
4908     if status_filter:
4909       status_filter.insert(0, qlang.OP_AND)
4910     else:
4911       status_filter = None
4912
4913     if name_filter and status_filter:
4914       return [qlang.OP_AND, name_filter, status_filter]
4915     elif name_filter:
4916       return name_filter
4917     else:
4918       return status_filter
4919
4920   def CheckArguments(self):
4921     self.oq = _OsQuery(self._BuildFilter(self.op.output_fields, self.op.names),
4922                        self.op.output_fields, False)
4923
4924   def ExpandNames(self):
4925     self.oq.ExpandNames(self)
4926
4927   def Exec(self, feedback_fn):
4928     return self.oq.OldStyleQuery(self)
4929
4930
4931 class LUNodeRemove(LogicalUnit):
4932   """Logical unit for removing a node.
4933
4934   """
4935   HPATH = "node-remove"
4936   HTYPE = constants.HTYPE_NODE
4937
4938   def BuildHooksEnv(self):
4939     """Build hooks env.
4940
4941     """
4942     return {
4943       "OP_TARGET": self.op.node_name,
4944       "NODE_NAME": self.op.node_name,
4945       }
4946
4947   def BuildHooksNodes(self):
4948     """Build hooks nodes.
4949
4950     This doesn't run on the target node in the pre phase as a failed
4951     node would then be impossible to remove.
4952
4953     """
4954     all_nodes = self.cfg.GetNodeList()
4955     try:
4956       all_nodes.remove(self.op.node_name)
4957     except ValueError:
4958       pass
4959     return (all_nodes, all_nodes)
4960
4961   def CheckPrereq(self):
4962     """Check prerequisites.
4963
4964     This checks:
4965      - the node exists in the configuration
4966      - it does not have primary or secondary instances
4967      - it's not the master
4968
4969     Any errors are signaled by raising errors.OpPrereqError.
4970
4971     """
4972     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
4973     node = self.cfg.GetNodeInfo(self.op.node_name)
4974     assert node is not None
4975
4976     masternode = self.cfg.GetMasterNode()
4977     if node.name == masternode:
4978       raise errors.OpPrereqError("Node is the master node, failover to another"
4979                                  " node is required", errors.ECODE_INVAL)
4980
4981     for instance_name, instance in self.cfg.GetAllInstancesInfo().items():
4982       if node.name in instance.all_nodes:
4983         raise errors.OpPrereqError("Instance %s is still running on the node,"
4984                                    " please remove first" % instance_name,
4985                                    errors.ECODE_INVAL)
4986     self.op.node_name = node.name
4987     self.node = node
4988
4989   def Exec(self, feedback_fn):
4990     """Removes the node from the cluster.
4991
4992     """
4993     node = self.node
4994     logging.info("Stopping the node daemon and removing configs from node %s",
4995                  node.name)
4996
4997     modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
4998
4999     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER), \
5000       "Not owning BGL"
5001
5002     # Promote nodes to master candidate as needed
5003     _AdjustCandidatePool(self, exceptions=[node.name])
5004     self.context.RemoveNode(node.name)
5005
5006     # Run post hooks on the node before it's removed
5007     _RunPostHook(self, node.name)
5008
5009     result = self.rpc.call_node_leave_cluster(node.name, modify_ssh_setup)
5010     msg = result.fail_msg
5011     if msg:
5012       self.LogWarning("Errors encountered on the remote node while leaving"
5013                       " the cluster: %s", msg)
5014
5015     # Remove node from our /etc/hosts
5016     if self.cfg.GetClusterInfo().modify_etc_hosts:
5017       master_node = self.cfg.GetMasterNode()
5018       result = self.rpc.call_etc_hosts_modify(master_node,
5019                                               constants.ETC_HOSTS_REMOVE,
5020                                               node.name, None)
5021       result.Raise("Can't update hosts file with new host data")
5022       _RedistributeAncillaryFiles(self)
5023
5024
5025 class _NodeQuery(_QueryBase):
5026   FIELDS = query.NODE_FIELDS
5027
5028   def ExpandNames(self, lu):
5029     lu.needed_locks = {}
5030     lu.share_locks = _ShareAll()
5031
5032     if self.names:
5033       self.wanted = _GetWantedNodes(lu, self.names)
5034     else:
5035       self.wanted = locking.ALL_SET
5036
5037     self.do_locking = (self.use_locking and
5038                        query.NQ_LIVE in self.requested_data)
5039
5040     if self.do_locking:
5041       # If any non-static field is requested we need to lock the nodes
5042       lu.needed_locks[locking.LEVEL_NODE] = self.wanted
5043
5044   def DeclareLocks(self, lu, level):
5045     pass
5046
5047   def _GetQueryData(self, lu):
5048     """Computes the list of nodes and their attributes.
5049
5050     """
5051     all_info = lu.cfg.GetAllNodesInfo()
5052
5053     nodenames = self._GetNames(lu, all_info.keys(), locking.LEVEL_NODE)
5054
5055     # Gather data as requested
5056     if query.NQ_LIVE in self.requested_data:
5057       # filter out non-vm_capable nodes
5058       toquery_nodes = [name for name in nodenames if all_info[name].vm_capable]
5059
5060       node_data = lu.rpc.call_node_info(toquery_nodes, [lu.cfg.GetVGName()],
5061                                         [lu.cfg.GetHypervisorType()])
5062       live_data = dict((name, _MakeLegacyNodeInfo(nresult.payload))
5063                        for (name, nresult) in node_data.items()
5064                        if not nresult.fail_msg and nresult.payload)
5065     else:
5066       live_data = None
5067
5068     if query.NQ_INST in self.requested_data:
5069       node_to_primary = dict([(name, set()) for name in nodenames])
5070       node_to_secondary = dict([(name, set()) for name in nodenames])
5071
5072       inst_data = lu.cfg.GetAllInstancesInfo()
5073
5074       for inst in inst_data.values():
5075         if inst.primary_node in node_to_primary:
5076           node_to_primary[inst.primary_node].add(inst.name)
5077         for secnode in inst.secondary_nodes:
5078           if secnode in node_to_secondary:
5079             node_to_secondary[secnode].add(inst.name)
5080     else:
5081       node_to_primary = None
5082       node_to_secondary = None
5083
5084     if query.NQ_OOB in self.requested_data:
5085       oob_support = dict((name, bool(_SupportsOob(lu.cfg, node)))
5086                          for name, node in all_info.iteritems())
5087     else:
5088       oob_support = None
5089
5090     if query.NQ_GROUP in self.requested_data:
5091       groups = lu.cfg.GetAllNodeGroupsInfo()
5092     else:
5093       groups = {}
5094
5095     return query.NodeQueryData([all_info[name] for name in nodenames],
5096                                live_data, lu.cfg.GetMasterNode(),
5097                                node_to_primary, node_to_secondary, groups,
5098                                oob_support, lu.cfg.GetClusterInfo())
5099
5100
5101 class LUNodeQuery(NoHooksLU):
5102   """Logical unit for querying nodes.
5103
5104   """
5105   # pylint: disable=W0142
5106   REQ_BGL = False
5107
5108   def CheckArguments(self):
5109     self.nq = _NodeQuery(qlang.MakeSimpleFilter("name", self.op.names),
5110                          self.op.output_fields, self.op.use_locking)
5111
5112   def ExpandNames(self):
5113     self.nq.ExpandNames(self)
5114
5115   def DeclareLocks(self, level):
5116     self.nq.DeclareLocks(self, level)
5117
5118   def Exec(self, feedback_fn):
5119     return self.nq.OldStyleQuery(self)
5120
5121
5122 class LUNodeQueryvols(NoHooksLU):
5123   """Logical unit for getting volumes on node(s).
5124
5125   """
5126   REQ_BGL = False
5127   _FIELDS_DYNAMIC = utils.FieldSet("phys", "vg", "name", "size", "instance")
5128   _FIELDS_STATIC = utils.FieldSet("node")
5129
5130   def CheckArguments(self):
5131     _CheckOutputFields(static=self._FIELDS_STATIC,
5132                        dynamic=self._FIELDS_DYNAMIC,
5133                        selected=self.op.output_fields)
5134
5135   def ExpandNames(self):
5136     self.share_locks = _ShareAll()
5137     self.needed_locks = {}
5138
5139     if not self.op.nodes:
5140       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5141     else:
5142       self.needed_locks[locking.LEVEL_NODE] = \
5143         _GetWantedNodes(self, self.op.nodes)
5144
5145   def Exec(self, feedback_fn):
5146     """Computes the list of nodes and their attributes.
5147
5148     """
5149     nodenames = self.owned_locks(locking.LEVEL_NODE)
5150     volumes = self.rpc.call_node_volumes(nodenames)
5151
5152     ilist = self.cfg.GetAllInstancesInfo()
5153     vol2inst = _MapInstanceDisksToNodes(ilist.values())
5154
5155     output = []
5156     for node in nodenames:
5157       nresult = volumes[node]
5158       if nresult.offline:
5159         continue
5160       msg = nresult.fail_msg
5161       if msg:
5162         self.LogWarning("Can't compute volume data on node %s: %s", node, msg)
5163         continue
5164
5165       node_vols = sorted(nresult.payload,
5166                          key=operator.itemgetter("dev"))
5167
5168       for vol in node_vols:
5169         node_output = []
5170         for field in self.op.output_fields:
5171           if field == "node":
5172             val = node
5173           elif field == "phys":
5174             val = vol["dev"]
5175           elif field == "vg":
5176             val = vol["vg"]
5177           elif field == "name":
5178             val = vol["name"]
5179           elif field == "size":
5180             val = int(float(vol["size"]))
5181           elif field == "instance":
5182             val = vol2inst.get((node, vol["vg"] + "/" + vol["name"]), "-")
5183           else:
5184             raise errors.ParameterError(field)
5185           node_output.append(str(val))
5186
5187         output.append(node_output)
5188
5189     return output
5190
5191
5192 class LUNodeQueryStorage(NoHooksLU):
5193   """Logical unit for getting information on storage units on node(s).
5194
5195   """
5196   _FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
5197   REQ_BGL = False
5198
5199   def CheckArguments(self):
5200     _CheckOutputFields(static=self._FIELDS_STATIC,
5201                        dynamic=utils.FieldSet(*constants.VALID_STORAGE_FIELDS),
5202                        selected=self.op.output_fields)
5203
5204   def ExpandNames(self):
5205     self.share_locks = _ShareAll()
5206     self.needed_locks = {}
5207
5208     if self.op.nodes:
5209       self.needed_locks[locking.LEVEL_NODE] = \
5210         _GetWantedNodes(self, self.op.nodes)
5211     else:
5212       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5213
5214   def Exec(self, feedback_fn):
5215     """Computes the list of nodes and their attributes.
5216
5217     """
5218     self.nodes = self.owned_locks(locking.LEVEL_NODE)
5219
5220     # Always get name to sort by
5221     if constants.SF_NAME in self.op.output_fields:
5222       fields = self.op.output_fields[:]
5223     else:
5224       fields = [constants.SF_NAME] + self.op.output_fields
5225
5226     # Never ask for node or type as it's only known to the LU
5227     for extra in [constants.SF_NODE, constants.SF_TYPE]:
5228       while extra in fields:
5229         fields.remove(extra)
5230
5231     field_idx = dict([(name, idx) for (idx, name) in enumerate(fields)])
5232     name_idx = field_idx[constants.SF_NAME]
5233
5234     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
5235     data = self.rpc.call_storage_list(self.nodes,
5236                                       self.op.storage_type, st_args,
5237                                       self.op.name, fields)
5238
5239     result = []
5240
5241     for node in utils.NiceSort(self.nodes):
5242       nresult = data[node]
5243       if nresult.offline:
5244         continue
5245
5246       msg = nresult.fail_msg
5247       if msg:
5248         self.LogWarning("Can't get storage data from node %s: %s", node, msg)
5249         continue
5250
5251       rows = dict([(row[name_idx], row) for row in nresult.payload])
5252
5253       for name in utils.NiceSort(rows.keys()):
5254         row = rows[name]
5255
5256         out = []
5257
5258         for field in self.op.output_fields:
5259           if field == constants.SF_NODE:
5260             val = node
5261           elif field == constants.SF_TYPE:
5262             val = self.op.storage_type
5263           elif field in field_idx:
5264             val = row[field_idx[field]]
5265           else:
5266             raise errors.ParameterError(field)
5267
5268           out.append(val)
5269
5270         result.append(out)
5271
5272     return result
5273
5274
5275 class _InstanceQuery(_QueryBase):
5276   FIELDS = query.INSTANCE_FIELDS
5277
5278   def ExpandNames(self, lu):
5279     lu.needed_locks = {}
5280     lu.share_locks = _ShareAll()
5281
5282     if self.names:
5283       self.wanted = _GetWantedInstances(lu, self.names)
5284     else:
5285       self.wanted = locking.ALL_SET
5286
5287     self.do_locking = (self.use_locking and
5288                        query.IQ_LIVE in self.requested_data)
5289     if self.do_locking:
5290       lu.needed_locks[locking.LEVEL_INSTANCE] = self.wanted
5291       lu.needed_locks[locking.LEVEL_NODEGROUP] = []
5292       lu.needed_locks[locking.LEVEL_NODE] = []
5293       lu.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
5294
5295     self.do_grouplocks = (self.do_locking and
5296                           query.IQ_NODES in self.requested_data)
5297
5298   def DeclareLocks(self, lu, level):
5299     if self.do_locking:
5300       if level == locking.LEVEL_NODEGROUP and self.do_grouplocks:
5301         assert not lu.needed_locks[locking.LEVEL_NODEGROUP]
5302
5303         # Lock all groups used by instances optimistically; this requires going
5304         # via the node before it's locked, requiring verification later on
5305         lu.needed_locks[locking.LEVEL_NODEGROUP] = \
5306           set(group_uuid
5307               for instance_name in lu.owned_locks(locking.LEVEL_INSTANCE)
5308               for group_uuid in lu.cfg.GetInstanceNodeGroups(instance_name))
5309       elif level == locking.LEVEL_NODE:
5310         lu._LockInstancesNodes() # pylint: disable=W0212
5311
5312   @staticmethod
5313   def _CheckGroupLocks(lu):
5314     owned_instances = frozenset(lu.owned_locks(locking.LEVEL_INSTANCE))
5315     owned_groups = frozenset(lu.owned_locks(locking.LEVEL_NODEGROUP))
5316
5317     # Check if node groups for locked instances are still correct
5318     for instance_name in owned_instances:
5319       _CheckInstanceNodeGroups(lu.cfg, instance_name, owned_groups)
5320
5321   def _GetQueryData(self, lu):
5322     """Computes the list of instances and their attributes.
5323
5324     """
5325     if self.do_grouplocks:
5326       self._CheckGroupLocks(lu)
5327
5328     cluster = lu.cfg.GetClusterInfo()
5329     all_info = lu.cfg.GetAllInstancesInfo()
5330
5331     instance_names = self._GetNames(lu, all_info.keys(), locking.LEVEL_INSTANCE)
5332
5333     instance_list = [all_info[name] for name in instance_names]
5334     nodes = frozenset(itertools.chain(*(inst.all_nodes
5335                                         for inst in instance_list)))
5336     hv_list = list(set([inst.hypervisor for inst in instance_list]))
5337     bad_nodes = []
5338     offline_nodes = []
5339     wrongnode_inst = set()
5340
5341     # Gather data as requested
5342     if self.requested_data & set([query.IQ_LIVE, query.IQ_CONSOLE]):
5343       live_data = {}
5344       node_data = lu.rpc.call_all_instances_info(nodes, hv_list)
5345       for name in nodes:
5346         result = node_data[name]
5347         if result.offline:
5348           # offline nodes will be in both lists
5349           assert result.fail_msg
5350           offline_nodes.append(name)
5351         if result.fail_msg:
5352           bad_nodes.append(name)
5353         elif result.payload:
5354           for inst in result.payload:
5355             if inst in all_info:
5356               if all_info[inst].primary_node == name:
5357                 live_data.update(result.payload)
5358               else:
5359                 wrongnode_inst.add(inst)
5360             else:
5361               # orphan instance; we don't list it here as we don't
5362               # handle this case yet in the output of instance listing
5363               logging.warning("Orphan instance '%s' found on node %s",
5364                               inst, name)
5365         # else no instance is alive
5366     else:
5367       live_data = {}
5368
5369     if query.IQ_DISKUSAGE in self.requested_data:
5370       disk_usage = dict((inst.name,
5371                          _ComputeDiskSize(inst.disk_template,
5372                                           [{constants.IDISK_SIZE: disk.size}
5373                                            for disk in inst.disks]))
5374                         for inst in instance_list)
5375     else:
5376       disk_usage = None
5377
5378     if query.IQ_CONSOLE in self.requested_data:
5379       consinfo = {}
5380       for inst in instance_list:
5381         if inst.name in live_data:
5382           # Instance is running
5383           consinfo[inst.name] = _GetInstanceConsole(cluster, inst)
5384         else:
5385           consinfo[inst.name] = None
5386       assert set(consinfo.keys()) == set(instance_names)
5387     else:
5388       consinfo = None
5389
5390     if query.IQ_NODES in self.requested_data:
5391       node_names = set(itertools.chain(*map(operator.attrgetter("all_nodes"),
5392                                             instance_list)))
5393       nodes = dict(lu.cfg.GetMultiNodeInfo(node_names))
5394       groups = dict((uuid, lu.cfg.GetNodeGroup(uuid))
5395                     for uuid in set(map(operator.attrgetter("group"),
5396                                         nodes.values())))
5397     else:
5398       nodes = None
5399       groups = None
5400
5401     return query.InstanceQueryData(instance_list, lu.cfg.GetClusterInfo(),
5402                                    disk_usage, offline_nodes, bad_nodes,
5403                                    live_data, wrongnode_inst, consinfo,
5404                                    nodes, groups)
5405
5406
5407 class LUQuery(NoHooksLU):
5408   """Query for resources/items of a certain kind.
5409
5410   """
5411   # pylint: disable=W0142
5412   REQ_BGL = False
5413
5414   def CheckArguments(self):
5415     qcls = _GetQueryImplementation(self.op.what)
5416
5417     self.impl = qcls(self.op.qfilter, self.op.fields, self.op.use_locking)
5418
5419   def ExpandNames(self):
5420     self.impl.ExpandNames(self)
5421
5422   def DeclareLocks(self, level):
5423     self.impl.DeclareLocks(self, level)
5424
5425   def Exec(self, feedback_fn):
5426     return self.impl.NewStyleQuery(self)
5427
5428
5429 class LUQueryFields(NoHooksLU):
5430   """Query for resources/items of a certain kind.
5431
5432   """
5433   # pylint: disable=W0142
5434   REQ_BGL = False
5435
5436   def CheckArguments(self):
5437     self.qcls = _GetQueryImplementation(self.op.what)
5438
5439   def ExpandNames(self):
5440     self.needed_locks = {}
5441
5442   def Exec(self, feedback_fn):
5443     return query.QueryFields(self.qcls.FIELDS, self.op.fields)
5444
5445
5446 class LUNodeModifyStorage(NoHooksLU):
5447   """Logical unit for modifying a storage volume on a node.
5448
5449   """
5450   REQ_BGL = False
5451
5452   def CheckArguments(self):
5453     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5454
5455     storage_type = self.op.storage_type
5456
5457     try:
5458       modifiable = constants.MODIFIABLE_STORAGE_FIELDS[storage_type]
5459     except KeyError:
5460       raise errors.OpPrereqError("Storage units of type '%s' can not be"
5461                                  " modified" % storage_type,
5462                                  errors.ECODE_INVAL)
5463
5464     diff = set(self.op.changes.keys()) - modifiable
5465     if diff:
5466       raise errors.OpPrereqError("The following fields can not be modified for"
5467                                  " storage units of type '%s': %r" %
5468                                  (storage_type, list(diff)),
5469                                  errors.ECODE_INVAL)
5470
5471   def ExpandNames(self):
5472     self.needed_locks = {
5473       locking.LEVEL_NODE: self.op.node_name,
5474       }
5475
5476   def Exec(self, feedback_fn):
5477     """Computes the list of nodes and their attributes.
5478
5479     """
5480     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
5481     result = self.rpc.call_storage_modify(self.op.node_name,
5482                                           self.op.storage_type, st_args,
5483                                           self.op.name, self.op.changes)
5484     result.Raise("Failed to modify storage unit '%s' on %s" %
5485                  (self.op.name, self.op.node_name))
5486
5487
5488 class LUNodeAdd(LogicalUnit):
5489   """Logical unit for adding node to the cluster.
5490
5491   """
5492   HPATH = "node-add"
5493   HTYPE = constants.HTYPE_NODE
5494   _NFLAGS = ["master_capable", "vm_capable"]
5495
5496   def CheckArguments(self):
5497     self.primary_ip_family = self.cfg.GetPrimaryIPFamily()
5498     # validate/normalize the node name
5499     self.hostname = netutils.GetHostname(name=self.op.node_name,
5500                                          family=self.primary_ip_family)
5501     self.op.node_name = self.hostname.name
5502
5503     if self.op.readd and self.op.node_name == self.cfg.GetMasterNode():
5504       raise errors.OpPrereqError("Cannot readd the master node",
5505                                  errors.ECODE_STATE)
5506
5507     if self.op.readd and self.op.group:
5508       raise errors.OpPrereqError("Cannot pass a node group when a node is"
5509                                  " being readded", errors.ECODE_INVAL)
5510
5511   def BuildHooksEnv(self):
5512     """Build hooks env.
5513
5514     This will run on all nodes before, and on all nodes + the new node after.
5515
5516     """
5517     return {
5518       "OP_TARGET": self.op.node_name,
5519       "NODE_NAME": self.op.node_name,
5520       "NODE_PIP": self.op.primary_ip,
5521       "NODE_SIP": self.op.secondary_ip,
5522       "MASTER_CAPABLE": str(self.op.master_capable),
5523       "VM_CAPABLE": str(self.op.vm_capable),
5524       }
5525
5526   def BuildHooksNodes(self):
5527     """Build hooks nodes.
5528
5529     """
5530     # Exclude added node
5531     pre_nodes = list(set(self.cfg.GetNodeList()) - set([self.op.node_name]))
5532     post_nodes = pre_nodes + [self.op.node_name, ]
5533
5534     return (pre_nodes, post_nodes)
5535
5536   def CheckPrereq(self):
5537     """Check prerequisites.
5538
5539     This checks:
5540      - the new node is not already in the config
5541      - it is resolvable
5542      - its parameters (single/dual homed) matches the cluster
5543
5544     Any errors are signaled by raising errors.OpPrereqError.
5545
5546     """
5547     cfg = self.cfg
5548     hostname = self.hostname
5549     node = hostname.name
5550     primary_ip = self.op.primary_ip = hostname.ip
5551     if self.op.secondary_ip is None:
5552       if self.primary_ip_family == netutils.IP6Address.family:
5553         raise errors.OpPrereqError("When using a IPv6 primary address, a valid"
5554                                    " IPv4 address must be given as secondary",
5555                                    errors.ECODE_INVAL)
5556       self.op.secondary_ip = primary_ip
5557
5558     secondary_ip = self.op.secondary_ip
5559     if not netutils.IP4Address.IsValid(secondary_ip):
5560       raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
5561                                  " address" % secondary_ip, errors.ECODE_INVAL)
5562
5563     node_list = cfg.GetNodeList()
5564     if not self.op.readd and node in node_list:
5565       raise errors.OpPrereqError("Node %s is already in the configuration" %
5566                                  node, errors.ECODE_EXISTS)
5567     elif self.op.readd and node not in node_list:
5568       raise errors.OpPrereqError("Node %s is not in the configuration" % node,
5569                                  errors.ECODE_NOENT)
5570
5571     self.changed_primary_ip = False
5572
5573     for existing_node_name, existing_node in cfg.GetMultiNodeInfo(node_list):
5574       if self.op.readd and node == existing_node_name:
5575         if existing_node.secondary_ip != secondary_ip:
5576           raise errors.OpPrereqError("Readded node doesn't have the same IP"
5577                                      " address configuration as before",
5578                                      errors.ECODE_INVAL)
5579         if existing_node.primary_ip != primary_ip:
5580           self.changed_primary_ip = True
5581
5582         continue
5583
5584       if (existing_node.primary_ip == primary_ip or
5585           existing_node.secondary_ip == primary_ip or
5586           existing_node.primary_ip == secondary_ip or
5587           existing_node.secondary_ip == secondary_ip):
5588         raise errors.OpPrereqError("New node ip address(es) conflict with"
5589                                    " existing node %s" % existing_node.name,
5590                                    errors.ECODE_NOTUNIQUE)
5591
5592     # After this 'if' block, None is no longer a valid value for the
5593     # _capable op attributes
5594     if self.op.readd:
5595       old_node = self.cfg.GetNodeInfo(node)
5596       assert old_node is not None, "Can't retrieve locked node %s" % node
5597       for attr in self._NFLAGS:
5598         if getattr(self.op, attr) is None:
5599           setattr(self.op, attr, getattr(old_node, attr))
5600     else:
5601       for attr in self._NFLAGS:
5602         if getattr(self.op, attr) is None:
5603           setattr(self.op, attr, True)
5604
5605     if self.op.readd and not self.op.vm_capable:
5606       pri, sec = cfg.GetNodeInstances(node)
5607       if pri or sec:
5608         raise errors.OpPrereqError("Node %s being re-added with vm_capable"
5609                                    " flag set to false, but it already holds"
5610                                    " instances" % node,
5611                                    errors.ECODE_STATE)
5612
5613     # check that the type of the node (single versus dual homed) is the
5614     # same as for the master
5615     myself = cfg.GetNodeInfo(self.cfg.GetMasterNode())
5616     master_singlehomed = myself.secondary_ip == myself.primary_ip
5617     newbie_singlehomed = secondary_ip == primary_ip
5618     if master_singlehomed != newbie_singlehomed:
5619       if master_singlehomed:
5620         raise errors.OpPrereqError("The master has no secondary ip but the"
5621                                    " new node has one",
5622                                    errors.ECODE_INVAL)
5623       else:
5624         raise errors.OpPrereqError("The master has a secondary ip but the"
5625                                    " new node doesn't have one",
5626                                    errors.ECODE_INVAL)
5627
5628     # checks reachability
5629     if not netutils.TcpPing(primary_ip, constants.DEFAULT_NODED_PORT):
5630       raise errors.OpPrereqError("Node not reachable by ping",
5631                                  errors.ECODE_ENVIRON)
5632
5633     if not newbie_singlehomed:
5634       # check reachability from my secondary ip to newbie's secondary ip
5635       if not netutils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
5636                            source=myself.secondary_ip):
5637         raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
5638                                    " based ping to node daemon port",
5639                                    errors.ECODE_ENVIRON)
5640
5641     if self.op.readd:
5642       exceptions = [node]
5643     else:
5644       exceptions = []
5645
5646     if self.op.master_capable:
5647       self.master_candidate = _DecideSelfPromotion(self, exceptions=exceptions)
5648     else:
5649       self.master_candidate = False
5650
5651     if self.op.readd:
5652       self.new_node = old_node
5653     else:
5654       node_group = cfg.LookupNodeGroup(self.op.group)
5655       self.new_node = objects.Node(name=node,
5656                                    primary_ip=primary_ip,
5657                                    secondary_ip=secondary_ip,
5658                                    master_candidate=self.master_candidate,
5659                                    offline=False, drained=False,
5660                                    group=node_group)
5661
5662     if self.op.ndparams:
5663       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
5664
5665     if self.op.hv_state:
5666       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None)
5667
5668     if self.op.disk_state:
5669       self.new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state, None)
5670
5671     # TODO: If we need to have multiple DnsOnlyRunner we probably should make
5672     #       it a property on the base class.
5673     result = rpc.DnsOnlyRunner().call_version([node])[node]
5674     result.Raise("Can't get version information from node %s" % node)
5675     if constants.PROTOCOL_VERSION == result.payload:
5676       logging.info("Communication to node %s fine, sw version %s match",
5677                    node, result.payload)
5678     else:
5679       raise errors.OpPrereqError("Version mismatch master version %s,"
5680                                  " node version %s" %
5681                                  (constants.PROTOCOL_VERSION, result.payload),
5682                                  errors.ECODE_ENVIRON)
5683
5684   def Exec(self, feedback_fn):
5685     """Adds the new node to the cluster.
5686
5687     """
5688     new_node = self.new_node
5689     node = new_node.name
5690
5691     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER), \
5692       "Not owning BGL"
5693
5694     # We adding a new node so we assume it's powered
5695     new_node.powered = True
5696
5697     # for re-adds, reset the offline/drained/master-candidate flags;
5698     # we need to reset here, otherwise offline would prevent RPC calls
5699     # later in the procedure; this also means that if the re-add
5700     # fails, we are left with a non-offlined, broken node
5701     if self.op.readd:
5702       new_node.drained = new_node.offline = False # pylint: disable=W0201
5703       self.LogInfo("Readding a node, the offline/drained flags were reset")
5704       # if we demote the node, we do cleanup later in the procedure
5705       new_node.master_candidate = self.master_candidate
5706       if self.changed_primary_ip:
5707         new_node.primary_ip = self.op.primary_ip
5708
5709     # copy the master/vm_capable flags
5710     for attr in self._NFLAGS:
5711       setattr(new_node, attr, getattr(self.op, attr))
5712
5713     # notify the user about any possible mc promotion
5714     if new_node.master_candidate:
5715       self.LogInfo("Node will be a master candidate")
5716
5717     if self.op.ndparams:
5718       new_node.ndparams = self.op.ndparams
5719     else:
5720       new_node.ndparams = {}
5721
5722     if self.op.hv_state:
5723       new_node.hv_state_static = self.new_hv_state
5724
5725     if self.op.disk_state:
5726       new_node.disk_state_static = self.new_disk_state
5727
5728     # Add node to our /etc/hosts, and add key to known_hosts
5729     if self.cfg.GetClusterInfo().modify_etc_hosts:
5730       master_node = self.cfg.GetMasterNode()
5731       result = self.rpc.call_etc_hosts_modify(master_node,
5732                                               constants.ETC_HOSTS_ADD,
5733                                               self.hostname.name,
5734                                               self.hostname.ip)
5735       result.Raise("Can't update hosts file with new host data")
5736
5737     if new_node.secondary_ip != new_node.primary_ip:
5738       _CheckNodeHasSecondaryIP(self, new_node.name, new_node.secondary_ip,
5739                                False)
5740
5741     node_verify_list = [self.cfg.GetMasterNode()]
5742     node_verify_param = {
5743       constants.NV_NODELIST: ([node], {}),
5744       # TODO: do a node-net-test as well?
5745     }
5746
5747     result = self.rpc.call_node_verify(node_verify_list, node_verify_param,
5748                                        self.cfg.GetClusterName())
5749     for verifier in node_verify_list:
5750       result[verifier].Raise("Cannot communicate with node %s" % verifier)
5751       nl_payload = result[verifier].payload[constants.NV_NODELIST]
5752       if nl_payload:
5753         for failed in nl_payload:
5754           feedback_fn("ssh/hostname verification failed"
5755                       " (checking from %s): %s" %
5756                       (verifier, nl_payload[failed]))
5757         raise errors.OpExecError("ssh/hostname verification failed")
5758
5759     if self.op.readd:
5760       _RedistributeAncillaryFiles(self)
5761       self.context.ReaddNode(new_node)
5762       # make sure we redistribute the config
5763       self.cfg.Update(new_node, feedback_fn)
5764       # and make sure the new node will not have old files around
5765       if not new_node.master_candidate:
5766         result = self.rpc.call_node_demote_from_mc(new_node.name)
5767         msg = result.fail_msg
5768         if msg:
5769           self.LogWarning("Node failed to demote itself from master"
5770                           " candidate status: %s" % msg)
5771     else:
5772       _RedistributeAncillaryFiles(self, additional_nodes=[node],
5773                                   additional_vm=self.op.vm_capable)
5774       self.context.AddNode(new_node, self.proc.GetECId())
5775
5776
5777 class LUNodeSetParams(LogicalUnit):
5778   """Modifies the parameters of a node.
5779
5780   @cvar _F2R: a dictionary from tuples of flags (mc, drained, offline)
5781       to the node role (as _ROLE_*)
5782   @cvar _R2F: a dictionary from node role to tuples of flags
5783   @cvar _FLAGS: a list of attribute names corresponding to the flags
5784
5785   """
5786   HPATH = "node-modify"
5787   HTYPE = constants.HTYPE_NODE
5788   REQ_BGL = False
5789   (_ROLE_CANDIDATE, _ROLE_DRAINED, _ROLE_OFFLINE, _ROLE_REGULAR) = range(4)
5790   _F2R = {
5791     (True, False, False): _ROLE_CANDIDATE,
5792     (False, True, False): _ROLE_DRAINED,
5793     (False, False, True): _ROLE_OFFLINE,
5794     (False, False, False): _ROLE_REGULAR,
5795     }
5796   _R2F = dict((v, k) for k, v in _F2R.items())
5797   _FLAGS = ["master_candidate", "drained", "offline"]
5798
5799   def CheckArguments(self):
5800     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5801     all_mods = [self.op.offline, self.op.master_candidate, self.op.drained,
5802                 self.op.master_capable, self.op.vm_capable,
5803                 self.op.secondary_ip, self.op.ndparams, self.op.hv_state,
5804                 self.op.disk_state]
5805     if all_mods.count(None) == len(all_mods):
5806       raise errors.OpPrereqError("Please pass at least one modification",
5807                                  errors.ECODE_INVAL)
5808     if all_mods.count(True) > 1:
5809       raise errors.OpPrereqError("Can't set the node into more than one"
5810                                  " state at the same time",
5811                                  errors.ECODE_INVAL)
5812
5813     # Boolean value that tells us whether we might be demoting from MC
5814     self.might_demote = (self.op.master_candidate == False or
5815                          self.op.offline == True or
5816                          self.op.drained == True or
5817                          self.op.master_capable == False)
5818
5819     if self.op.secondary_ip:
5820       if not netutils.IP4Address.IsValid(self.op.secondary_ip):
5821         raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
5822                                    " address" % self.op.secondary_ip,
5823                                    errors.ECODE_INVAL)
5824
5825     self.lock_all = self.op.auto_promote and self.might_demote
5826     self.lock_instances = self.op.secondary_ip is not None
5827
5828   def _InstanceFilter(self, instance):
5829     """Filter for getting affected instances.
5830
5831     """
5832     return (instance.disk_template in constants.DTS_INT_MIRROR and
5833             self.op.node_name in instance.all_nodes)
5834
5835   def ExpandNames(self):
5836     if self.lock_all:
5837       self.needed_locks = {locking.LEVEL_NODE: locking.ALL_SET}
5838     else:
5839       self.needed_locks = {locking.LEVEL_NODE: self.op.node_name}
5840
5841     # Since modifying a node can have severe effects on currently running
5842     # operations the resource lock is at least acquired in shared mode
5843     self.needed_locks[locking.LEVEL_NODE_RES] = \
5844       self.needed_locks[locking.LEVEL_NODE]
5845
5846     # Get node resource and instance locks in shared mode; they are not used
5847     # for anything but read-only access
5848     self.share_locks[locking.LEVEL_NODE_RES] = 1
5849     self.share_locks[locking.LEVEL_INSTANCE] = 1
5850
5851     if self.lock_instances:
5852       self.needed_locks[locking.LEVEL_INSTANCE] = \
5853         frozenset(self.cfg.GetInstancesInfoByFilter(self._InstanceFilter))
5854
5855   def BuildHooksEnv(self):
5856     """Build hooks env.
5857
5858     This runs on the master node.
5859
5860     """
5861     return {
5862       "OP_TARGET": self.op.node_name,
5863       "MASTER_CANDIDATE": str(self.op.master_candidate),
5864       "OFFLINE": str(self.op.offline),
5865       "DRAINED": str(self.op.drained),
5866       "MASTER_CAPABLE": str(self.op.master_capable),
5867       "VM_CAPABLE": str(self.op.vm_capable),
5868       }
5869
5870   def BuildHooksNodes(self):
5871     """Build hooks nodes.
5872
5873     """
5874     nl = [self.cfg.GetMasterNode(), self.op.node_name]
5875     return (nl, nl)
5876
5877   def CheckPrereq(self):
5878     """Check prerequisites.
5879
5880     This only checks the instance list against the existing names.
5881
5882     """
5883     node = self.node = self.cfg.GetNodeInfo(self.op.node_name)
5884
5885     if self.lock_instances:
5886       affected_instances = \
5887         self.cfg.GetInstancesInfoByFilter(self._InstanceFilter)
5888
5889       # Verify instance locks
5890       owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
5891       wanted_instances = frozenset(affected_instances.keys())
5892       if wanted_instances - owned_instances:
5893         raise errors.OpPrereqError("Instances affected by changing node %s's"
5894                                    " secondary IP address have changed since"
5895                                    " locks were acquired, wanted '%s', have"
5896                                    " '%s'; retry the operation" %
5897                                    (self.op.node_name,
5898                                     utils.CommaJoin(wanted_instances),
5899                                     utils.CommaJoin(owned_instances)),
5900                                    errors.ECODE_STATE)
5901     else:
5902       affected_instances = None
5903
5904     if (self.op.master_candidate is not None or
5905         self.op.drained is not None or
5906         self.op.offline is not None):
5907       # we can't change the master's node flags
5908       if self.op.node_name == self.cfg.GetMasterNode():
5909         raise errors.OpPrereqError("The master role can be changed"
5910                                    " only via master-failover",
5911                                    errors.ECODE_INVAL)
5912
5913     if self.op.master_candidate and not node.master_capable:
5914       raise errors.OpPrereqError("Node %s is not master capable, cannot make"
5915                                  " it a master candidate" % node.name,
5916                                  errors.ECODE_STATE)
5917
5918     if self.op.vm_capable == False:
5919       (ipri, isec) = self.cfg.GetNodeInstances(self.op.node_name)
5920       if ipri or isec:
5921         raise errors.OpPrereqError("Node %s hosts instances, cannot unset"
5922                                    " the vm_capable flag" % node.name,
5923                                    errors.ECODE_STATE)
5924
5925     if node.master_candidate and self.might_demote and not self.lock_all:
5926       assert not self.op.auto_promote, "auto_promote set but lock_all not"
5927       # check if after removing the current node, we're missing master
5928       # candidates
5929       (mc_remaining, mc_should, _) = \
5930           self.cfg.GetMasterCandidateStats(exceptions=[node.name])
5931       if mc_remaining < mc_should:
5932         raise errors.OpPrereqError("Not enough master candidates, please"
5933                                    " pass auto promote option to allow"
5934                                    " promotion (--auto-promote or RAPI"
5935                                    " auto_promote=True)", errors.ECODE_STATE)
5936
5937     self.old_flags = old_flags = (node.master_candidate,
5938                                   node.drained, node.offline)
5939     assert old_flags in self._F2R, "Un-handled old flags %s" % str(old_flags)
5940     self.old_role = old_role = self._F2R[old_flags]
5941
5942     # Check for ineffective changes
5943     for attr in self._FLAGS:
5944       if (getattr(self.op, attr) == False and getattr(node, attr) == False):
5945         self.LogInfo("Ignoring request to unset flag %s, already unset", attr)
5946         setattr(self.op, attr, None)
5947
5948     # Past this point, any flag change to False means a transition
5949     # away from the respective state, as only real changes are kept
5950
5951     # TODO: We might query the real power state if it supports OOB
5952     if _SupportsOob(self.cfg, node):
5953       if self.op.offline is False and not (node.powered or
5954                                            self.op.powered == True):
5955         raise errors.OpPrereqError(("Node %s needs to be turned on before its"
5956                                     " offline status can be reset") %
5957                                    self.op.node_name)
5958     elif self.op.powered is not None:
5959       raise errors.OpPrereqError(("Unable to change powered state for node %s"
5960                                   " as it does not support out-of-band"
5961                                   " handling") % self.op.node_name)
5962
5963     # If we're being deofflined/drained, we'll MC ourself if needed
5964     if (self.op.drained == False or self.op.offline == False or
5965         (self.op.master_capable and not node.master_capable)):
5966       if _DecideSelfPromotion(self):
5967         self.op.master_candidate = True
5968         self.LogInfo("Auto-promoting node to master candidate")
5969
5970     # If we're no longer master capable, we'll demote ourselves from MC
5971     if self.op.master_capable == False and node.master_candidate:
5972       self.LogInfo("Demoting from master candidate")
5973       self.op.master_candidate = False
5974
5975     # Compute new role
5976     assert [getattr(self.op, attr) for attr in self._FLAGS].count(True) <= 1
5977     if self.op.master_candidate:
5978       new_role = self._ROLE_CANDIDATE
5979     elif self.op.drained:
5980       new_role = self._ROLE_DRAINED
5981     elif self.op.offline:
5982       new_role = self._ROLE_OFFLINE
5983     elif False in [self.op.master_candidate, self.op.drained, self.op.offline]:
5984       # False is still in new flags, which means we're un-setting (the
5985       # only) True flag
5986       new_role = self._ROLE_REGULAR
5987     else: # no new flags, nothing, keep old role
5988       new_role = old_role
5989
5990     self.new_role = new_role
5991
5992     if old_role == self._ROLE_OFFLINE and new_role != old_role:
5993       # Trying to transition out of offline status
5994       result = self.rpc.call_version([node.name])[node.name]
5995       if result.fail_msg:
5996         raise errors.OpPrereqError("Node %s is being de-offlined but fails"
5997                                    " to report its version: %s" %
5998                                    (node.name, result.fail_msg),
5999                                    errors.ECODE_STATE)
6000       else:
6001         self.LogWarning("Transitioning node from offline to online state"
6002                         " without using re-add. Please make sure the node"
6003                         " is healthy!")
6004
6005     # When changing the secondary ip, verify if this is a single-homed to
6006     # multi-homed transition or vice versa, and apply the relevant
6007     # restrictions.
6008     if self.op.secondary_ip:
6009       # Ok even without locking, because this can't be changed by any LU
6010       master = self.cfg.GetNodeInfo(self.cfg.GetMasterNode())
6011       master_singlehomed = master.secondary_ip == master.primary_ip
6012       if master_singlehomed and self.op.secondary_ip != node.primary_ip:
6013         if self.op.force and node.name == master.name:
6014           self.LogWarning("Transitioning from single-homed to multi-homed"
6015                           " cluster. All nodes will require a secondary ip.")
6016         else:
6017           raise errors.OpPrereqError("Changing the secondary ip on a"
6018                                      " single-homed cluster requires the"
6019                                      " --force option to be passed, and the"
6020                                      " target node to be the master",
6021                                      errors.ECODE_INVAL)
6022       elif not master_singlehomed and self.op.secondary_ip == node.primary_ip:
6023         if self.op.force and node.name == master.name:
6024           self.LogWarning("Transitioning from multi-homed to single-homed"
6025                           " cluster. Secondary IPs will have to be removed.")
6026         else:
6027           raise errors.OpPrereqError("Cannot set the secondary IP to be the"
6028                                      " same as the primary IP on a multi-homed"
6029                                      " cluster, unless the --force option is"
6030                                      " passed, and the target node is the"
6031                                      " master", errors.ECODE_INVAL)
6032
6033       assert not (frozenset(affected_instances) -
6034                   self.owned_locks(locking.LEVEL_INSTANCE))
6035
6036       if node.offline:
6037         if affected_instances:
6038           raise errors.OpPrereqError("Cannot change secondary IP address:"
6039                                      " offline node has instances (%s)"
6040                                      " configured to use it" %
6041                                      utils.CommaJoin(affected_instances.keys()))
6042       else:
6043         # On online nodes, check that no instances are running, and that
6044         # the node has the new ip and we can reach it.
6045         for instance in affected_instances.values():
6046           _CheckInstanceState(self, instance, INSTANCE_DOWN,
6047                               msg="cannot change secondary ip")
6048
6049         _CheckNodeHasSecondaryIP(self, node.name, self.op.secondary_ip, True)
6050         if master.name != node.name:
6051           # check reachability from master secondary ip to new secondary ip
6052           if not netutils.TcpPing(self.op.secondary_ip,
6053                                   constants.DEFAULT_NODED_PORT,
6054                                   source=master.secondary_ip):
6055             raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
6056                                        " based ping to node daemon port",
6057                                        errors.ECODE_ENVIRON)
6058
6059     if self.op.ndparams:
6060       new_ndparams = _GetUpdatedParams(self.node.ndparams, self.op.ndparams)
6061       utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES)
6062       self.new_ndparams = new_ndparams
6063
6064     if self.op.hv_state:
6065       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
6066                                                  self.node.hv_state_static)
6067
6068     if self.op.disk_state:
6069       self.new_disk_state = \
6070         _MergeAndVerifyDiskState(self.op.disk_state,
6071                                  self.node.disk_state_static)
6072
6073   def Exec(self, feedback_fn):
6074     """Modifies a node.
6075
6076     """
6077     node = self.node
6078     old_role = self.old_role
6079     new_role = self.new_role
6080
6081     result = []
6082
6083     if self.op.ndparams:
6084       node.ndparams = self.new_ndparams
6085
6086     if self.op.powered is not None:
6087       node.powered = self.op.powered
6088
6089     if self.op.hv_state:
6090       node.hv_state_static = self.new_hv_state
6091
6092     if self.op.disk_state:
6093       node.disk_state_static = self.new_disk_state
6094
6095     for attr in ["master_capable", "vm_capable"]:
6096       val = getattr(self.op, attr)
6097       if val is not None:
6098         setattr(node, attr, val)
6099         result.append((attr, str(val)))
6100
6101     if new_role != old_role:
6102       # Tell the node to demote itself, if no longer MC and not offline
6103       if old_role == self._ROLE_CANDIDATE and new_role != self._ROLE_OFFLINE:
6104         msg = self.rpc.call_node_demote_from_mc(node.name).fail_msg
6105         if msg:
6106           self.LogWarning("Node failed to demote itself: %s", msg)
6107
6108       new_flags = self._R2F[new_role]
6109       for of, nf, desc in zip(self.old_flags, new_flags, self._FLAGS):
6110         if of != nf:
6111           result.append((desc, str(nf)))
6112       (node.master_candidate, node.drained, node.offline) = new_flags
6113
6114       # we locked all nodes, we adjust the CP before updating this node
6115       if self.lock_all:
6116         _AdjustCandidatePool(self, [node.name])
6117
6118     if self.op.secondary_ip:
6119       node.secondary_ip = self.op.secondary_ip
6120       result.append(("secondary_ip", self.op.secondary_ip))
6121
6122     # this will trigger configuration file update, if needed
6123     self.cfg.Update(node, feedback_fn)
6124
6125     # this will trigger job queue propagation or cleanup if the mc
6126     # flag changed
6127     if [old_role, new_role].count(self._ROLE_CANDIDATE) == 1:
6128       self.context.ReaddNode(node)
6129
6130     return result
6131
6132
6133 class LUNodePowercycle(NoHooksLU):
6134   """Powercycles a node.
6135
6136   """
6137   REQ_BGL = False
6138
6139   def CheckArguments(self):
6140     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
6141     if self.op.node_name == self.cfg.GetMasterNode() and not self.op.force:
6142       raise errors.OpPrereqError("The node is the master and the force"
6143                                  " parameter was not set",
6144                                  errors.ECODE_INVAL)
6145
6146   def ExpandNames(self):
6147     """Locking for PowercycleNode.
6148
6149     This is a last-resort option and shouldn't block on other
6150     jobs. Therefore, we grab no locks.
6151
6152     """
6153     self.needed_locks = {}
6154
6155   def Exec(self, feedback_fn):
6156     """Reboots a node.
6157
6158     """
6159     result = self.rpc.call_node_powercycle(self.op.node_name,
6160                                            self.cfg.GetHypervisorType())
6161     result.Raise("Failed to schedule the reboot")
6162     return result.payload
6163
6164
6165 class LUClusterQuery(NoHooksLU):
6166   """Query cluster configuration.
6167
6168   """
6169   REQ_BGL = False
6170
6171   def ExpandNames(self):
6172     self.needed_locks = {}
6173
6174   def Exec(self, feedback_fn):
6175     """Return cluster config.
6176
6177     """
6178     cluster = self.cfg.GetClusterInfo()
6179     os_hvp = {}
6180
6181     # Filter just for enabled hypervisors
6182     for os_name, hv_dict in cluster.os_hvp.items():
6183       os_hvp[os_name] = {}
6184       for hv_name, hv_params in hv_dict.items():
6185         if hv_name in cluster.enabled_hypervisors:
6186           os_hvp[os_name][hv_name] = hv_params
6187
6188     # Convert ip_family to ip_version
6189     primary_ip_version = constants.IP4_VERSION
6190     if cluster.primary_ip_family == netutils.IP6Address.family:
6191       primary_ip_version = constants.IP6_VERSION
6192
6193     result = {
6194       "software_version": constants.RELEASE_VERSION,
6195       "protocol_version": constants.PROTOCOL_VERSION,
6196       "config_version": constants.CONFIG_VERSION,
6197       "os_api_version": max(constants.OS_API_VERSIONS),
6198       "export_version": constants.EXPORT_VERSION,
6199       "architecture": runtime.GetArchInfo(),
6200       "name": cluster.cluster_name,
6201       "master": cluster.master_node,
6202       "default_hypervisor": cluster.primary_hypervisor,
6203       "enabled_hypervisors": cluster.enabled_hypervisors,
6204       "hvparams": dict([(hypervisor_name, cluster.hvparams[hypervisor_name])
6205                         for hypervisor_name in cluster.enabled_hypervisors]),
6206       "os_hvp": os_hvp,
6207       "beparams": cluster.beparams,
6208       "osparams": cluster.osparams,
6209       "ipolicy": cluster.ipolicy,
6210       "nicparams": cluster.nicparams,
6211       "ndparams": cluster.ndparams,
6212       "diskparams": cluster.diskparams,
6213       "candidate_pool_size": cluster.candidate_pool_size,
6214       "master_netdev": cluster.master_netdev,
6215       "master_netmask": cluster.master_netmask,
6216       "use_external_mip_script": cluster.use_external_mip_script,
6217       "volume_group_name": cluster.volume_group_name,
6218       "drbd_usermode_helper": cluster.drbd_usermode_helper,
6219       "file_storage_dir": cluster.file_storage_dir,
6220       "shared_file_storage_dir": cluster.shared_file_storage_dir,
6221       "maintain_node_health": cluster.maintain_node_health,
6222       "ctime": cluster.ctime,
6223       "mtime": cluster.mtime,
6224       "uuid": cluster.uuid,
6225       "tags": list(cluster.GetTags()),
6226       "uid_pool": cluster.uid_pool,
6227       "default_iallocator": cluster.default_iallocator,
6228       "reserved_lvs": cluster.reserved_lvs,
6229       "primary_ip_version": primary_ip_version,
6230       "prealloc_wipe_disks": cluster.prealloc_wipe_disks,
6231       "hidden_os": cluster.hidden_os,
6232       "blacklisted_os": cluster.blacklisted_os,
6233       }
6234
6235     return result
6236
6237
6238 class LUClusterConfigQuery(NoHooksLU):
6239   """Return configuration values.
6240
6241   """
6242   REQ_BGL = False
6243
6244   def CheckArguments(self):
6245     self.cq = _ClusterQuery(None, self.op.output_fields, False)
6246
6247   def ExpandNames(self):
6248     self.cq.ExpandNames(self)
6249
6250   def DeclareLocks(self, level):
6251     self.cq.DeclareLocks(self, level)
6252
6253   def Exec(self, feedback_fn):
6254     result = self.cq.OldStyleQuery(self)
6255
6256     assert len(result) == 1
6257
6258     return result[0]
6259
6260
6261 class _ClusterQuery(_QueryBase):
6262   FIELDS = query.CLUSTER_FIELDS
6263
6264   #: Do not sort (there is only one item)
6265   SORT_FIELD = None
6266
6267   def ExpandNames(self, lu):
6268     lu.needed_locks = {}
6269
6270     # The following variables interact with _QueryBase._GetNames
6271     self.wanted = locking.ALL_SET
6272     self.do_locking = self.use_locking
6273
6274     if self.do_locking:
6275       raise errors.OpPrereqError("Can not use locking for cluster queries",
6276                                  errors.ECODE_INVAL)
6277
6278   def DeclareLocks(self, lu, level):
6279     pass
6280
6281   def _GetQueryData(self, lu):
6282     """Computes the list of nodes and their attributes.
6283
6284     """
6285     # Locking is not used
6286     assert not (compat.any(lu.glm.is_owned(level)
6287                            for level in locking.LEVELS
6288                            if level != locking.LEVEL_CLUSTER) or
6289                 self.do_locking or self.use_locking)
6290
6291     if query.CQ_CONFIG in self.requested_data:
6292       cluster = lu.cfg.GetClusterInfo()
6293     else:
6294       cluster = NotImplemented
6295
6296     if query.CQ_QUEUE_DRAINED in self.requested_data:
6297       drain_flag = os.path.exists(constants.JOB_QUEUE_DRAIN_FILE)
6298     else:
6299       drain_flag = NotImplemented
6300
6301     if query.CQ_WATCHER_PAUSE in self.requested_data:
6302       watcher_pause = utils.ReadWatcherPauseFile(constants.WATCHER_PAUSEFILE)
6303     else:
6304       watcher_pause = NotImplemented
6305
6306     return query.ClusterQueryData(cluster, drain_flag, watcher_pause)
6307
6308
6309 class LUInstanceActivateDisks(NoHooksLU):
6310   """Bring up an instance's disks.
6311
6312   """
6313   REQ_BGL = False
6314
6315   def ExpandNames(self):
6316     self._ExpandAndLockInstance()
6317     self.needed_locks[locking.LEVEL_NODE] = []
6318     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6319
6320   def DeclareLocks(self, level):
6321     if level == locking.LEVEL_NODE:
6322       self._LockInstancesNodes()
6323
6324   def CheckPrereq(self):
6325     """Check prerequisites.
6326
6327     This checks that the instance is in the cluster.
6328
6329     """
6330     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6331     assert self.instance is not None, \
6332       "Cannot retrieve locked instance %s" % self.op.instance_name
6333     _CheckNodeOnline(self, self.instance.primary_node)
6334
6335   def Exec(self, feedback_fn):
6336     """Activate the disks.
6337
6338     """
6339     disks_ok, disks_info = \
6340               _AssembleInstanceDisks(self, self.instance,
6341                                      ignore_size=self.op.ignore_size)
6342     if not disks_ok:
6343       raise errors.OpExecError("Cannot activate block devices")
6344
6345     if self.op.wait_for_sync:
6346       if not _WaitForSync(self, self.instance):
6347         raise errors.OpExecError("Some disks of the instance are degraded!")
6348
6349     return disks_info
6350
6351
6352 def _AssembleInstanceDisks(lu, instance, disks=None, ignore_secondaries=False,
6353                            ignore_size=False):
6354   """Prepare the block devices for an instance.
6355
6356   This sets up the block devices on all nodes.
6357
6358   @type lu: L{LogicalUnit}
6359   @param lu: the logical unit on whose behalf we execute
6360   @type instance: L{objects.Instance}
6361   @param instance: the instance for whose disks we assemble
6362   @type disks: list of L{objects.Disk} or None
6363   @param disks: which disks to assemble (or all, if None)
6364   @type ignore_secondaries: boolean
6365   @param ignore_secondaries: if true, errors on secondary nodes
6366       won't result in an error return from the function
6367   @type ignore_size: boolean
6368   @param ignore_size: if true, the current known size of the disk
6369       will not be used during the disk activation, useful for cases
6370       when the size is wrong
6371   @return: False if the operation failed, otherwise a list of
6372       (host, instance_visible_name, node_visible_name)
6373       with the mapping from node devices to instance devices
6374
6375   """
6376   device_info = []
6377   disks_ok = True
6378   iname = instance.name
6379   disks = _ExpandCheckDisks(instance, disks)
6380
6381   # With the two passes mechanism we try to reduce the window of
6382   # opportunity for the race condition of switching DRBD to primary
6383   # before handshaking occured, but we do not eliminate it
6384
6385   # The proper fix would be to wait (with some limits) until the
6386   # connection has been made and drbd transitions from WFConnection
6387   # into any other network-connected state (Connected, SyncTarget,
6388   # SyncSource, etc.)
6389
6390   # 1st pass, assemble on all nodes in secondary mode
6391   for idx, inst_disk in enumerate(disks):
6392     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
6393       if ignore_size:
6394         node_disk = node_disk.Copy()
6395         node_disk.UnsetSize()
6396       lu.cfg.SetDiskID(node_disk, node)
6397       result = lu.rpc.call_blockdev_assemble(node, (node_disk, instance), iname,
6398                                              False, idx)
6399       msg = result.fail_msg
6400       if msg:
6401         is_offline_secondary = (node in instance.secondary_nodes and
6402                                 result.offline)
6403         lu.proc.LogWarning("Could not prepare block device %s on node %s"
6404                            " (is_primary=False, pass=1): %s",
6405                            inst_disk.iv_name, node, msg)
6406         if not (ignore_secondaries or is_offline_secondary):
6407           disks_ok = False
6408
6409   # FIXME: race condition on drbd migration to primary
6410
6411   # 2nd pass, do only the primary node
6412   for idx, inst_disk in enumerate(disks):
6413     dev_path = None
6414
6415     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
6416       if node != instance.primary_node:
6417         continue
6418       if ignore_size:
6419         node_disk = node_disk.Copy()
6420         node_disk.UnsetSize()
6421       lu.cfg.SetDiskID(node_disk, node)
6422       result = lu.rpc.call_blockdev_assemble(node, (node_disk, instance), iname,
6423                                              True, idx)
6424       msg = result.fail_msg
6425       if msg:
6426         lu.proc.LogWarning("Could not prepare block device %s on node %s"
6427                            " (is_primary=True, pass=2): %s",
6428                            inst_disk.iv_name, node, msg)
6429         disks_ok = False
6430       else:
6431         dev_path = result.payload
6432
6433     device_info.append((instance.primary_node, inst_disk.iv_name, dev_path))
6434
6435   # leave the disks configured for the primary node
6436   # this is a workaround that would be fixed better by
6437   # improving the logical/physical id handling
6438   for disk in disks:
6439     lu.cfg.SetDiskID(disk, instance.primary_node)
6440
6441   return disks_ok, device_info
6442
6443
6444 def _StartInstanceDisks(lu, instance, force):
6445   """Start the disks of an instance.
6446
6447   """
6448   disks_ok, _ = _AssembleInstanceDisks(lu, instance,
6449                                            ignore_secondaries=force)
6450   if not disks_ok:
6451     _ShutdownInstanceDisks(lu, instance)
6452     if force is not None and not force:
6453       lu.proc.LogWarning("", hint="If the message above refers to a"
6454                          " secondary node,"
6455                          " you can retry the operation using '--force'.")
6456     raise errors.OpExecError("Disk consistency error")
6457
6458
6459 class LUInstanceDeactivateDisks(NoHooksLU):
6460   """Shutdown an instance's disks.
6461
6462   """
6463   REQ_BGL = False
6464
6465   def ExpandNames(self):
6466     self._ExpandAndLockInstance()
6467     self.needed_locks[locking.LEVEL_NODE] = []
6468     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6469
6470   def DeclareLocks(self, level):
6471     if level == locking.LEVEL_NODE:
6472       self._LockInstancesNodes()
6473
6474   def CheckPrereq(self):
6475     """Check prerequisites.
6476
6477     This checks that the instance is in the cluster.
6478
6479     """
6480     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6481     assert self.instance is not None, \
6482       "Cannot retrieve locked instance %s" % self.op.instance_name
6483
6484   def Exec(self, feedback_fn):
6485     """Deactivate the disks
6486
6487     """
6488     instance = self.instance
6489     if self.op.force:
6490       _ShutdownInstanceDisks(self, instance)
6491     else:
6492       _SafeShutdownInstanceDisks(self, instance)
6493
6494
6495 def _SafeShutdownInstanceDisks(lu, instance, disks=None):
6496   """Shutdown block devices of an instance.
6497
6498   This function checks if an instance is running, before calling
6499   _ShutdownInstanceDisks.
6500
6501   """
6502   _CheckInstanceState(lu, instance, INSTANCE_DOWN, msg="cannot shutdown disks")
6503   _ShutdownInstanceDisks(lu, instance, disks=disks)
6504
6505
6506 def _ExpandCheckDisks(instance, disks):
6507   """Return the instance disks selected by the disks list
6508
6509   @type disks: list of L{objects.Disk} or None
6510   @param disks: selected disks
6511   @rtype: list of L{objects.Disk}
6512   @return: selected instance disks to act on
6513
6514   """
6515   if disks is None:
6516     return instance.disks
6517   else:
6518     if not set(disks).issubset(instance.disks):
6519       raise errors.ProgrammerError("Can only act on disks belonging to the"
6520                                    " target instance")
6521     return disks
6522
6523
6524 def _ShutdownInstanceDisks(lu, instance, disks=None, ignore_primary=False):
6525   """Shutdown block devices of an instance.
6526
6527   This does the shutdown on all nodes of the instance.
6528
6529   If the ignore_primary is false, errors on the primary node are
6530   ignored.
6531
6532   """
6533   all_result = True
6534   disks = _ExpandCheckDisks(instance, disks)
6535
6536   for disk in disks:
6537     for node, top_disk in disk.ComputeNodeTree(instance.primary_node):
6538       lu.cfg.SetDiskID(top_disk, node)
6539       result = lu.rpc.call_blockdev_shutdown(node, (top_disk, instance))
6540       msg = result.fail_msg
6541       if msg:
6542         lu.LogWarning("Could not shutdown block device %s on node %s: %s",
6543                       disk.iv_name, node, msg)
6544         if ((node == instance.primary_node and not ignore_primary) or
6545             (node != instance.primary_node and not result.offline)):
6546           all_result = False
6547   return all_result
6548
6549
6550 def _CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name):
6551   """Checks if a node has enough free memory.
6552
6553   This function check if a given node has the needed amount of free
6554   memory. In case the node has less memory or we cannot get the
6555   information from the node, this function raise an OpPrereqError
6556   exception.
6557
6558   @type lu: C{LogicalUnit}
6559   @param lu: a logical unit from which we get configuration data
6560   @type node: C{str}
6561   @param node: the node to check
6562   @type reason: C{str}
6563   @param reason: string to use in the error message
6564   @type requested: C{int}
6565   @param requested: the amount of memory in MiB to check for
6566   @type hypervisor_name: C{str}
6567   @param hypervisor_name: the hypervisor to ask for memory stats
6568   @rtype: integer
6569   @return: node current free memory
6570   @raise errors.OpPrereqError: if the node doesn't have enough memory, or
6571       we cannot check the node
6572
6573   """
6574   nodeinfo = lu.rpc.call_node_info([node], None, [hypervisor_name])
6575   nodeinfo[node].Raise("Can't get data from node %s" % node,
6576                        prereq=True, ecode=errors.ECODE_ENVIRON)
6577   (_, _, (hv_info, )) = nodeinfo[node].payload
6578
6579   free_mem = hv_info.get("memory_free", None)
6580   if not isinstance(free_mem, int):
6581     raise errors.OpPrereqError("Can't compute free memory on node %s, result"
6582                                " was '%s'" % (node, free_mem),
6583                                errors.ECODE_ENVIRON)
6584   if requested > free_mem:
6585     raise errors.OpPrereqError("Not enough memory on node %s for %s:"
6586                                " needed %s MiB, available %s MiB" %
6587                                (node, reason, requested, free_mem),
6588                                errors.ECODE_NORES)
6589   return free_mem
6590
6591
6592 def _CheckNodesFreeDiskPerVG(lu, nodenames, req_sizes):
6593   """Checks if nodes have enough free disk space in the all VGs.
6594
6595   This function check if all given nodes have the needed amount of
6596   free disk. In case any node has less disk or we cannot get the
6597   information from the node, this function raise an OpPrereqError
6598   exception.
6599
6600   @type lu: C{LogicalUnit}
6601   @param lu: a logical unit from which we get configuration data
6602   @type nodenames: C{list}
6603   @param nodenames: the list of node names to check
6604   @type req_sizes: C{dict}
6605   @param req_sizes: the hash of vg and corresponding amount of disk in
6606       MiB to check for
6607   @raise errors.OpPrereqError: if the node doesn't have enough disk,
6608       or we cannot check the node
6609
6610   """
6611   for vg, req_size in req_sizes.items():
6612     _CheckNodesFreeDiskOnVG(lu, nodenames, vg, req_size)
6613
6614
6615 def _CheckNodesFreeDiskOnVG(lu, nodenames, vg, requested):
6616   """Checks if nodes have enough free disk space in the specified VG.
6617
6618   This function check if all given nodes have the needed amount of
6619   free disk. In case any node has less disk or we cannot get the
6620   information from the node, this function raise an OpPrereqError
6621   exception.
6622
6623   @type lu: C{LogicalUnit}
6624   @param lu: a logical unit from which we get configuration data
6625   @type nodenames: C{list}
6626   @param nodenames: the list of node names to check
6627   @type vg: C{str}
6628   @param vg: the volume group to check
6629   @type requested: C{int}
6630   @param requested: the amount of disk in MiB to check for
6631   @raise errors.OpPrereqError: if the node doesn't have enough disk,
6632       or we cannot check the node
6633
6634   """
6635   nodeinfo = lu.rpc.call_node_info(nodenames, [vg], None)
6636   for node in nodenames:
6637     info = nodeinfo[node]
6638     info.Raise("Cannot get current information from node %s" % node,
6639                prereq=True, ecode=errors.ECODE_ENVIRON)
6640     (_, (vg_info, ), _) = info.payload
6641     vg_free = vg_info.get("vg_free", None)
6642     if not isinstance(vg_free, int):
6643       raise errors.OpPrereqError("Can't compute free disk space on node"
6644                                  " %s for vg %s, result was '%s'" %
6645                                  (node, vg, vg_free), errors.ECODE_ENVIRON)
6646     if requested > vg_free:
6647       raise errors.OpPrereqError("Not enough disk space on target node %s"
6648                                  " vg %s: required %d MiB, available %d MiB" %
6649                                  (node, vg, requested, vg_free),
6650                                  errors.ECODE_NORES)
6651
6652
6653 def _CheckNodesPhysicalCPUs(lu, nodenames, requested, hypervisor_name):
6654   """Checks if nodes have enough physical CPUs
6655
6656   This function checks if all given nodes have the needed number of
6657   physical CPUs. In case any node has less CPUs or we cannot get the
6658   information from the node, this function raises an OpPrereqError
6659   exception.
6660
6661   @type lu: C{LogicalUnit}
6662   @param lu: a logical unit from which we get configuration data
6663   @type nodenames: C{list}
6664   @param nodenames: the list of node names to check
6665   @type requested: C{int}
6666   @param requested: the minimum acceptable number of physical CPUs
6667   @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
6668       or we cannot check the node
6669
6670   """
6671   nodeinfo = lu.rpc.call_node_info(nodenames, None, [hypervisor_name])
6672   for node in nodenames:
6673     info = nodeinfo[node]
6674     info.Raise("Cannot get current information from node %s" % node,
6675                prereq=True, ecode=errors.ECODE_ENVIRON)
6676     (_, _, (hv_info, )) = info.payload
6677     num_cpus = hv_info.get("cpu_total", None)
6678     if not isinstance(num_cpus, int):
6679       raise errors.OpPrereqError("Can't compute the number of physical CPUs"
6680                                  " on node %s, result was '%s'" %
6681                                  (node, num_cpus), errors.ECODE_ENVIRON)
6682     if requested > num_cpus:
6683       raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
6684                                  "required" % (node, num_cpus, requested),
6685                                  errors.ECODE_NORES)
6686
6687
6688 class LUInstanceStartup(LogicalUnit):
6689   """Starts an instance.
6690
6691   """
6692   HPATH = "instance-start"
6693   HTYPE = constants.HTYPE_INSTANCE
6694   REQ_BGL = False
6695
6696   def CheckArguments(self):
6697     # extra beparams
6698     if self.op.beparams:
6699       # fill the beparams dict
6700       objects.UpgradeBeParams(self.op.beparams)
6701       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
6702
6703   def ExpandNames(self):
6704     self._ExpandAndLockInstance()
6705     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
6706
6707   def DeclareLocks(self, level):
6708     if level == locking.LEVEL_NODE_RES:
6709       self._LockInstancesNodes(primary_only=True, level=locking.LEVEL_NODE_RES)
6710
6711   def BuildHooksEnv(self):
6712     """Build hooks env.
6713
6714     This runs on master, primary and secondary nodes of the instance.
6715
6716     """
6717     env = {
6718       "FORCE": self.op.force,
6719       }
6720
6721     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
6722
6723     return env
6724
6725   def BuildHooksNodes(self):
6726     """Build hooks nodes.
6727
6728     """
6729     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6730     return (nl, nl)
6731
6732   def CheckPrereq(self):
6733     """Check prerequisites.
6734
6735     This checks that the instance is in the cluster.
6736
6737     """
6738     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6739     assert self.instance is not None, \
6740       "Cannot retrieve locked instance %s" % self.op.instance_name
6741
6742     # extra hvparams
6743     if self.op.hvparams:
6744       # check hypervisor parameter syntax (locally)
6745       cluster = self.cfg.GetClusterInfo()
6746       utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
6747       filled_hvp = cluster.FillHV(instance)
6748       filled_hvp.update(self.op.hvparams)
6749       hv_type = hypervisor.GetHypervisor(instance.hypervisor)
6750       hv_type.CheckParameterSyntax(filled_hvp)
6751       _CheckHVParams(self, instance.all_nodes, instance.hypervisor, filled_hvp)
6752
6753     _CheckInstanceState(self, instance, INSTANCE_ONLINE)
6754
6755     self.primary_offline = self.cfg.GetNodeInfo(instance.primary_node).offline
6756
6757     if self.primary_offline and self.op.ignore_offline_nodes:
6758       self.proc.LogWarning("Ignoring offline primary node")
6759
6760       if self.op.hvparams or self.op.beparams:
6761         self.proc.LogWarning("Overridden parameters are ignored")
6762     else:
6763       _CheckNodeOnline(self, instance.primary_node)
6764
6765       bep = self.cfg.GetClusterInfo().FillBE(instance)
6766       bep.update(self.op.beparams)
6767
6768       # check bridges existence
6769       _CheckInstanceBridgesExist(self, instance)
6770
6771       remote_info = self.rpc.call_instance_info(instance.primary_node,
6772                                                 instance.name,
6773                                                 instance.hypervisor)
6774       remote_info.Raise("Error checking node %s" % instance.primary_node,
6775                         prereq=True, ecode=errors.ECODE_ENVIRON)
6776       if not remote_info.payload: # not running already
6777         _CheckNodeFreeMemory(self, instance.primary_node,
6778                              "starting instance %s" % instance.name,
6779                              bep[constants.BE_MINMEM], instance.hypervisor)
6780
6781   def Exec(self, feedback_fn):
6782     """Start the instance.
6783
6784     """
6785     instance = self.instance
6786     force = self.op.force
6787
6788     if not self.op.no_remember:
6789       self.cfg.MarkInstanceUp(instance.name)
6790
6791     if self.primary_offline:
6792       assert self.op.ignore_offline_nodes
6793       self.proc.LogInfo("Primary node offline, marked instance as started")
6794     else:
6795       node_current = instance.primary_node
6796
6797       _StartInstanceDisks(self, instance, force)
6798
6799       result = \
6800         self.rpc.call_instance_start(node_current,
6801                                      (instance, self.op.hvparams,
6802                                       self.op.beparams),
6803                                      self.op.startup_paused)
6804       msg = result.fail_msg
6805       if msg:
6806         _ShutdownInstanceDisks(self, instance)
6807         raise errors.OpExecError("Could not start instance: %s" % msg)
6808
6809
6810 class LUInstanceReboot(LogicalUnit):
6811   """Reboot an instance.
6812
6813   """
6814   HPATH = "instance-reboot"
6815   HTYPE = constants.HTYPE_INSTANCE
6816   REQ_BGL = False
6817
6818   def ExpandNames(self):
6819     self._ExpandAndLockInstance()
6820
6821   def BuildHooksEnv(self):
6822     """Build hooks env.
6823
6824     This runs on master, primary and secondary nodes of the instance.
6825
6826     """
6827     env = {
6828       "IGNORE_SECONDARIES": self.op.ignore_secondaries,
6829       "REBOOT_TYPE": self.op.reboot_type,
6830       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
6831       }
6832
6833     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
6834
6835     return env
6836
6837   def BuildHooksNodes(self):
6838     """Build hooks nodes.
6839
6840     """
6841     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6842     return (nl, nl)
6843
6844   def CheckPrereq(self):
6845     """Check prerequisites.
6846
6847     This checks that the instance is in the cluster.
6848
6849     """
6850     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6851     assert self.instance is not None, \
6852       "Cannot retrieve locked instance %s" % self.op.instance_name
6853     _CheckInstanceState(self, instance, INSTANCE_ONLINE)
6854     _CheckNodeOnline(self, instance.primary_node)
6855
6856     # check bridges existence
6857     _CheckInstanceBridgesExist(self, instance)
6858
6859   def Exec(self, feedback_fn):
6860     """Reboot the instance.
6861
6862     """
6863     instance = self.instance
6864     ignore_secondaries = self.op.ignore_secondaries
6865     reboot_type = self.op.reboot_type
6866
6867     remote_info = self.rpc.call_instance_info(instance.primary_node,
6868                                               instance.name,
6869                                               instance.hypervisor)
6870     remote_info.Raise("Error checking node %s" % instance.primary_node)
6871     instance_running = bool(remote_info.payload)
6872
6873     node_current = instance.primary_node
6874
6875     if instance_running and reboot_type in [constants.INSTANCE_REBOOT_SOFT,
6876                                             constants.INSTANCE_REBOOT_HARD]:
6877       for disk in instance.disks:
6878         self.cfg.SetDiskID(disk, node_current)
6879       result = self.rpc.call_instance_reboot(node_current, instance,
6880                                              reboot_type,
6881                                              self.op.shutdown_timeout)
6882       result.Raise("Could not reboot instance")
6883     else:
6884       if instance_running:
6885         result = self.rpc.call_instance_shutdown(node_current, instance,
6886                                                  self.op.shutdown_timeout)
6887         result.Raise("Could not shutdown instance for full reboot")
6888         _ShutdownInstanceDisks(self, instance)
6889       else:
6890         self.LogInfo("Instance %s was already stopped, starting now",
6891                      instance.name)
6892       _StartInstanceDisks(self, instance, ignore_secondaries)
6893       result = self.rpc.call_instance_start(node_current,
6894                                             (instance, None, None), False)
6895       msg = result.fail_msg
6896       if msg:
6897         _ShutdownInstanceDisks(self, instance)
6898         raise errors.OpExecError("Could not start instance for"
6899                                  " full reboot: %s" % msg)
6900
6901     self.cfg.MarkInstanceUp(instance.name)
6902
6903
6904 class LUInstanceShutdown(LogicalUnit):
6905   """Shutdown an instance.
6906
6907   """
6908   HPATH = "instance-stop"
6909   HTYPE = constants.HTYPE_INSTANCE
6910   REQ_BGL = False
6911
6912   def ExpandNames(self):
6913     self._ExpandAndLockInstance()
6914
6915   def BuildHooksEnv(self):
6916     """Build hooks env.
6917
6918     This runs on master, primary and secondary nodes of the instance.
6919
6920     """
6921     env = _BuildInstanceHookEnvByObject(self, self.instance)
6922     env["TIMEOUT"] = self.op.timeout
6923     return env
6924
6925   def BuildHooksNodes(self):
6926     """Build hooks nodes.
6927
6928     """
6929     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6930     return (nl, nl)
6931
6932   def CheckPrereq(self):
6933     """Check prerequisites.
6934
6935     This checks that the instance is in the cluster.
6936
6937     """
6938     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6939     assert self.instance is not None, \
6940       "Cannot retrieve locked instance %s" % self.op.instance_name
6941
6942     _CheckInstanceState(self, self.instance, INSTANCE_ONLINE)
6943
6944     self.primary_offline = \
6945       self.cfg.GetNodeInfo(self.instance.primary_node).offline
6946
6947     if self.primary_offline and self.op.ignore_offline_nodes:
6948       self.proc.LogWarning("Ignoring offline primary node")
6949     else:
6950       _CheckNodeOnline(self, self.instance.primary_node)
6951
6952   def Exec(self, feedback_fn):
6953     """Shutdown the instance.
6954
6955     """
6956     instance = self.instance
6957     node_current = instance.primary_node
6958     timeout = self.op.timeout
6959
6960     if not self.op.no_remember:
6961       self.cfg.MarkInstanceDown(instance.name)
6962
6963     if self.primary_offline:
6964       assert self.op.ignore_offline_nodes
6965       self.proc.LogInfo("Primary node offline, marked instance as stopped")
6966     else:
6967       result = self.rpc.call_instance_shutdown(node_current, instance, timeout)
6968       msg = result.fail_msg
6969       if msg:
6970         self.proc.LogWarning("Could not shutdown instance: %s" % msg)
6971
6972       _ShutdownInstanceDisks(self, instance)
6973
6974
6975 class LUInstanceReinstall(LogicalUnit):
6976   """Reinstall an instance.
6977
6978   """
6979   HPATH = "instance-reinstall"
6980   HTYPE = constants.HTYPE_INSTANCE
6981   REQ_BGL = False
6982
6983   def ExpandNames(self):
6984     self._ExpandAndLockInstance()
6985
6986   def BuildHooksEnv(self):
6987     """Build hooks env.
6988
6989     This runs on master, primary and secondary nodes of the instance.
6990
6991     """
6992     return _BuildInstanceHookEnvByObject(self, self.instance)
6993
6994   def BuildHooksNodes(self):
6995     """Build hooks nodes.
6996
6997     """
6998     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6999     return (nl, nl)
7000
7001   def CheckPrereq(self):
7002     """Check prerequisites.
7003
7004     This checks that the instance is in the cluster and is not running.
7005
7006     """
7007     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7008     assert instance is not None, \
7009       "Cannot retrieve locked instance %s" % self.op.instance_name
7010     _CheckNodeOnline(self, instance.primary_node, "Instance primary node"
7011                      " offline, cannot reinstall")
7012
7013     if instance.disk_template == constants.DT_DISKLESS:
7014       raise errors.OpPrereqError("Instance '%s' has no disks" %
7015                                  self.op.instance_name,
7016                                  errors.ECODE_INVAL)
7017     _CheckInstanceState(self, instance, INSTANCE_DOWN, msg="cannot reinstall")
7018
7019     if self.op.os_type is not None:
7020       # OS verification
7021       pnode = _ExpandNodeName(self.cfg, instance.primary_node)
7022       _CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant)
7023       instance_os = self.op.os_type
7024     else:
7025       instance_os = instance.os
7026
7027     nodelist = list(instance.all_nodes)
7028
7029     if self.op.osparams:
7030       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
7031       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
7032       self.os_inst = i_osdict # the new dict (without defaults)
7033     else:
7034       self.os_inst = None
7035
7036     self.instance = instance
7037
7038   def Exec(self, feedback_fn):
7039     """Reinstall the instance.
7040
7041     """
7042     inst = self.instance
7043
7044     if self.op.os_type is not None:
7045       feedback_fn("Changing OS to '%s'..." % self.op.os_type)
7046       inst.os = self.op.os_type
7047       # Write to configuration
7048       self.cfg.Update(inst, feedback_fn)
7049
7050     _StartInstanceDisks(self, inst, None)
7051     try:
7052       feedback_fn("Running the instance OS create scripts...")
7053       # FIXME: pass debug option from opcode to backend
7054       result = self.rpc.call_instance_os_add(inst.primary_node,
7055                                              (inst, self.os_inst), True,
7056                                              self.op.debug_level)
7057       result.Raise("Could not install OS for instance %s on node %s" %
7058                    (inst.name, inst.primary_node))
7059     finally:
7060       _ShutdownInstanceDisks(self, inst)
7061
7062
7063 class LUInstanceRecreateDisks(LogicalUnit):
7064   """Recreate an instance's missing disks.
7065
7066   """
7067   HPATH = "instance-recreate-disks"
7068   HTYPE = constants.HTYPE_INSTANCE
7069   REQ_BGL = False
7070
7071   _MODIFYABLE = frozenset([
7072     constants.IDISK_SIZE,
7073     constants.IDISK_MODE,
7074     ])
7075
7076   # New or changed disk parameters may have different semantics
7077   assert constants.IDISK_PARAMS == (_MODIFYABLE | frozenset([
7078     constants.IDISK_ADOPT,
7079
7080     # TODO: Implement support changing VG while recreating
7081     constants.IDISK_VG,
7082     constants.IDISK_METAVG,
7083     ]))
7084
7085   def CheckArguments(self):
7086     if self.op.disks and ht.TPositiveInt(self.op.disks[0]):
7087       # Normalize and convert deprecated list of disk indices
7088       self.op.disks = [(idx, {}) for idx in sorted(frozenset(self.op.disks))]
7089
7090     duplicates = utils.FindDuplicates(map(compat.fst, self.op.disks))
7091     if duplicates:
7092       raise errors.OpPrereqError("Some disks have been specified more than"
7093                                  " once: %s" % utils.CommaJoin(duplicates),
7094                                  errors.ECODE_INVAL)
7095
7096     for (idx, params) in self.op.disks:
7097       utils.ForceDictType(params, constants.IDISK_PARAMS_TYPES)
7098       unsupported = frozenset(params.keys()) - self._MODIFYABLE
7099       if unsupported:
7100         raise errors.OpPrereqError("Parameters for disk %s try to change"
7101                                    " unmodifyable parameter(s): %s" %
7102                                    (idx, utils.CommaJoin(unsupported)),
7103                                    errors.ECODE_INVAL)
7104
7105   def ExpandNames(self):
7106     self._ExpandAndLockInstance()
7107     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
7108     if self.op.nodes:
7109       self.op.nodes = [_ExpandNodeName(self.cfg, n) for n in self.op.nodes]
7110       self.needed_locks[locking.LEVEL_NODE] = list(self.op.nodes)
7111     else:
7112       self.needed_locks[locking.LEVEL_NODE] = []
7113     self.needed_locks[locking.LEVEL_NODE_RES] = []
7114
7115   def DeclareLocks(self, level):
7116     if level == locking.LEVEL_NODE:
7117       # if we replace the nodes, we only need to lock the old primary,
7118       # otherwise we need to lock all nodes for disk re-creation
7119       primary_only = bool(self.op.nodes)
7120       self._LockInstancesNodes(primary_only=primary_only)
7121     elif level == locking.LEVEL_NODE_RES:
7122       # Copy node locks
7123       self.needed_locks[locking.LEVEL_NODE_RES] = \
7124         self.needed_locks[locking.LEVEL_NODE][:]
7125
7126   def BuildHooksEnv(self):
7127     """Build hooks env.
7128
7129     This runs on master, primary and secondary nodes of the instance.
7130
7131     """
7132     return _BuildInstanceHookEnvByObject(self, self.instance)
7133
7134   def BuildHooksNodes(self):
7135     """Build hooks nodes.
7136
7137     """
7138     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7139     return (nl, nl)
7140
7141   def CheckPrereq(self):
7142     """Check prerequisites.
7143
7144     This checks that the instance is in the cluster and is not running.
7145
7146     """
7147     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7148     assert instance is not None, \
7149       "Cannot retrieve locked instance %s" % self.op.instance_name
7150     if self.op.nodes:
7151       if len(self.op.nodes) != len(instance.all_nodes):
7152         raise errors.OpPrereqError("Instance %s currently has %d nodes, but"
7153                                    " %d replacement nodes were specified" %
7154                                    (instance.name, len(instance.all_nodes),
7155                                     len(self.op.nodes)),
7156                                    errors.ECODE_INVAL)
7157       assert instance.disk_template != constants.DT_DRBD8 or \
7158           len(self.op.nodes) == 2
7159       assert instance.disk_template != constants.DT_PLAIN or \
7160           len(self.op.nodes) == 1
7161       primary_node = self.op.nodes[0]
7162     else:
7163       primary_node = instance.primary_node
7164     _CheckNodeOnline(self, primary_node)
7165
7166     if instance.disk_template == constants.DT_DISKLESS:
7167       raise errors.OpPrereqError("Instance '%s' has no disks" %
7168                                  self.op.instance_name, errors.ECODE_INVAL)
7169
7170     # if we replace nodes *and* the old primary is offline, we don't
7171     # check
7172     assert instance.primary_node in self.owned_locks(locking.LEVEL_NODE)
7173     assert instance.primary_node in self.owned_locks(locking.LEVEL_NODE_RES)
7174     old_pnode = self.cfg.GetNodeInfo(instance.primary_node)
7175     if not (self.op.nodes and old_pnode.offline):
7176       _CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
7177                           msg="cannot recreate disks")
7178
7179     if self.op.disks:
7180       self.disks = dict(self.op.disks)
7181     else:
7182       self.disks = dict((idx, {}) for idx in range(len(instance.disks)))
7183
7184     maxidx = max(self.disks.keys())
7185     if maxidx >= len(instance.disks):
7186       raise errors.OpPrereqError("Invalid disk index '%s'" % maxidx,
7187                                  errors.ECODE_INVAL)
7188
7189     if (self.op.nodes and
7190         sorted(self.disks.keys()) != range(len(instance.disks))):
7191       raise errors.OpPrereqError("Can't recreate disks partially and"
7192                                  " change the nodes at the same time",
7193                                  errors.ECODE_INVAL)
7194
7195     self.instance = instance
7196
7197   def Exec(self, feedback_fn):
7198     """Recreate the disks.
7199
7200     """
7201     instance = self.instance
7202
7203     assert (self.owned_locks(locking.LEVEL_NODE) ==
7204             self.owned_locks(locking.LEVEL_NODE_RES))
7205
7206     to_skip = []
7207     mods = [] # keeps track of needed changes
7208
7209     for idx, disk in enumerate(instance.disks):
7210       try:
7211         changes = self.disks[idx]
7212       except KeyError:
7213         # Disk should not be recreated
7214         to_skip.append(idx)
7215         continue
7216
7217       # update secondaries for disks, if needed
7218       if self.op.nodes and disk.dev_type == constants.LD_DRBD8:
7219         # need to update the nodes and minors
7220         assert len(self.op.nodes) == 2
7221         assert len(disk.logical_id) == 6 # otherwise disk internals
7222                                          # have changed
7223         (_, _, old_port, _, _, old_secret) = disk.logical_id
7224         new_minors = self.cfg.AllocateDRBDMinor(self.op.nodes, instance.name)
7225         new_id = (self.op.nodes[0], self.op.nodes[1], old_port,
7226                   new_minors[0], new_minors[1], old_secret)
7227         assert len(disk.logical_id) == len(new_id)
7228       else:
7229         new_id = None
7230
7231       mods.append((idx, new_id, changes))
7232
7233     # now that we have passed all asserts above, we can apply the mods
7234     # in a single run (to avoid partial changes)
7235     for idx, new_id, changes in mods:
7236       disk = instance.disks[idx]
7237       if new_id is not None:
7238         assert disk.dev_type == constants.LD_DRBD8
7239         disk.logical_id = new_id
7240       if changes:
7241         disk.Update(size=changes.get(constants.IDISK_SIZE, None),
7242                     mode=changes.get(constants.IDISK_MODE, None))
7243
7244     # change primary node, if needed
7245     if self.op.nodes:
7246       instance.primary_node = self.op.nodes[0]
7247       self.LogWarning("Changing the instance's nodes, you will have to"
7248                       " remove any disks left on the older nodes manually")
7249
7250     if self.op.nodes:
7251       self.cfg.Update(instance, feedback_fn)
7252
7253     _CreateDisks(self, instance, to_skip=to_skip)
7254
7255
7256 class LUInstanceRename(LogicalUnit):
7257   """Rename an instance.
7258
7259   """
7260   HPATH = "instance-rename"
7261   HTYPE = constants.HTYPE_INSTANCE
7262
7263   def CheckArguments(self):
7264     """Check arguments.
7265
7266     """
7267     if self.op.ip_check and not self.op.name_check:
7268       # TODO: make the ip check more flexible and not depend on the name check
7269       raise errors.OpPrereqError("IP address check requires a name check",
7270                                  errors.ECODE_INVAL)
7271
7272   def BuildHooksEnv(self):
7273     """Build hooks env.
7274
7275     This runs on master, primary and secondary nodes of the instance.
7276
7277     """
7278     env = _BuildInstanceHookEnvByObject(self, self.instance)
7279     env["INSTANCE_NEW_NAME"] = self.op.new_name
7280     return env
7281
7282   def BuildHooksNodes(self):
7283     """Build hooks nodes.
7284
7285     """
7286     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7287     return (nl, nl)
7288
7289   def CheckPrereq(self):
7290     """Check prerequisites.
7291
7292     This checks that the instance is in the cluster and is not running.
7293
7294     """
7295     self.op.instance_name = _ExpandInstanceName(self.cfg,
7296                                                 self.op.instance_name)
7297     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7298     assert instance is not None
7299     _CheckNodeOnline(self, instance.primary_node)
7300     _CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
7301                         msg="cannot rename")
7302     self.instance = instance
7303
7304     new_name = self.op.new_name
7305     if self.op.name_check:
7306       hostname = netutils.GetHostname(name=new_name)
7307       if hostname.name != new_name:
7308         self.LogInfo("Resolved given name '%s' to '%s'", new_name,
7309                      hostname.name)
7310       if not utils.MatchNameComponent(self.op.new_name, [hostname.name]):
7311         raise errors.OpPrereqError(("Resolved hostname '%s' does not look the"
7312                                     " same as given hostname '%s'") %
7313                                     (hostname.name, self.op.new_name),
7314                                     errors.ECODE_INVAL)
7315       new_name = self.op.new_name = hostname.name
7316       if (self.op.ip_check and
7317           netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
7318         raise errors.OpPrereqError("IP %s of instance %s already in use" %
7319                                    (hostname.ip, new_name),
7320                                    errors.ECODE_NOTUNIQUE)
7321
7322     instance_list = self.cfg.GetInstanceList()
7323     if new_name in instance_list and new_name != instance.name:
7324       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
7325                                  new_name, errors.ECODE_EXISTS)
7326
7327   def Exec(self, feedback_fn):
7328     """Rename the instance.
7329
7330     """
7331     inst = self.instance
7332     old_name = inst.name
7333
7334     rename_file_storage = False
7335     if (inst.disk_template in constants.DTS_FILEBASED and
7336         self.op.new_name != inst.name):
7337       old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
7338       rename_file_storage = True
7339
7340     self.cfg.RenameInstance(inst.name, self.op.new_name)
7341     # Change the instance lock. This is definitely safe while we hold the BGL.
7342     # Otherwise the new lock would have to be added in acquired mode.
7343     assert self.REQ_BGL
7344     self.glm.remove(locking.LEVEL_INSTANCE, old_name)
7345     self.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
7346
7347     # re-read the instance from the configuration after rename
7348     inst = self.cfg.GetInstanceInfo(self.op.new_name)
7349
7350     if rename_file_storage:
7351       new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
7352       result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
7353                                                      old_file_storage_dir,
7354                                                      new_file_storage_dir)
7355       result.Raise("Could not rename on node %s directory '%s' to '%s'"
7356                    " (but the instance has been renamed in Ganeti)" %
7357                    (inst.primary_node, old_file_storage_dir,
7358                     new_file_storage_dir))
7359
7360     _StartInstanceDisks(self, inst, None)
7361     try:
7362       result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
7363                                                  old_name, self.op.debug_level)
7364       msg = result.fail_msg
7365       if msg:
7366         msg = ("Could not run OS rename script for instance %s on node %s"
7367                " (but the instance has been renamed in Ganeti): %s" %
7368                (inst.name, inst.primary_node, msg))
7369         self.proc.LogWarning(msg)
7370     finally:
7371       _ShutdownInstanceDisks(self, inst)
7372
7373     return inst.name
7374
7375
7376 class LUInstanceRemove(LogicalUnit):
7377   """Remove an instance.
7378
7379   """
7380   HPATH = "instance-remove"
7381   HTYPE = constants.HTYPE_INSTANCE
7382   REQ_BGL = False
7383
7384   def ExpandNames(self):
7385     self._ExpandAndLockInstance()
7386     self.needed_locks[locking.LEVEL_NODE] = []
7387     self.needed_locks[locking.LEVEL_NODE_RES] = []
7388     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7389
7390   def DeclareLocks(self, level):
7391     if level == locking.LEVEL_NODE:
7392       self._LockInstancesNodes()
7393     elif level == locking.LEVEL_NODE_RES:
7394       # Copy node locks
7395       self.needed_locks[locking.LEVEL_NODE_RES] = \
7396         self.needed_locks[locking.LEVEL_NODE][:]
7397
7398   def BuildHooksEnv(self):
7399     """Build hooks env.
7400
7401     This runs on master, primary and secondary nodes of the instance.
7402
7403     """
7404     env = _BuildInstanceHookEnvByObject(self, self.instance)
7405     env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
7406     return env
7407
7408   def BuildHooksNodes(self):
7409     """Build hooks nodes.
7410
7411     """
7412     nl = [self.cfg.GetMasterNode()]
7413     nl_post = list(self.instance.all_nodes) + nl
7414     return (nl, nl_post)
7415
7416   def CheckPrereq(self):
7417     """Check prerequisites.
7418
7419     This checks that the instance is in the cluster.
7420
7421     """
7422     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7423     assert self.instance is not None, \
7424       "Cannot retrieve locked instance %s" % self.op.instance_name
7425
7426   def Exec(self, feedback_fn):
7427     """Remove the instance.
7428
7429     """
7430     instance = self.instance
7431     logging.info("Shutting down instance %s on node %s",
7432                  instance.name, instance.primary_node)
7433
7434     result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
7435                                              self.op.shutdown_timeout)
7436     msg = result.fail_msg
7437     if msg:
7438       if self.op.ignore_failures:
7439         feedback_fn("Warning: can't shutdown instance: %s" % msg)
7440       else:
7441         raise errors.OpExecError("Could not shutdown instance %s on"
7442                                  " node %s: %s" %
7443                                  (instance.name, instance.primary_node, msg))
7444
7445     assert (self.owned_locks(locking.LEVEL_NODE) ==
7446             self.owned_locks(locking.LEVEL_NODE_RES))
7447     assert not (set(instance.all_nodes) -
7448                 self.owned_locks(locking.LEVEL_NODE)), \
7449       "Not owning correct locks"
7450
7451     _RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
7452
7453
7454 def _RemoveInstance(lu, feedback_fn, instance, ignore_failures):
7455   """Utility function to remove an instance.
7456
7457   """
7458   logging.info("Removing block devices for instance %s", instance.name)
7459
7460   if not _RemoveDisks(lu, instance, ignore_failures=ignore_failures):
7461     if not ignore_failures:
7462       raise errors.OpExecError("Can't remove instance's disks")
7463     feedback_fn("Warning: can't remove instance's disks")
7464
7465   logging.info("Removing instance %s out of cluster config", instance.name)
7466
7467   lu.cfg.RemoveInstance(instance.name)
7468
7469   assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \
7470     "Instance lock removal conflict"
7471
7472   # Remove lock for the instance
7473   lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
7474
7475
7476 class LUInstanceQuery(NoHooksLU):
7477   """Logical unit for querying instances.
7478
7479   """
7480   # pylint: disable=W0142
7481   REQ_BGL = False
7482
7483   def CheckArguments(self):
7484     self.iq = _InstanceQuery(qlang.MakeSimpleFilter("name", self.op.names),
7485                              self.op.output_fields, self.op.use_locking)
7486
7487   def ExpandNames(self):
7488     self.iq.ExpandNames(self)
7489
7490   def DeclareLocks(self, level):
7491     self.iq.DeclareLocks(self, level)
7492
7493   def Exec(self, feedback_fn):
7494     return self.iq.OldStyleQuery(self)
7495
7496
7497 class LUInstanceFailover(LogicalUnit):
7498   """Failover an instance.
7499
7500   """
7501   HPATH = "instance-failover"
7502   HTYPE = constants.HTYPE_INSTANCE
7503   REQ_BGL = False
7504
7505   def CheckArguments(self):
7506     """Check the arguments.
7507
7508     """
7509     self.iallocator = getattr(self.op, "iallocator", None)
7510     self.target_node = getattr(self.op, "target_node", None)
7511
7512   def ExpandNames(self):
7513     self._ExpandAndLockInstance()
7514
7515     if self.op.target_node is not None:
7516       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7517
7518     self.needed_locks[locking.LEVEL_NODE] = []
7519     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7520
7521     self.needed_locks[locking.LEVEL_NODE_RES] = []
7522     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
7523
7524     ignore_consistency = self.op.ignore_consistency
7525     shutdown_timeout = self.op.shutdown_timeout
7526     self._migrater = TLMigrateInstance(self, self.op.instance_name,
7527                                        cleanup=False,
7528                                        failover=True,
7529                                        ignore_consistency=ignore_consistency,
7530                                        shutdown_timeout=shutdown_timeout,
7531                                        ignore_ipolicy=self.op.ignore_ipolicy)
7532     self.tasklets = [self._migrater]
7533
7534   def DeclareLocks(self, level):
7535     if level == locking.LEVEL_NODE:
7536       instance = self.context.cfg.GetInstanceInfo(self.op.instance_name)
7537       if instance.disk_template in constants.DTS_EXT_MIRROR:
7538         if self.op.target_node is None:
7539           self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7540         else:
7541           self.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
7542                                                    self.op.target_node]
7543         del self.recalculate_locks[locking.LEVEL_NODE]
7544       else:
7545         self._LockInstancesNodes()
7546     elif level == locking.LEVEL_NODE_RES:
7547       # Copy node locks
7548       self.needed_locks[locking.LEVEL_NODE_RES] = \
7549         self.needed_locks[locking.LEVEL_NODE][:]
7550
7551   def BuildHooksEnv(self):
7552     """Build hooks env.
7553
7554     This runs on master, primary and secondary nodes of the instance.
7555
7556     """
7557     instance = self._migrater.instance
7558     source_node = instance.primary_node
7559     target_node = self.op.target_node
7560     env = {
7561       "IGNORE_CONSISTENCY": self.op.ignore_consistency,
7562       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
7563       "OLD_PRIMARY": source_node,
7564       "NEW_PRIMARY": target_node,
7565       }
7566
7567     if instance.disk_template in constants.DTS_INT_MIRROR:
7568       env["OLD_SECONDARY"] = instance.secondary_nodes[0]
7569       env["NEW_SECONDARY"] = source_node
7570     else:
7571       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = ""
7572
7573     env.update(_BuildInstanceHookEnvByObject(self, instance))
7574
7575     return env
7576
7577   def BuildHooksNodes(self):
7578     """Build hooks nodes.
7579
7580     """
7581     instance = self._migrater.instance
7582     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
7583     return (nl, nl + [instance.primary_node])
7584
7585
7586 class LUInstanceMigrate(LogicalUnit):
7587   """Migrate an instance.
7588
7589   This is migration without shutting down, compared to the failover,
7590   which is done with shutdown.
7591
7592   """
7593   HPATH = "instance-migrate"
7594   HTYPE = constants.HTYPE_INSTANCE
7595   REQ_BGL = False
7596
7597   def ExpandNames(self):
7598     self._ExpandAndLockInstance()
7599
7600     if self.op.target_node is not None:
7601       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7602
7603     self.needed_locks[locking.LEVEL_NODE] = []
7604     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7605
7606     self.needed_locks[locking.LEVEL_NODE] = []
7607     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7608
7609     self._migrater = \
7610       TLMigrateInstance(self, self.op.instance_name,
7611                         cleanup=self.op.cleanup,
7612                         failover=False,
7613                         fallback=self.op.allow_failover,
7614                         allow_runtime_changes=self.op.allow_runtime_changes,
7615                         ignore_ipolicy=self.op.ignore_ipolicy)
7616     self.tasklets = [self._migrater]
7617
7618   def DeclareLocks(self, level):
7619     if level == locking.LEVEL_NODE:
7620       instance = self.context.cfg.GetInstanceInfo(self.op.instance_name)
7621       if instance.disk_template in constants.DTS_EXT_MIRROR:
7622         if self.op.target_node is None:
7623           self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7624         else:
7625           self.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
7626                                                    self.op.target_node]
7627         del self.recalculate_locks[locking.LEVEL_NODE]
7628       else:
7629         self._LockInstancesNodes()
7630     elif level == locking.LEVEL_NODE_RES:
7631       # Copy node locks
7632       self.needed_locks[locking.LEVEL_NODE_RES] = \
7633         self.needed_locks[locking.LEVEL_NODE][:]
7634
7635   def BuildHooksEnv(self):
7636     """Build hooks env.
7637
7638     This runs on master, primary and secondary nodes of the instance.
7639
7640     """
7641     instance = self._migrater.instance
7642     source_node = instance.primary_node
7643     target_node = self.op.target_node
7644     env = _BuildInstanceHookEnvByObject(self, instance)
7645     env.update({
7646       "MIGRATE_LIVE": self._migrater.live,
7647       "MIGRATE_CLEANUP": self.op.cleanup,
7648       "OLD_PRIMARY": source_node,
7649       "NEW_PRIMARY": target_node,
7650       "ALLOW_RUNTIME_CHANGES": self.op.allow_runtime_changes,
7651       })
7652
7653     if instance.disk_template in constants.DTS_INT_MIRROR:
7654       env["OLD_SECONDARY"] = target_node
7655       env["NEW_SECONDARY"] = source_node
7656     else:
7657       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = None
7658
7659     return env
7660
7661   def BuildHooksNodes(self):
7662     """Build hooks nodes.
7663
7664     """
7665     instance = self._migrater.instance
7666     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
7667     return (nl, nl + [instance.primary_node])
7668
7669
7670 class LUInstanceMove(LogicalUnit):
7671   """Move an instance by data-copying.
7672
7673   """
7674   HPATH = "instance-move"
7675   HTYPE = constants.HTYPE_INSTANCE
7676   REQ_BGL = False
7677
7678   def ExpandNames(self):
7679     self._ExpandAndLockInstance()
7680     target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7681     self.op.target_node = target_node
7682     self.needed_locks[locking.LEVEL_NODE] = [target_node]
7683     self.needed_locks[locking.LEVEL_NODE_RES] = []
7684     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
7685
7686   def DeclareLocks(self, level):
7687     if level == locking.LEVEL_NODE:
7688       self._LockInstancesNodes(primary_only=True)
7689     elif level == locking.LEVEL_NODE_RES:
7690       # Copy node locks
7691       self.needed_locks[locking.LEVEL_NODE_RES] = \
7692         self.needed_locks[locking.LEVEL_NODE][:]
7693
7694   def BuildHooksEnv(self):
7695     """Build hooks env.
7696
7697     This runs on master, primary and secondary nodes of the instance.
7698
7699     """
7700     env = {
7701       "TARGET_NODE": self.op.target_node,
7702       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
7703       }
7704     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
7705     return env
7706
7707   def BuildHooksNodes(self):
7708     """Build hooks nodes.
7709
7710     """
7711     nl = [
7712       self.cfg.GetMasterNode(),
7713       self.instance.primary_node,
7714       self.op.target_node,
7715       ]
7716     return (nl, nl)
7717
7718   def CheckPrereq(self):
7719     """Check prerequisites.
7720
7721     This checks that the instance is in the cluster.
7722
7723     """
7724     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7725     assert self.instance is not None, \
7726       "Cannot retrieve locked instance %s" % self.op.instance_name
7727
7728     node = self.cfg.GetNodeInfo(self.op.target_node)
7729     assert node is not None, \
7730       "Cannot retrieve locked node %s" % self.op.target_node
7731
7732     self.target_node = target_node = node.name
7733
7734     if target_node == instance.primary_node:
7735       raise errors.OpPrereqError("Instance %s is already on the node %s" %
7736                                  (instance.name, target_node),
7737                                  errors.ECODE_STATE)
7738
7739     bep = self.cfg.GetClusterInfo().FillBE(instance)
7740
7741     for idx, dsk in enumerate(instance.disks):
7742       if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
7743         raise errors.OpPrereqError("Instance disk %d has a complex layout,"
7744                                    " cannot copy" % idx, errors.ECODE_STATE)
7745
7746     _CheckNodeOnline(self, target_node)
7747     _CheckNodeNotDrained(self, target_node)
7748     _CheckNodeVmCapable(self, target_node)
7749     ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(),
7750                                      self.cfg.GetNodeGroup(node.group))
7751     _CheckTargetNodeIPolicy(self, ipolicy, instance, node,
7752                             ignore=self.op.ignore_ipolicy)
7753
7754     if instance.admin_state == constants.ADMINST_UP:
7755       # check memory requirements on the secondary node
7756       _CheckNodeFreeMemory(self, target_node, "failing over instance %s" %
7757                            instance.name, bep[constants.BE_MAXMEM],
7758                            instance.hypervisor)
7759     else:
7760       self.LogInfo("Not checking memory on the secondary node as"
7761                    " instance will not be started")
7762
7763     # check bridge existance
7764     _CheckInstanceBridgesExist(self, instance, node=target_node)
7765
7766   def Exec(self, feedback_fn):
7767     """Move an instance.
7768
7769     The move is done by shutting it down on its present node, copying
7770     the data over (slow) and starting it on the new node.
7771
7772     """
7773     instance = self.instance
7774
7775     source_node = instance.primary_node
7776     target_node = self.target_node
7777
7778     self.LogInfo("Shutting down instance %s on source node %s",
7779                  instance.name, source_node)
7780
7781     assert (self.owned_locks(locking.LEVEL_NODE) ==
7782             self.owned_locks(locking.LEVEL_NODE_RES))
7783
7784     result = self.rpc.call_instance_shutdown(source_node, instance,
7785                                              self.op.shutdown_timeout)
7786     msg = result.fail_msg
7787     if msg:
7788       if self.op.ignore_consistency:
7789         self.proc.LogWarning("Could not shutdown instance %s on node %s."
7790                              " Proceeding anyway. Please make sure node"
7791                              " %s is down. Error details: %s",
7792                              instance.name, source_node, source_node, msg)
7793       else:
7794         raise errors.OpExecError("Could not shutdown instance %s on"
7795                                  " node %s: %s" %
7796                                  (instance.name, source_node, msg))
7797
7798     # create the target disks
7799     try:
7800       _CreateDisks(self, instance, target_node=target_node)
7801     except errors.OpExecError:
7802       self.LogWarning("Device creation failed, reverting...")
7803       try:
7804         _RemoveDisks(self, instance, target_node=target_node)
7805       finally:
7806         self.cfg.ReleaseDRBDMinors(instance.name)
7807         raise
7808
7809     cluster_name = self.cfg.GetClusterInfo().cluster_name
7810
7811     errs = []
7812     # activate, get path, copy the data over
7813     for idx, disk in enumerate(instance.disks):
7814       self.LogInfo("Copying data for disk %d", idx)
7815       result = self.rpc.call_blockdev_assemble(target_node, (disk, instance),
7816                                                instance.name, True, idx)
7817       if result.fail_msg:
7818         self.LogWarning("Can't assemble newly created disk %d: %s",
7819                         idx, result.fail_msg)
7820         errs.append(result.fail_msg)
7821         break
7822       dev_path = result.payload
7823       result = self.rpc.call_blockdev_export(source_node, (disk, instance),
7824                                              target_node, dev_path,
7825                                              cluster_name)
7826       if result.fail_msg:
7827         self.LogWarning("Can't copy data over for disk %d: %s",
7828                         idx, result.fail_msg)
7829         errs.append(result.fail_msg)
7830         break
7831
7832     if errs:
7833       self.LogWarning("Some disks failed to copy, aborting")
7834       try:
7835         _RemoveDisks(self, instance, target_node=target_node)
7836       finally:
7837         self.cfg.ReleaseDRBDMinors(instance.name)
7838         raise errors.OpExecError("Errors during disk copy: %s" %
7839                                  (",".join(errs),))
7840
7841     instance.primary_node = target_node
7842     self.cfg.Update(instance, feedback_fn)
7843
7844     self.LogInfo("Removing the disks on the original node")
7845     _RemoveDisks(self, instance, target_node=source_node)
7846
7847     # Only start the instance if it's marked as up
7848     if instance.admin_state == constants.ADMINST_UP:
7849       self.LogInfo("Starting instance %s on node %s",
7850                    instance.name, target_node)
7851
7852       disks_ok, _ = _AssembleInstanceDisks(self, instance,
7853                                            ignore_secondaries=True)
7854       if not disks_ok:
7855         _ShutdownInstanceDisks(self, instance)
7856         raise errors.OpExecError("Can't activate the instance's disks")
7857
7858       result = self.rpc.call_instance_start(target_node,
7859                                             (instance, None, None), False)
7860       msg = result.fail_msg
7861       if msg:
7862         _ShutdownInstanceDisks(self, instance)
7863         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
7864                                  (instance.name, target_node, msg))
7865
7866
7867 class LUNodeMigrate(LogicalUnit):
7868   """Migrate all instances from a node.
7869
7870   """
7871   HPATH = "node-migrate"
7872   HTYPE = constants.HTYPE_NODE
7873   REQ_BGL = False
7874
7875   def CheckArguments(self):
7876     pass
7877
7878   def ExpandNames(self):
7879     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
7880
7881     self.share_locks = _ShareAll()
7882     self.needed_locks = {
7883       locking.LEVEL_NODE: [self.op.node_name],
7884       }
7885
7886   def BuildHooksEnv(self):
7887     """Build hooks env.
7888
7889     This runs on the master, the primary and all the secondaries.
7890
7891     """
7892     return {
7893       "NODE_NAME": self.op.node_name,
7894       "ALLOW_RUNTIME_CHANGES": self.op.allow_runtime_changes,
7895       }
7896
7897   def BuildHooksNodes(self):
7898     """Build hooks nodes.
7899
7900     """
7901     nl = [self.cfg.GetMasterNode()]
7902     return (nl, nl)
7903
7904   def CheckPrereq(self):
7905     pass
7906
7907   def Exec(self, feedback_fn):
7908     # Prepare jobs for migration instances
7909     allow_runtime_changes = self.op.allow_runtime_changes
7910     jobs = [
7911       [opcodes.OpInstanceMigrate(instance_name=inst.name,
7912                                  mode=self.op.mode,
7913                                  live=self.op.live,
7914                                  iallocator=self.op.iallocator,
7915                                  target_node=self.op.target_node,
7916                                  allow_runtime_changes=allow_runtime_changes,
7917                                  ignore_ipolicy=self.op.ignore_ipolicy)]
7918       for inst in _GetNodePrimaryInstances(self.cfg, self.op.node_name)
7919       ]
7920
7921     # TODO: Run iallocator in this opcode and pass correct placement options to
7922     # OpInstanceMigrate. Since other jobs can modify the cluster between
7923     # running the iallocator and the actual migration, a good consistency model
7924     # will have to be found.
7925
7926     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
7927             frozenset([self.op.node_name]))
7928
7929     return ResultWithJobs(jobs)
7930
7931
7932 class TLMigrateInstance(Tasklet):
7933   """Tasklet class for instance migration.
7934
7935   @type live: boolean
7936   @ivar live: whether the migration will be done live or non-live;
7937       this variable is initalized only after CheckPrereq has run
7938   @type cleanup: boolean
7939   @ivar cleanup: Wheater we cleanup from a failed migration
7940   @type iallocator: string
7941   @ivar iallocator: The iallocator used to determine target_node
7942   @type target_node: string
7943   @ivar target_node: If given, the target_node to reallocate the instance to
7944   @type failover: boolean
7945   @ivar failover: Whether operation results in failover or migration
7946   @type fallback: boolean
7947   @ivar fallback: Whether fallback to failover is allowed if migration not
7948                   possible
7949   @type ignore_consistency: boolean
7950   @ivar ignore_consistency: Wheter we should ignore consistency between source
7951                             and target node
7952   @type shutdown_timeout: int
7953   @ivar shutdown_timeout: In case of failover timeout of the shutdown
7954   @type ignore_ipolicy: bool
7955   @ivar ignore_ipolicy: If true, we can ignore instance policy when migrating
7956
7957   """
7958
7959   # Constants
7960   _MIGRATION_POLL_INTERVAL = 1      # seconds
7961   _MIGRATION_FEEDBACK_INTERVAL = 10 # seconds
7962
7963   def __init__(self, lu, instance_name, cleanup=False,
7964                failover=False, fallback=False,
7965                ignore_consistency=False,
7966                allow_runtime_changes=True,
7967                shutdown_timeout=constants.DEFAULT_SHUTDOWN_TIMEOUT,
7968                ignore_ipolicy=False):
7969     """Initializes this class.
7970
7971     """
7972     Tasklet.__init__(self, lu)
7973
7974     # Parameters
7975     self.instance_name = instance_name
7976     self.cleanup = cleanup
7977     self.live = False # will be overridden later
7978     self.failover = failover
7979     self.fallback = fallback
7980     self.ignore_consistency = ignore_consistency
7981     self.shutdown_timeout = shutdown_timeout
7982     self.ignore_ipolicy = ignore_ipolicy
7983     self.allow_runtime_changes = allow_runtime_changes
7984
7985   def CheckPrereq(self):
7986     """Check prerequisites.
7987
7988     This checks that the instance is in the cluster.
7989
7990     """
7991     instance_name = _ExpandInstanceName(self.lu.cfg, self.instance_name)
7992     instance = self.cfg.GetInstanceInfo(instance_name)
7993     assert instance is not None
7994     self.instance = instance
7995     cluster = self.cfg.GetClusterInfo()
7996
7997     if (not self.cleanup and
7998         not instance.admin_state == constants.ADMINST_UP and
7999         not self.failover and self.fallback):
8000       self.lu.LogInfo("Instance is marked down or offline, fallback allowed,"
8001                       " switching to failover")
8002       self.failover = True
8003
8004     if instance.disk_template not in constants.DTS_MIRRORED:
8005       if self.failover:
8006         text = "failovers"
8007       else:
8008         text = "migrations"
8009       raise errors.OpPrereqError("Instance's disk layout '%s' does not allow"
8010                                  " %s" % (instance.disk_template, text),
8011                                  errors.ECODE_STATE)
8012
8013     if instance.disk_template in constants.DTS_EXT_MIRROR:
8014       _CheckIAllocatorOrNode(self.lu, "iallocator", "target_node")
8015
8016       if self.lu.op.iallocator:
8017         self._RunAllocator()
8018       else:
8019         # We set set self.target_node as it is required by
8020         # BuildHooksEnv
8021         self.target_node = self.lu.op.target_node
8022
8023       # Check that the target node is correct in terms of instance policy
8024       nodeinfo = self.cfg.GetNodeInfo(self.target_node)
8025       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
8026       ipolicy = _CalculateGroupIPolicy(cluster, group_info)
8027       _CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo,
8028                               ignore=self.ignore_ipolicy)
8029
8030       # self.target_node is already populated, either directly or by the
8031       # iallocator run
8032       target_node = self.target_node
8033       if self.target_node == instance.primary_node:
8034         raise errors.OpPrereqError("Cannot migrate instance %s"
8035                                    " to its primary (%s)" %
8036                                    (instance.name, instance.primary_node))
8037
8038       if len(self.lu.tasklets) == 1:
8039         # It is safe to release locks only when we're the only tasklet
8040         # in the LU
8041         _ReleaseLocks(self.lu, locking.LEVEL_NODE,
8042                       keep=[instance.primary_node, self.target_node])
8043
8044     else:
8045       secondary_nodes = instance.secondary_nodes
8046       if not secondary_nodes:
8047         raise errors.ConfigurationError("No secondary node but using"
8048                                         " %s disk template" %
8049                                         instance.disk_template)
8050       target_node = secondary_nodes[0]
8051       if self.lu.op.iallocator or (self.lu.op.target_node and
8052                                    self.lu.op.target_node != target_node):
8053         if self.failover:
8054           text = "failed over"
8055         else:
8056           text = "migrated"
8057         raise errors.OpPrereqError("Instances with disk template %s cannot"
8058                                    " be %s to arbitrary nodes"
8059                                    " (neither an iallocator nor a target"
8060                                    " node can be passed)" %
8061                                    (instance.disk_template, text),
8062                                    errors.ECODE_INVAL)
8063       nodeinfo = self.cfg.GetNodeInfo(target_node)
8064       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
8065       ipolicy = _CalculateGroupIPolicy(cluster, group_info)
8066       _CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo,
8067                               ignore=self.ignore_ipolicy)
8068
8069     i_be = cluster.FillBE(instance)
8070
8071     # check memory requirements on the secondary node
8072     if (not self.cleanup and
8073          (not self.failover or instance.admin_state == constants.ADMINST_UP)):
8074       self.tgt_free_mem = _CheckNodeFreeMemory(self.lu, target_node,
8075                                                "migrating instance %s" %
8076                                                instance.name,
8077                                                i_be[constants.BE_MINMEM],
8078                                                instance.hypervisor)
8079     else:
8080       self.lu.LogInfo("Not checking memory on the secondary node as"
8081                       " instance will not be started")
8082
8083     # check if failover must be forced instead of migration
8084     if (not self.cleanup and not self.failover and
8085         i_be[constants.BE_ALWAYS_FAILOVER]):
8086       if self.fallback:
8087         self.lu.LogInfo("Instance configured to always failover; fallback"
8088                         " to failover")
8089         self.failover = True
8090       else:
8091         raise errors.OpPrereqError("This instance has been configured to"
8092                                    " always failover, please allow failover",
8093                                    errors.ECODE_STATE)
8094
8095     # check bridge existance
8096     _CheckInstanceBridgesExist(self.lu, instance, node=target_node)
8097
8098     if not self.cleanup:
8099       _CheckNodeNotDrained(self.lu, target_node)
8100       if not self.failover:
8101         result = self.rpc.call_instance_migratable(instance.primary_node,
8102                                                    instance)
8103         if result.fail_msg and self.fallback:
8104           self.lu.LogInfo("Can't migrate, instance offline, fallback to"
8105                           " failover")
8106           self.failover = True
8107         else:
8108           result.Raise("Can't migrate, please use failover",
8109                        prereq=True, ecode=errors.ECODE_STATE)
8110
8111     assert not (self.failover and self.cleanup)
8112
8113     if not self.failover:
8114       if self.lu.op.live is not None and self.lu.op.mode is not None:
8115         raise errors.OpPrereqError("Only one of the 'live' and 'mode'"
8116                                    " parameters are accepted",
8117                                    errors.ECODE_INVAL)
8118       if self.lu.op.live is not None:
8119         if self.lu.op.live:
8120           self.lu.op.mode = constants.HT_MIGRATION_LIVE
8121         else:
8122           self.lu.op.mode = constants.HT_MIGRATION_NONLIVE
8123         # reset the 'live' parameter to None so that repeated
8124         # invocations of CheckPrereq do not raise an exception
8125         self.lu.op.live = None
8126       elif self.lu.op.mode is None:
8127         # read the default value from the hypervisor
8128         i_hv = cluster.FillHV(self.instance, skip_globals=False)
8129         self.lu.op.mode = i_hv[constants.HV_MIGRATION_MODE]
8130
8131       self.live = self.lu.op.mode == constants.HT_MIGRATION_LIVE
8132     else:
8133       # Failover is never live
8134       self.live = False
8135
8136     if not (self.failover or self.cleanup):
8137       remote_info = self.rpc.call_instance_info(instance.primary_node,
8138                                                 instance.name,
8139                                                 instance.hypervisor)
8140       remote_info.Raise("Error checking instance on node %s" %
8141                         instance.primary_node)
8142       instance_running = bool(remote_info.payload)
8143       if instance_running:
8144         self.current_mem = int(remote_info.payload["memory"])
8145
8146   def _RunAllocator(self):
8147     """Run the allocator based on input opcode.
8148
8149     """
8150     # FIXME: add a self.ignore_ipolicy option
8151     ial = IAllocator(self.cfg, self.rpc,
8152                      mode=constants.IALLOCATOR_MODE_RELOC,
8153                      name=self.instance_name,
8154                      relocate_from=[self.instance.primary_node],
8155                      )
8156
8157     ial.Run(self.lu.op.iallocator)
8158
8159     if not ial.success:
8160       raise errors.OpPrereqError("Can't compute nodes using"
8161                                  " iallocator '%s': %s" %
8162                                  (self.lu.op.iallocator, ial.info),
8163                                  errors.ECODE_NORES)
8164     if len(ial.result) != ial.required_nodes:
8165       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
8166                                  " of nodes (%s), required %s" %
8167                                  (self.lu.op.iallocator, len(ial.result),
8168                                   ial.required_nodes), errors.ECODE_FAULT)
8169     self.target_node = ial.result[0]
8170     self.lu.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
8171                  self.instance_name, self.lu.op.iallocator,
8172                  utils.CommaJoin(ial.result))
8173
8174   def _WaitUntilSync(self):
8175     """Poll with custom rpc for disk sync.
8176
8177     This uses our own step-based rpc call.
8178
8179     """
8180     self.feedback_fn("* wait until resync is done")
8181     all_done = False
8182     while not all_done:
8183       all_done = True
8184       result = self.rpc.call_drbd_wait_sync(self.all_nodes,
8185                                             self.nodes_ip,
8186                                             (self.instance.disks,
8187                                              self.instance))
8188       min_percent = 100
8189       for node, nres in result.items():
8190         nres.Raise("Cannot resync disks on node %s" % node)
8191         node_done, node_percent = nres.payload
8192         all_done = all_done and node_done
8193         if node_percent is not None:
8194           min_percent = min(min_percent, node_percent)
8195       if not all_done:
8196         if min_percent < 100:
8197           self.feedback_fn("   - progress: %.1f%%" % min_percent)
8198         time.sleep(2)
8199
8200   def _EnsureSecondary(self, node):
8201     """Demote a node to secondary.
8202
8203     """
8204     self.feedback_fn("* switching node %s to secondary mode" % node)
8205
8206     for dev in self.instance.disks:
8207       self.cfg.SetDiskID(dev, node)
8208
8209     result = self.rpc.call_blockdev_close(node, self.instance.name,
8210                                           self.instance.disks)
8211     result.Raise("Cannot change disk to secondary on node %s" % node)
8212
8213   def _GoStandalone(self):
8214     """Disconnect from the network.
8215
8216     """
8217     self.feedback_fn("* changing into standalone mode")
8218     result = self.rpc.call_drbd_disconnect_net(self.all_nodes, self.nodes_ip,
8219                                                self.instance.disks)
8220     for node, nres in result.items():
8221       nres.Raise("Cannot disconnect disks node %s" % node)
8222
8223   def _GoReconnect(self, multimaster):
8224     """Reconnect to the network.
8225
8226     """
8227     if multimaster:
8228       msg = "dual-master"
8229     else:
8230       msg = "single-master"
8231     self.feedback_fn("* changing disks into %s mode" % msg)
8232     result = self.rpc.call_drbd_attach_net(self.all_nodes, self.nodes_ip,
8233                                            (self.instance.disks, self.instance),
8234                                            self.instance.name, multimaster)
8235     for node, nres in result.items():
8236       nres.Raise("Cannot change disks config on node %s" % node)
8237
8238   def _ExecCleanup(self):
8239     """Try to cleanup after a failed migration.
8240
8241     The cleanup is done by:
8242       - check that the instance is running only on one node
8243         (and update the config if needed)
8244       - change disks on its secondary node to secondary
8245       - wait until disks are fully synchronized
8246       - disconnect from the network
8247       - change disks into single-master mode
8248       - wait again until disks are fully synchronized
8249
8250     """
8251     instance = self.instance
8252     target_node = self.target_node
8253     source_node = self.source_node
8254
8255     # check running on only one node
8256     self.feedback_fn("* checking where the instance actually runs"
8257                      " (if this hangs, the hypervisor might be in"
8258                      " a bad state)")
8259     ins_l = self.rpc.call_instance_list(self.all_nodes, [instance.hypervisor])
8260     for node, result in ins_l.items():
8261       result.Raise("Can't contact node %s" % node)
8262
8263     runningon_source = instance.name in ins_l[source_node].payload
8264     runningon_target = instance.name in ins_l[target_node].payload
8265
8266     if runningon_source and runningon_target:
8267       raise errors.OpExecError("Instance seems to be running on two nodes,"
8268                                " or the hypervisor is confused; you will have"
8269                                " to ensure manually that it runs only on one"
8270                                " and restart this operation")
8271
8272     if not (runningon_source or runningon_target):
8273       raise errors.OpExecError("Instance does not seem to be running at all;"
8274                                " in this case it's safer to repair by"
8275                                " running 'gnt-instance stop' to ensure disk"
8276                                " shutdown, and then restarting it")
8277
8278     if runningon_target:
8279       # the migration has actually succeeded, we need to update the config
8280       self.feedback_fn("* instance running on secondary node (%s),"
8281                        " updating config" % target_node)
8282       instance.primary_node = target_node
8283       self.cfg.Update(instance, self.feedback_fn)
8284       demoted_node = source_node
8285     else:
8286       self.feedback_fn("* instance confirmed to be running on its"
8287                        " primary node (%s)" % source_node)
8288       demoted_node = target_node
8289
8290     if instance.disk_template in constants.DTS_INT_MIRROR:
8291       self._EnsureSecondary(demoted_node)
8292       try:
8293         self._WaitUntilSync()
8294       except errors.OpExecError:
8295         # we ignore here errors, since if the device is standalone, it
8296         # won't be able to sync
8297         pass
8298       self._GoStandalone()
8299       self._GoReconnect(False)
8300       self._WaitUntilSync()
8301
8302     self.feedback_fn("* done")
8303
8304   def _RevertDiskStatus(self):
8305     """Try to revert the disk status after a failed migration.
8306
8307     """
8308     target_node = self.target_node
8309     if self.instance.disk_template in constants.DTS_EXT_MIRROR:
8310       return
8311
8312     try:
8313       self._EnsureSecondary(target_node)
8314       self._GoStandalone()
8315       self._GoReconnect(False)
8316       self._WaitUntilSync()
8317     except errors.OpExecError, err:
8318       self.lu.LogWarning("Migration failed and I can't reconnect the drives,"
8319                          " please try to recover the instance manually;"
8320                          " error '%s'" % str(err))
8321
8322   def _AbortMigration(self):
8323     """Call the hypervisor code to abort a started migration.
8324
8325     """
8326     instance = self.instance
8327     target_node = self.target_node
8328     source_node = self.source_node
8329     migration_info = self.migration_info
8330
8331     abort_result = self.rpc.call_instance_finalize_migration_dst(target_node,
8332                                                                  instance,
8333                                                                  migration_info,
8334                                                                  False)
8335     abort_msg = abort_result.fail_msg
8336     if abort_msg:
8337       logging.error("Aborting migration failed on target node %s: %s",
8338                     target_node, abort_msg)
8339       # Don't raise an exception here, as we stil have to try to revert the
8340       # disk status, even if this step failed.
8341
8342     abort_result = self.rpc.call_instance_finalize_migration_src(source_node,
8343         instance, False, self.live)
8344     abort_msg = abort_result.fail_msg
8345     if abort_msg:
8346       logging.error("Aborting migration failed on source node %s: %s",
8347                     source_node, abort_msg)
8348
8349   def _ExecMigration(self):
8350     """Migrate an instance.
8351
8352     The migrate is done by:
8353       - change the disks into dual-master mode
8354       - wait until disks are fully synchronized again
8355       - migrate the instance
8356       - change disks on the new secondary node (the old primary) to secondary
8357       - wait until disks are fully synchronized
8358       - change disks into single-master mode
8359
8360     """
8361     instance = self.instance
8362     target_node = self.target_node
8363     source_node = self.source_node
8364
8365     # Check for hypervisor version mismatch and warn the user.
8366     nodeinfo = self.rpc.call_node_info([source_node, target_node],
8367                                        None, [self.instance.hypervisor])
8368     for ninfo in nodeinfo.values():
8369       ninfo.Raise("Unable to retrieve node information from node '%s'" %
8370                   ninfo.node)
8371     (_, _, (src_info, )) = nodeinfo[source_node].payload
8372     (_, _, (dst_info, )) = nodeinfo[target_node].payload
8373
8374     if ((constants.HV_NODEINFO_KEY_VERSION in src_info) and
8375         (constants.HV_NODEINFO_KEY_VERSION in dst_info)):
8376       src_version = src_info[constants.HV_NODEINFO_KEY_VERSION]
8377       dst_version = dst_info[constants.HV_NODEINFO_KEY_VERSION]
8378       if src_version != dst_version:
8379         self.feedback_fn("* warning: hypervisor version mismatch between"
8380                          " source (%s) and target (%s) node" %
8381                          (src_version, dst_version))
8382
8383     self.feedback_fn("* checking disk consistency between source and target")
8384     for (idx, dev) in enumerate(instance.disks):
8385       if not _CheckDiskConsistency(self.lu, instance, dev, target_node, False):
8386         raise errors.OpExecError("Disk %s is degraded or not fully"
8387                                  " synchronized on target node,"
8388                                  " aborting migration" % idx)
8389
8390     if self.current_mem > self.tgt_free_mem:
8391       if not self.allow_runtime_changes:
8392         raise errors.OpExecError("Memory ballooning not allowed and not enough"
8393                                  " free memory to fit instance %s on target"
8394                                  " node %s (have %dMB, need %dMB)" %
8395                                  (instance.name, target_node,
8396                                   self.tgt_free_mem, self.current_mem))
8397       self.feedback_fn("* setting instance memory to %s" % self.tgt_free_mem)
8398       rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
8399                                                      instance,
8400                                                      self.tgt_free_mem)
8401       rpcres.Raise("Cannot modify instance runtime memory")
8402
8403     # First get the migration information from the remote node
8404     result = self.rpc.call_migration_info(source_node, instance)
8405     msg = result.fail_msg
8406     if msg:
8407       log_err = ("Failed fetching source migration information from %s: %s" %
8408                  (source_node, msg))
8409       logging.error(log_err)
8410       raise errors.OpExecError(log_err)
8411
8412     self.migration_info = migration_info = result.payload
8413
8414     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
8415       # Then switch the disks to master/master mode
8416       self._EnsureSecondary(target_node)
8417       self._GoStandalone()
8418       self._GoReconnect(True)
8419       self._WaitUntilSync()
8420
8421     self.feedback_fn("* preparing %s to accept the instance" % target_node)
8422     result = self.rpc.call_accept_instance(target_node,
8423                                            instance,
8424                                            migration_info,
8425                                            self.nodes_ip[target_node])
8426
8427     msg = result.fail_msg
8428     if msg:
8429       logging.error("Instance pre-migration failed, trying to revert"
8430                     " disk status: %s", msg)
8431       self.feedback_fn("Pre-migration failed, aborting")
8432       self._AbortMigration()
8433       self._RevertDiskStatus()
8434       raise errors.OpExecError("Could not pre-migrate instance %s: %s" %
8435                                (instance.name, msg))
8436
8437     self.feedback_fn("* migrating instance to %s" % target_node)
8438     result = self.rpc.call_instance_migrate(source_node, instance,
8439                                             self.nodes_ip[target_node],
8440                                             self.live)
8441     msg = result.fail_msg
8442     if msg:
8443       logging.error("Instance migration failed, trying to revert"
8444                     " disk status: %s", msg)
8445       self.feedback_fn("Migration failed, aborting")
8446       self._AbortMigration()
8447       self._RevertDiskStatus()
8448       raise errors.OpExecError("Could not migrate instance %s: %s" %
8449                                (instance.name, msg))
8450
8451     self.feedback_fn("* starting memory transfer")
8452     last_feedback = time.time()
8453     while True:
8454       result = self.rpc.call_instance_get_migration_status(source_node,
8455                                                            instance)
8456       msg = result.fail_msg
8457       ms = result.payload   # MigrationStatus instance
8458       if msg or (ms.status in constants.HV_MIGRATION_FAILED_STATUSES):
8459         logging.error("Instance migration failed, trying to revert"
8460                       " disk status: %s", msg)
8461         self.feedback_fn("Migration failed, aborting")
8462         self._AbortMigration()
8463         self._RevertDiskStatus()
8464         raise errors.OpExecError("Could not migrate instance %s: %s" %
8465                                  (instance.name, msg))
8466
8467       if result.payload.status != constants.HV_MIGRATION_ACTIVE:
8468         self.feedback_fn("* memory transfer complete")
8469         break
8470
8471       if (utils.TimeoutExpired(last_feedback,
8472                                self._MIGRATION_FEEDBACK_INTERVAL) and
8473           ms.transferred_ram is not None):
8474         mem_progress = 100 * float(ms.transferred_ram) / float(ms.total_ram)
8475         self.feedback_fn("* memory transfer progress: %.2f %%" % mem_progress)
8476         last_feedback = time.time()
8477
8478       time.sleep(self._MIGRATION_POLL_INTERVAL)
8479
8480     result = self.rpc.call_instance_finalize_migration_src(source_node,
8481                                                            instance,
8482                                                            True,
8483                                                            self.live)
8484     msg = result.fail_msg
8485     if msg:
8486       logging.error("Instance migration succeeded, but finalization failed"
8487                     " on the source node: %s", msg)
8488       raise errors.OpExecError("Could not finalize instance migration: %s" %
8489                                msg)
8490
8491     instance.primary_node = target_node
8492
8493     # distribute new instance config to the other nodes
8494     self.cfg.Update(instance, self.feedback_fn)
8495
8496     result = self.rpc.call_instance_finalize_migration_dst(target_node,
8497                                                            instance,
8498                                                            migration_info,
8499                                                            True)
8500     msg = result.fail_msg
8501     if msg:
8502       logging.error("Instance migration succeeded, but finalization failed"
8503                     " on the target node: %s", msg)
8504       raise errors.OpExecError("Could not finalize instance migration: %s" %
8505                                msg)
8506
8507     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
8508       self._EnsureSecondary(source_node)
8509       self._WaitUntilSync()
8510       self._GoStandalone()
8511       self._GoReconnect(False)
8512       self._WaitUntilSync()
8513
8514     # If the instance's disk template is `rbd' and there was a successful
8515     # migration, unmap the device from the source node.
8516     if self.instance.disk_template == constants.DT_RBD:
8517       disks = _ExpandCheckDisks(instance, instance.disks)
8518       self.feedback_fn("* unmapping instance's disks from %s" % source_node)
8519       for disk in disks:
8520         result = self.rpc.call_blockdev_shutdown(source_node, (disk, instance))
8521         msg = result.fail_msg
8522         if msg:
8523           logging.error("Migration was successful, but couldn't unmap the"
8524                         " block device %s on source node %s: %s",
8525                         disk.iv_name, source_node, msg)
8526           logging.error("You need to unmap the device %s manually on %s",
8527                         disk.iv_name, source_node)
8528
8529     self.feedback_fn("* done")
8530
8531   def _ExecFailover(self):
8532     """Failover an instance.
8533
8534     The failover is done by shutting it down on its present node and
8535     starting it on the secondary.
8536
8537     """
8538     instance = self.instance
8539     primary_node = self.cfg.GetNodeInfo(instance.primary_node)
8540
8541     source_node = instance.primary_node
8542     target_node = self.target_node
8543
8544     if instance.admin_state == constants.ADMINST_UP:
8545       self.feedback_fn("* checking disk consistency between source and target")
8546       for (idx, dev) in enumerate(instance.disks):
8547         # for drbd, these are drbd over lvm
8548         if not _CheckDiskConsistency(self.lu, instance, dev, target_node,
8549                                      False):
8550           if primary_node.offline:
8551             self.feedback_fn("Node %s is offline, ignoring degraded disk %s on"
8552                              " target node %s" %
8553                              (primary_node.name, idx, target_node))
8554           elif not self.ignore_consistency:
8555             raise errors.OpExecError("Disk %s is degraded on target node,"
8556                                      " aborting failover" % idx)
8557     else:
8558       self.feedback_fn("* not checking disk consistency as instance is not"
8559                        " running")
8560
8561     self.feedback_fn("* shutting down instance on source node")
8562     logging.info("Shutting down instance %s on node %s",
8563                  instance.name, source_node)
8564
8565     result = self.rpc.call_instance_shutdown(source_node, instance,
8566                                              self.shutdown_timeout)
8567     msg = result.fail_msg
8568     if msg:
8569       if self.ignore_consistency or primary_node.offline:
8570         self.lu.LogWarning("Could not shutdown instance %s on node %s,"
8571                            " proceeding anyway; please make sure node"
8572                            " %s is down; error details: %s",
8573                            instance.name, source_node, source_node, msg)
8574       else:
8575         raise errors.OpExecError("Could not shutdown instance %s on"
8576                                  " node %s: %s" %
8577                                  (instance.name, source_node, msg))
8578
8579     self.feedback_fn("* deactivating the instance's disks on source node")
8580     if not _ShutdownInstanceDisks(self.lu, instance, ignore_primary=True):
8581       raise errors.OpExecError("Can't shut down the instance's disks")
8582
8583     instance.primary_node = target_node
8584     # distribute new instance config to the other nodes
8585     self.cfg.Update(instance, self.feedback_fn)
8586
8587     # Only start the instance if it's marked as up
8588     if instance.admin_state == constants.ADMINST_UP:
8589       self.feedback_fn("* activating the instance's disks on target node %s" %
8590                        target_node)
8591       logging.info("Starting instance %s on node %s",
8592                    instance.name, target_node)
8593
8594       disks_ok, _ = _AssembleInstanceDisks(self.lu, instance,
8595                                            ignore_secondaries=True)
8596       if not disks_ok:
8597         _ShutdownInstanceDisks(self.lu, instance)
8598         raise errors.OpExecError("Can't activate the instance's disks")
8599
8600       self.feedback_fn("* starting the instance on the target node %s" %
8601                        target_node)
8602       result = self.rpc.call_instance_start(target_node, (instance, None, None),
8603                                             False)
8604       msg = result.fail_msg
8605       if msg:
8606         _ShutdownInstanceDisks(self.lu, instance)
8607         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
8608                                  (instance.name, target_node, msg))
8609
8610   def Exec(self, feedback_fn):
8611     """Perform the migration.
8612
8613     """
8614     self.feedback_fn = feedback_fn
8615     self.source_node = self.instance.primary_node
8616
8617     # FIXME: if we implement migrate-to-any in DRBD, this needs fixing
8618     if self.instance.disk_template in constants.DTS_INT_MIRROR:
8619       self.target_node = self.instance.secondary_nodes[0]
8620       # Otherwise self.target_node has been populated either
8621       # directly, or through an iallocator.
8622
8623     self.all_nodes = [self.source_node, self.target_node]
8624     self.nodes_ip = dict((name, node.secondary_ip) for (name, node)
8625                          in self.cfg.GetMultiNodeInfo(self.all_nodes))
8626
8627     if self.failover:
8628       feedback_fn("Failover instance %s" % self.instance.name)
8629       self._ExecFailover()
8630     else:
8631       feedback_fn("Migrating instance %s" % self.instance.name)
8632
8633       if self.cleanup:
8634         return self._ExecCleanup()
8635       else:
8636         return self._ExecMigration()
8637
8638
8639 def _CreateBlockDev(lu, node, instance, device, force_create, info,
8640                     force_open):
8641   """Wrapper around L{_CreateBlockDevInner}.
8642
8643   This method annotates the root device first.
8644
8645   """
8646   (disk,) = _AnnotateDiskParams(instance, [device], lu.cfg)
8647   return _CreateBlockDevInner(lu, node, instance, disk, force_create, info,
8648                               force_open)
8649
8650
8651 def _CreateBlockDevInner(lu, node, instance, device, force_create,
8652                          info, force_open):
8653   """Create a tree of block devices on a given node.
8654
8655   If this device type has to be created on secondaries, create it and
8656   all its children.
8657
8658   If not, just recurse to children keeping the same 'force' value.
8659
8660   @attention: The device has to be annotated already.
8661
8662   @param lu: the lu on whose behalf we execute
8663   @param node: the node on which to create the device
8664   @type instance: L{objects.Instance}
8665   @param instance: the instance which owns the device
8666   @type device: L{objects.Disk}
8667   @param device: the device to create
8668   @type force_create: boolean
8669   @param force_create: whether to force creation of this device; this
8670       will be change to True whenever we find a device which has
8671       CreateOnSecondary() attribute
8672   @param info: the extra 'metadata' we should attach to the device
8673       (this will be represented as a LVM tag)
8674   @type force_open: boolean
8675   @param force_open: this parameter will be passes to the
8676       L{backend.BlockdevCreate} function where it specifies
8677       whether we run on primary or not, and it affects both
8678       the child assembly and the device own Open() execution
8679
8680   """
8681   if device.CreateOnSecondary():
8682     force_create = True
8683
8684   if device.children:
8685     for child in device.children:
8686       _CreateBlockDevInner(lu, node, instance, child, force_create,
8687                            info, force_open)
8688
8689   if not force_create:
8690     return
8691
8692   _CreateSingleBlockDev(lu, node, instance, device, info, force_open)
8693
8694
8695 def _CreateSingleBlockDev(lu, node, instance, device, info, force_open):
8696   """Create a single block device on a given node.
8697
8698   This will not recurse over children of the device, so they must be
8699   created in advance.
8700
8701   @param lu: the lu on whose behalf we execute
8702   @param node: the node on which to create the device
8703   @type instance: L{objects.Instance}
8704   @param instance: the instance which owns the device
8705   @type device: L{objects.Disk}
8706   @param device: the device to create
8707   @param info: the extra 'metadata' we should attach to the device
8708       (this will be represented as a LVM tag)
8709   @type force_open: boolean
8710   @param force_open: this parameter will be passes to the
8711       L{backend.BlockdevCreate} function where it specifies
8712       whether we run on primary or not, and it affects both
8713       the child assembly and the device own Open() execution
8714
8715   """
8716   lu.cfg.SetDiskID(device, node)
8717   result = lu.rpc.call_blockdev_create(node, device, device.size,
8718                                        instance.name, force_open, info)
8719   result.Raise("Can't create block device %s on"
8720                " node %s for instance %s" % (device, node, instance.name))
8721   if device.physical_id is None:
8722     device.physical_id = result.payload
8723
8724
8725 def _GenerateUniqueNames(lu, exts):
8726   """Generate a suitable LV name.
8727
8728   This will generate a logical volume name for the given instance.
8729
8730   """
8731   results = []
8732   for val in exts:
8733     new_id = lu.cfg.GenerateUniqueID(lu.proc.GetECId())
8734     results.append("%s%s" % (new_id, val))
8735   return results
8736
8737
8738 def _GenerateDRBD8Branch(lu, primary, secondary, size, vgnames, names,
8739                          iv_name, p_minor, s_minor):
8740   """Generate a drbd8 device complete with its children.
8741
8742   """
8743   assert len(vgnames) == len(names) == 2
8744   port = lu.cfg.AllocatePort()
8745   shared_secret = lu.cfg.GenerateDRBDSecret(lu.proc.GetECId())
8746
8747   dev_data = objects.Disk(dev_type=constants.LD_LV, size=size,
8748                           logical_id=(vgnames[0], names[0]),
8749                           params={})
8750   dev_meta = objects.Disk(dev_type=constants.LD_LV, size=DRBD_META_SIZE,
8751                           logical_id=(vgnames[1], names[1]),
8752                           params={})
8753   drbd_dev = objects.Disk(dev_type=constants.LD_DRBD8, size=size,
8754                           logical_id=(primary, secondary, port,
8755                                       p_minor, s_minor,
8756                                       shared_secret),
8757                           children=[dev_data, dev_meta],
8758                           iv_name=iv_name, params={})
8759   return drbd_dev
8760
8761
8762 _DISK_TEMPLATE_NAME_PREFIX = {
8763   constants.DT_PLAIN: "",
8764   constants.DT_RBD: ".rbd",
8765   }
8766
8767
8768 _DISK_TEMPLATE_DEVICE_TYPE = {
8769   constants.DT_PLAIN: constants.LD_LV,
8770   constants.DT_FILE: constants.LD_FILE,
8771   constants.DT_SHARED_FILE: constants.LD_FILE,
8772   constants.DT_BLOCK: constants.LD_BLOCKDEV,
8773   constants.DT_RBD: constants.LD_RBD,
8774   }
8775
8776
8777 def _GenerateDiskTemplate(lu, template_name, instance_name, primary_node,
8778     secondary_nodes, disk_info, file_storage_dir, file_driver, base_index,
8779     feedback_fn, full_disk_params, _req_file_storage=opcodes.RequireFileStorage,
8780     _req_shr_file_storage=opcodes.RequireSharedFileStorage):
8781   """Generate the entire disk layout for a given template type.
8782
8783   """
8784   #TODO: compute space requirements
8785
8786   vgname = lu.cfg.GetVGName()
8787   disk_count = len(disk_info)
8788   disks = []
8789
8790   if template_name == constants.DT_DISKLESS:
8791     pass
8792   elif template_name == constants.DT_DRBD8:
8793     if len(secondary_nodes) != 1:
8794       raise errors.ProgrammerError("Wrong template configuration")
8795     remote_node = secondary_nodes[0]
8796     minors = lu.cfg.AllocateDRBDMinor(
8797       [primary_node, remote_node] * len(disk_info), instance_name)
8798
8799     (drbd_params, _, _) = objects.Disk.ComputeLDParams(template_name,
8800                                                        full_disk_params)
8801     drbd_default_metavg = drbd_params[constants.LDP_DEFAULT_METAVG]
8802
8803     names = []
8804     for lv_prefix in _GenerateUniqueNames(lu, [".disk%d" % (base_index + i)
8805                                                for i in range(disk_count)]):
8806       names.append(lv_prefix + "_data")
8807       names.append(lv_prefix + "_meta")
8808     for idx, disk in enumerate(disk_info):
8809       disk_index = idx + base_index
8810       data_vg = disk.get(constants.IDISK_VG, vgname)
8811       meta_vg = disk.get(constants.IDISK_METAVG, drbd_default_metavg)
8812       disk_dev = _GenerateDRBD8Branch(lu, primary_node, remote_node,
8813                                       disk[constants.IDISK_SIZE],
8814                                       [data_vg, meta_vg],
8815                                       names[idx * 2:idx * 2 + 2],
8816                                       "disk/%d" % disk_index,
8817                                       minors[idx * 2], minors[idx * 2 + 1])
8818       disk_dev.mode = disk[constants.IDISK_MODE]
8819       disks.append(disk_dev)
8820   else:
8821     if secondary_nodes:
8822       raise errors.ProgrammerError("Wrong template configuration")
8823
8824     if template_name == constants.DT_FILE:
8825       _req_file_storage()
8826     elif template_name == constants.DT_SHARED_FILE:
8827       _req_shr_file_storage()
8828
8829     name_prefix = _DISK_TEMPLATE_NAME_PREFIX.get(template_name, None)
8830     if name_prefix is None:
8831       names = None
8832     else:
8833       names = _GenerateUniqueNames(lu, ["%s.disk%s" %
8834                                         (name_prefix, base_index + i)
8835                                         for i in range(disk_count)])
8836
8837     if template_name == constants.DT_PLAIN:
8838       def logical_id_fn(idx, _, disk):
8839         vg = disk.get(constants.IDISK_VG, vgname)
8840         return (vg, names[idx])
8841     elif template_name in (constants.DT_FILE, constants.DT_SHARED_FILE):
8842       logical_id_fn = \
8843         lambda _, disk_index, disk: (file_driver,
8844                                      "%s/disk%d" % (file_storage_dir,
8845                                                     disk_index))
8846     elif template_name == constants.DT_BLOCK:
8847       logical_id_fn = \
8848         lambda idx, disk_index, disk: (constants.BLOCKDEV_DRIVER_MANUAL,
8849                                        disk[constants.IDISK_ADOPT])
8850     elif template_name == constants.DT_RBD:
8851       logical_id_fn = lambda idx, _, disk: ("rbd", names[idx])
8852     else:
8853       raise errors.ProgrammerError("Unknown disk template '%s'" % template_name)
8854
8855     dev_type = _DISK_TEMPLATE_DEVICE_TYPE[template_name]
8856
8857     for idx, disk in enumerate(disk_info):
8858       disk_index = idx + base_index
8859       size = disk[constants.IDISK_SIZE]
8860       feedback_fn("* disk %s, size %s" %
8861                   (disk_index, utils.FormatUnit(size, "h")))
8862       disks.append(objects.Disk(dev_type=dev_type, size=size,
8863                                 logical_id=logical_id_fn(idx, disk_index, disk),
8864                                 iv_name="disk/%d" % disk_index,
8865                                 mode=disk[constants.IDISK_MODE],
8866                                 params={}))
8867
8868   return disks
8869
8870
8871 def _GetInstanceInfoText(instance):
8872   """Compute that text that should be added to the disk's metadata.
8873
8874   """
8875   return "originstname+%s" % instance.name
8876
8877
8878 def _CalcEta(time_taken, written, total_size):
8879   """Calculates the ETA based on size written and total size.
8880
8881   @param time_taken: The time taken so far
8882   @param written: amount written so far
8883   @param total_size: The total size of data to be written
8884   @return: The remaining time in seconds
8885
8886   """
8887   avg_time = time_taken / float(written)
8888   return (total_size - written) * avg_time
8889
8890
8891 def _WipeDisks(lu, instance):
8892   """Wipes instance disks.
8893
8894   @type lu: L{LogicalUnit}
8895   @param lu: the logical unit on whose behalf we execute
8896   @type instance: L{objects.Instance}
8897   @param instance: the instance whose disks we should create
8898   @return: the success of the wipe
8899
8900   """
8901   node = instance.primary_node
8902
8903   for device in instance.disks:
8904     lu.cfg.SetDiskID(device, node)
8905
8906   logging.info("Pause sync of instance %s disks", instance.name)
8907   result = lu.rpc.call_blockdev_pause_resume_sync(node,
8908                                                   (instance.disks, instance),
8909                                                   True)
8910
8911   for idx, success in enumerate(result.payload):
8912     if not success:
8913       logging.warn("pause-sync of instance %s for disks %d failed",
8914                    instance.name, idx)
8915
8916   try:
8917     for idx, device in enumerate(instance.disks):
8918       # The wipe size is MIN_WIPE_CHUNK_PERCENT % of the instance disk but
8919       # MAX_WIPE_CHUNK at max
8920       wipe_chunk_size = min(constants.MAX_WIPE_CHUNK, device.size / 100.0 *
8921                             constants.MIN_WIPE_CHUNK_PERCENT)
8922       # we _must_ make this an int, otherwise rounding errors will
8923       # occur
8924       wipe_chunk_size = int(wipe_chunk_size)
8925
8926       lu.LogInfo("* Wiping disk %d", idx)
8927       logging.info("Wiping disk %d for instance %s, node %s using"
8928                    " chunk size %s", idx, instance.name, node, wipe_chunk_size)
8929
8930       offset = 0
8931       size = device.size
8932       last_output = 0
8933       start_time = time.time()
8934
8935       while offset < size:
8936         wipe_size = min(wipe_chunk_size, size - offset)
8937         logging.debug("Wiping disk %d, offset %s, chunk %s",
8938                       idx, offset, wipe_size)
8939         result = lu.rpc.call_blockdev_wipe(node, (device, instance), offset,
8940                                            wipe_size)
8941         result.Raise("Could not wipe disk %d at offset %d for size %d" %
8942                      (idx, offset, wipe_size))
8943         now = time.time()
8944         offset += wipe_size
8945         if now - last_output >= 60:
8946           eta = _CalcEta(now - start_time, offset, size)
8947           lu.LogInfo(" - done: %.1f%% ETA: %s" %
8948                      (offset / float(size) * 100, utils.FormatSeconds(eta)))
8949           last_output = now
8950   finally:
8951     logging.info("Resume sync of instance %s disks", instance.name)
8952
8953     result = lu.rpc.call_blockdev_pause_resume_sync(node,
8954                                                     (instance.disks, instance),
8955                                                     False)
8956
8957     for idx, success in enumerate(result.payload):
8958       if not success:
8959         lu.LogWarning("Resume sync of disk %d failed, please have a"
8960                       " look at the status and troubleshoot the issue", idx)
8961         logging.warn("resume-sync of instance %s for disks %d failed",
8962                      instance.name, idx)
8963
8964
8965 def _CreateDisks(lu, instance, to_skip=None, target_node=None):
8966   """Create all disks for an instance.
8967
8968   This abstracts away some work from AddInstance.
8969
8970   @type lu: L{LogicalUnit}
8971   @param lu: the logical unit on whose behalf we execute
8972   @type instance: L{objects.Instance}
8973   @param instance: the instance whose disks we should create
8974   @type to_skip: list
8975   @param to_skip: list of indices to skip
8976   @type target_node: string
8977   @param target_node: if passed, overrides the target node for creation
8978   @rtype: boolean
8979   @return: the success of the creation
8980
8981   """
8982   info = _GetInstanceInfoText(instance)
8983   if target_node is None:
8984     pnode = instance.primary_node
8985     all_nodes = instance.all_nodes
8986   else:
8987     pnode = target_node
8988     all_nodes = [pnode]
8989
8990   if instance.disk_template in constants.DTS_FILEBASED:
8991     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
8992     result = lu.rpc.call_file_storage_dir_create(pnode, file_storage_dir)
8993
8994     result.Raise("Failed to create directory '%s' on"
8995                  " node %s" % (file_storage_dir, pnode))
8996
8997   # Note: this needs to be kept in sync with adding of disks in
8998   # LUInstanceSetParams
8999   for idx, device in enumerate(instance.disks):
9000     if to_skip and idx in to_skip:
9001       continue
9002     logging.info("Creating disk %s for instance '%s'", idx, instance.name)
9003     #HARDCODE
9004     for node in all_nodes:
9005       f_create = node == pnode
9006       _CreateBlockDev(lu, node, instance, device, f_create, info, f_create)
9007
9008
9009 def _RemoveDisks(lu, instance, target_node=None, ignore_failures=False):
9010   """Remove all disks for an instance.
9011
9012   This abstracts away some work from `AddInstance()` and
9013   `RemoveInstance()`. Note that in case some of the devices couldn't
9014   be removed, the removal will continue with the other ones (compare
9015   with `_CreateDisks()`).
9016
9017   @type lu: L{LogicalUnit}
9018   @param lu: the logical unit on whose behalf we execute
9019   @type instance: L{objects.Instance}
9020   @param instance: the instance whose disks we should remove
9021   @type target_node: string
9022   @param target_node: used to override the node on which to remove the disks
9023   @rtype: boolean
9024   @return: the success of the removal
9025
9026   """
9027   logging.info("Removing block devices for instance %s", instance.name)
9028
9029   all_result = True
9030   ports_to_release = set()
9031   anno_disks = _AnnotateDiskParams(instance, instance.disks, lu.cfg)
9032   for (idx, device) in enumerate(anno_disks):
9033     if target_node:
9034       edata = [(target_node, device)]
9035     else:
9036       edata = device.ComputeNodeTree(instance.primary_node)
9037     for node, disk in edata:
9038       lu.cfg.SetDiskID(disk, node)
9039       result = lu.rpc.call_blockdev_remove(node, disk)
9040       if result.fail_msg:
9041         lu.LogWarning("Could not remove disk %s on node %s,"
9042                       " continuing anyway: %s", idx, node, result.fail_msg)
9043         if not (result.offline and node != instance.primary_node):
9044           all_result = False
9045
9046     # if this is a DRBD disk, return its port to the pool
9047     if device.dev_type in constants.LDS_DRBD:
9048       ports_to_release.add(device.logical_id[2])
9049
9050   if all_result or ignore_failures:
9051     for port in ports_to_release:
9052       lu.cfg.AddTcpUdpPort(port)
9053
9054   if instance.disk_template == constants.DT_FILE:
9055     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
9056     if target_node:
9057       tgt = target_node
9058     else:
9059       tgt = instance.primary_node
9060     result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
9061     if result.fail_msg:
9062       lu.LogWarning("Could not remove directory '%s' on node %s: %s",
9063                     file_storage_dir, instance.primary_node, result.fail_msg)
9064       all_result = False
9065
9066   return all_result
9067
9068
9069 def _ComputeDiskSizePerVG(disk_template, disks):
9070   """Compute disk size requirements in the volume group
9071
9072   """
9073   def _compute(disks, payload):
9074     """Universal algorithm.
9075
9076     """
9077     vgs = {}
9078     for disk in disks:
9079       vgs[disk[constants.IDISK_VG]] = \
9080         vgs.get(constants.IDISK_VG, 0) + disk[constants.IDISK_SIZE] + payload
9081
9082     return vgs
9083
9084   # Required free disk space as a function of disk and swap space
9085   req_size_dict = {
9086     constants.DT_DISKLESS: {},
9087     constants.DT_PLAIN: _compute(disks, 0),
9088     # 128 MB are added for drbd metadata for each disk
9089     constants.DT_DRBD8: _compute(disks, DRBD_META_SIZE),
9090     constants.DT_FILE: {},
9091     constants.DT_SHARED_FILE: {},
9092   }
9093
9094   if disk_template not in req_size_dict:
9095     raise errors.ProgrammerError("Disk template '%s' size requirement"
9096                                  " is unknown" % disk_template)
9097
9098   return req_size_dict[disk_template]
9099
9100
9101 def _ComputeDiskSize(disk_template, disks):
9102   """Compute disk size requirements in the volume group
9103
9104   """
9105   # Required free disk space as a function of disk and swap space
9106   req_size_dict = {
9107     constants.DT_DISKLESS: None,
9108     constants.DT_PLAIN: sum(d[constants.IDISK_SIZE] for d in disks),
9109     # 128 MB are added for drbd metadata for each disk
9110     constants.DT_DRBD8:
9111       sum(d[constants.IDISK_SIZE] + DRBD_META_SIZE for d in disks),
9112     constants.DT_FILE: None,
9113     constants.DT_SHARED_FILE: 0,
9114     constants.DT_BLOCK: 0,
9115     constants.DT_RBD: 0,
9116   }
9117
9118   if disk_template not in req_size_dict:
9119     raise errors.ProgrammerError("Disk template '%s' size requirement"
9120                                  " is unknown" % disk_template)
9121
9122   return req_size_dict[disk_template]
9123
9124
9125 def _FilterVmNodes(lu, nodenames):
9126   """Filters out non-vm_capable nodes from a list.
9127
9128   @type lu: L{LogicalUnit}
9129   @param lu: the logical unit for which we check
9130   @type nodenames: list
9131   @param nodenames: the list of nodes on which we should check
9132   @rtype: list
9133   @return: the list of vm-capable nodes
9134
9135   """
9136   vm_nodes = frozenset(lu.cfg.GetNonVmCapableNodeList())
9137   return [name for name in nodenames if name not in vm_nodes]
9138
9139
9140 def _CheckHVParams(lu, nodenames, hvname, hvparams):
9141   """Hypervisor parameter validation.
9142
9143   This function abstract the hypervisor parameter validation to be
9144   used in both instance create and instance modify.
9145
9146   @type lu: L{LogicalUnit}
9147   @param lu: the logical unit for which we check
9148   @type nodenames: list
9149   @param nodenames: the list of nodes on which we should check
9150   @type hvname: string
9151   @param hvname: the name of the hypervisor we should use
9152   @type hvparams: dict
9153   @param hvparams: the parameters which we need to check
9154   @raise errors.OpPrereqError: if the parameters are not valid
9155
9156   """
9157   nodenames = _FilterVmNodes(lu, nodenames)
9158
9159   cluster = lu.cfg.GetClusterInfo()
9160   hvfull = objects.FillDict(cluster.hvparams.get(hvname, {}), hvparams)
9161
9162   hvinfo = lu.rpc.call_hypervisor_validate_params(nodenames, hvname, hvfull)
9163   for node in nodenames:
9164     info = hvinfo[node]
9165     if info.offline:
9166       continue
9167     info.Raise("Hypervisor parameter validation failed on node %s" % node)
9168
9169
9170 def _CheckOSParams(lu, required, nodenames, osname, osparams):
9171   """OS parameters validation.
9172
9173   @type lu: L{LogicalUnit}
9174   @param lu: the logical unit for which we check
9175   @type required: boolean
9176   @param required: whether the validation should fail if the OS is not
9177       found
9178   @type nodenames: list
9179   @param nodenames: the list of nodes on which we should check
9180   @type osname: string
9181   @param osname: the name of the hypervisor we should use
9182   @type osparams: dict
9183   @param osparams: the parameters which we need to check
9184   @raise errors.OpPrereqError: if the parameters are not valid
9185
9186   """
9187   nodenames = _FilterVmNodes(lu, nodenames)
9188   result = lu.rpc.call_os_validate(nodenames, required, osname,
9189                                    [constants.OS_VALIDATE_PARAMETERS],
9190                                    osparams)
9191   for node, nres in result.items():
9192     # we don't check for offline cases since this should be run only
9193     # against the master node and/or an instance's nodes
9194     nres.Raise("OS Parameters validation failed on node %s" % node)
9195     if not nres.payload:
9196       lu.LogInfo("OS %s not found on node %s, validation skipped",
9197                  osname, node)
9198
9199
9200 class LUInstanceCreate(LogicalUnit):
9201   """Create an instance.
9202
9203   """
9204   HPATH = "instance-add"
9205   HTYPE = constants.HTYPE_INSTANCE
9206   REQ_BGL = False
9207
9208   def CheckArguments(self):
9209     """Check arguments.
9210
9211     """
9212     # do not require name_check to ease forward/backward compatibility
9213     # for tools
9214     if self.op.no_install and self.op.start:
9215       self.LogInfo("No-installation mode selected, disabling startup")
9216       self.op.start = False
9217     # validate/normalize the instance name
9218     self.op.instance_name = \
9219       netutils.Hostname.GetNormalizedName(self.op.instance_name)
9220
9221     if self.op.ip_check and not self.op.name_check:
9222       # TODO: make the ip check more flexible and not depend on the name check
9223       raise errors.OpPrereqError("Cannot do IP address check without a name"
9224                                  " check", errors.ECODE_INVAL)
9225
9226     # check nics' parameter names
9227     for nic in self.op.nics:
9228       utils.ForceDictType(nic, constants.INIC_PARAMS_TYPES)
9229
9230     # check disks. parameter names and consistent adopt/no-adopt strategy
9231     has_adopt = has_no_adopt = False
9232     for disk in self.op.disks:
9233       utils.ForceDictType(disk, constants.IDISK_PARAMS_TYPES)
9234       if constants.IDISK_ADOPT in disk:
9235         has_adopt = True
9236       else:
9237         has_no_adopt = True
9238     if has_adopt and has_no_adopt:
9239       raise errors.OpPrereqError("Either all disks are adopted or none is",
9240                                  errors.ECODE_INVAL)
9241     if has_adopt:
9242       if self.op.disk_template not in constants.DTS_MAY_ADOPT:
9243         raise errors.OpPrereqError("Disk adoption is not supported for the"
9244                                    " '%s' disk template" %
9245                                    self.op.disk_template,
9246                                    errors.ECODE_INVAL)
9247       if self.op.iallocator is not None:
9248         raise errors.OpPrereqError("Disk adoption not allowed with an"
9249                                    " iallocator script", errors.ECODE_INVAL)
9250       if self.op.mode == constants.INSTANCE_IMPORT:
9251         raise errors.OpPrereqError("Disk adoption not allowed for"
9252                                    " instance import", errors.ECODE_INVAL)
9253     else:
9254       if self.op.disk_template in constants.DTS_MUST_ADOPT:
9255         raise errors.OpPrereqError("Disk template %s requires disk adoption,"
9256                                    " but no 'adopt' parameter given" %
9257                                    self.op.disk_template,
9258                                    errors.ECODE_INVAL)
9259
9260     self.adopt_disks = has_adopt
9261
9262     # instance name verification
9263     if self.op.name_check:
9264       self.hostname1 = netutils.GetHostname(name=self.op.instance_name)
9265       self.op.instance_name = self.hostname1.name
9266       # used in CheckPrereq for ip ping check
9267       self.check_ip = self.hostname1.ip
9268     else:
9269       self.check_ip = None
9270
9271     # file storage checks
9272     if (self.op.file_driver and
9273         not self.op.file_driver in constants.FILE_DRIVER):
9274       raise errors.OpPrereqError("Invalid file driver name '%s'" %
9275                                  self.op.file_driver, errors.ECODE_INVAL)
9276
9277     if self.op.disk_template == constants.DT_FILE:
9278       opcodes.RequireFileStorage()
9279     elif self.op.disk_template == constants.DT_SHARED_FILE:
9280       opcodes.RequireSharedFileStorage()
9281
9282     ### Node/iallocator related checks
9283     _CheckIAllocatorOrNode(self, "iallocator", "pnode")
9284
9285     if self.op.pnode is not None:
9286       if self.op.disk_template in constants.DTS_INT_MIRROR:
9287         if self.op.snode is None:
9288           raise errors.OpPrereqError("The networked disk templates need"
9289                                      " a mirror node", errors.ECODE_INVAL)
9290       elif self.op.snode:
9291         self.LogWarning("Secondary node will be ignored on non-mirrored disk"
9292                         " template")
9293         self.op.snode = None
9294
9295     self._cds = _GetClusterDomainSecret()
9296
9297     if self.op.mode == constants.INSTANCE_IMPORT:
9298       # On import force_variant must be True, because if we forced it at
9299       # initial install, our only chance when importing it back is that it
9300       # works again!
9301       self.op.force_variant = True
9302
9303       if self.op.no_install:
9304         self.LogInfo("No-installation mode has no effect during import")
9305
9306     elif self.op.mode == constants.INSTANCE_CREATE:
9307       if self.op.os_type is None:
9308         raise errors.OpPrereqError("No guest OS specified",
9309                                    errors.ECODE_INVAL)
9310       if self.op.os_type in self.cfg.GetClusterInfo().blacklisted_os:
9311         raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
9312                                    " installation" % self.op.os_type,
9313                                    errors.ECODE_STATE)
9314       if self.op.disk_template is None:
9315         raise errors.OpPrereqError("No disk template specified",
9316                                    errors.ECODE_INVAL)
9317
9318     elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
9319       # Check handshake to ensure both clusters have the same domain secret
9320       src_handshake = self.op.source_handshake
9321       if not src_handshake:
9322         raise errors.OpPrereqError("Missing source handshake",
9323                                    errors.ECODE_INVAL)
9324
9325       errmsg = masterd.instance.CheckRemoteExportHandshake(self._cds,
9326                                                            src_handshake)
9327       if errmsg:
9328         raise errors.OpPrereqError("Invalid handshake: %s" % errmsg,
9329                                    errors.ECODE_INVAL)
9330
9331       # Load and check source CA
9332       self.source_x509_ca_pem = self.op.source_x509_ca
9333       if not self.source_x509_ca_pem:
9334         raise errors.OpPrereqError("Missing source X509 CA",
9335                                    errors.ECODE_INVAL)
9336
9337       try:
9338         (cert, _) = utils.LoadSignedX509Certificate(self.source_x509_ca_pem,
9339                                                     self._cds)
9340       except OpenSSL.crypto.Error, err:
9341         raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
9342                                    (err, ), errors.ECODE_INVAL)
9343
9344       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
9345       if errcode is not None:
9346         raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
9347                                    errors.ECODE_INVAL)
9348
9349       self.source_x509_ca = cert
9350
9351       src_instance_name = self.op.source_instance_name
9352       if not src_instance_name:
9353         raise errors.OpPrereqError("Missing source instance name",
9354                                    errors.ECODE_INVAL)
9355
9356       self.source_instance_name = \
9357           netutils.GetHostname(name=src_instance_name).name
9358
9359     else:
9360       raise errors.OpPrereqError("Invalid instance creation mode %r" %
9361                                  self.op.mode, errors.ECODE_INVAL)
9362
9363   def ExpandNames(self):
9364     """ExpandNames for CreateInstance.
9365
9366     Figure out the right locks for instance creation.
9367
9368     """
9369     self.needed_locks = {}
9370
9371     instance_name = self.op.instance_name
9372     # this is just a preventive check, but someone might still add this
9373     # instance in the meantime, and creation will fail at lock-add time
9374     if instance_name in self.cfg.GetInstanceList():
9375       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
9376                                  instance_name, errors.ECODE_EXISTS)
9377
9378     self.add_locks[locking.LEVEL_INSTANCE] = instance_name
9379
9380     if self.op.iallocator:
9381       # TODO: Find a solution to not lock all nodes in the cluster, e.g. by
9382       # specifying a group on instance creation and then selecting nodes from
9383       # that group
9384       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9385       self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
9386     else:
9387       self.op.pnode = _ExpandNodeName(self.cfg, self.op.pnode)
9388       nodelist = [self.op.pnode]
9389       if self.op.snode is not None:
9390         self.op.snode = _ExpandNodeName(self.cfg, self.op.snode)
9391         nodelist.append(self.op.snode)
9392       self.needed_locks[locking.LEVEL_NODE] = nodelist
9393       # Lock resources of instance's primary and secondary nodes (copy to
9394       # prevent accidential modification)
9395       self.needed_locks[locking.LEVEL_NODE_RES] = list(nodelist)
9396
9397     # in case of import lock the source node too
9398     if self.op.mode == constants.INSTANCE_IMPORT:
9399       src_node = self.op.src_node
9400       src_path = self.op.src_path
9401
9402       if src_path is None:
9403         self.op.src_path = src_path = self.op.instance_name
9404
9405       if src_node is None:
9406         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9407         self.op.src_node = None
9408         if os.path.isabs(src_path):
9409           raise errors.OpPrereqError("Importing an instance from a path"
9410                                      " requires a source node option",
9411                                      errors.ECODE_INVAL)
9412       else:
9413         self.op.src_node = src_node = _ExpandNodeName(self.cfg, src_node)
9414         if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
9415           self.needed_locks[locking.LEVEL_NODE].append(src_node)
9416         if not os.path.isabs(src_path):
9417           self.op.src_path = src_path = \
9418             utils.PathJoin(constants.EXPORT_DIR, src_path)
9419
9420   def _RunAllocator(self):
9421     """Run the allocator based on input opcode.
9422
9423     """
9424     nics = [n.ToDict() for n in self.nics]
9425     ial = IAllocator(self.cfg, self.rpc,
9426                      mode=constants.IALLOCATOR_MODE_ALLOC,
9427                      name=self.op.instance_name,
9428                      disk_template=self.op.disk_template,
9429                      tags=self.op.tags,
9430                      os=self.op.os_type,
9431                      vcpus=self.be_full[constants.BE_VCPUS],
9432                      memory=self.be_full[constants.BE_MAXMEM],
9433                      spindle_use=self.be_full[constants.BE_SPINDLE_USE],
9434                      disks=self.disks,
9435                      nics=nics,
9436                      hypervisor=self.op.hypervisor,
9437                      )
9438
9439     ial.Run(self.op.iallocator)
9440
9441     if not ial.success:
9442       raise errors.OpPrereqError("Can't compute nodes using"
9443                                  " iallocator '%s': %s" %
9444                                  (self.op.iallocator, ial.info),
9445                                  errors.ECODE_NORES)
9446     if len(ial.result) != ial.required_nodes:
9447       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
9448                                  " of nodes (%s), required %s" %
9449                                  (self.op.iallocator, len(ial.result),
9450                                   ial.required_nodes), errors.ECODE_FAULT)
9451     self.op.pnode = ial.result[0]
9452     self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
9453                  self.op.instance_name, self.op.iallocator,
9454                  utils.CommaJoin(ial.result))
9455     if ial.required_nodes == 2:
9456       self.op.snode = ial.result[1]
9457
9458   def BuildHooksEnv(self):
9459     """Build hooks env.
9460
9461     This runs on master, primary and secondary nodes of the instance.
9462
9463     """
9464     env = {
9465       "ADD_MODE": self.op.mode,
9466       }
9467     if self.op.mode == constants.INSTANCE_IMPORT:
9468       env["SRC_NODE"] = self.op.src_node
9469       env["SRC_PATH"] = self.op.src_path
9470       env["SRC_IMAGES"] = self.src_images
9471
9472     env.update(_BuildInstanceHookEnv(
9473       name=self.op.instance_name,
9474       primary_node=self.op.pnode,
9475       secondary_nodes=self.secondaries,
9476       status=self.op.start,
9477       os_type=self.op.os_type,
9478       minmem=self.be_full[constants.BE_MINMEM],
9479       maxmem=self.be_full[constants.BE_MAXMEM],
9480       vcpus=self.be_full[constants.BE_VCPUS],
9481       nics=_NICListToTuple(self, self.nics),
9482       disk_template=self.op.disk_template,
9483       disks=[(d[constants.IDISK_SIZE], d[constants.IDISK_MODE])
9484              for d in self.disks],
9485       bep=self.be_full,
9486       hvp=self.hv_full,
9487       hypervisor_name=self.op.hypervisor,
9488       tags=self.op.tags,
9489     ))
9490
9491     return env
9492
9493   def BuildHooksNodes(self):
9494     """Build hooks nodes.
9495
9496     """
9497     nl = [self.cfg.GetMasterNode(), self.op.pnode] + self.secondaries
9498     return nl, nl
9499
9500   def _ReadExportInfo(self):
9501     """Reads the export information from disk.
9502
9503     It will override the opcode source node and path with the actual
9504     information, if these two were not specified before.
9505
9506     @return: the export information
9507
9508     """
9509     assert self.op.mode == constants.INSTANCE_IMPORT
9510
9511     src_node = self.op.src_node
9512     src_path = self.op.src_path
9513
9514     if src_node is None:
9515       locked_nodes = self.owned_locks(locking.LEVEL_NODE)
9516       exp_list = self.rpc.call_export_list(locked_nodes)
9517       found = False
9518       for node in exp_list:
9519         if exp_list[node].fail_msg:
9520           continue
9521         if src_path in exp_list[node].payload:
9522           found = True
9523           self.op.src_node = src_node = node
9524           self.op.src_path = src_path = utils.PathJoin(constants.EXPORT_DIR,
9525                                                        src_path)
9526           break
9527       if not found:
9528         raise errors.OpPrereqError("No export found for relative path %s" %
9529                                     src_path, errors.ECODE_INVAL)
9530
9531     _CheckNodeOnline(self, src_node)
9532     result = self.rpc.call_export_info(src_node, src_path)
9533     result.Raise("No export or invalid export found in dir %s" % src_path)
9534
9535     export_info = objects.SerializableConfigParser.Loads(str(result.payload))
9536     if not export_info.has_section(constants.INISECT_EXP):
9537       raise errors.ProgrammerError("Corrupted export config",
9538                                    errors.ECODE_ENVIRON)
9539
9540     ei_version = export_info.get(constants.INISECT_EXP, "version")
9541     if (int(ei_version) != constants.EXPORT_VERSION):
9542       raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
9543                                  (ei_version, constants.EXPORT_VERSION),
9544                                  errors.ECODE_ENVIRON)
9545     return export_info
9546
9547   def _ReadExportParams(self, einfo):
9548     """Use export parameters as defaults.
9549
9550     In case the opcode doesn't specify (as in override) some instance
9551     parameters, then try to use them from the export information, if
9552     that declares them.
9553
9554     """
9555     self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
9556
9557     if self.op.disk_template is None:
9558       if einfo.has_option(constants.INISECT_INS, "disk_template"):
9559         self.op.disk_template = einfo.get(constants.INISECT_INS,
9560                                           "disk_template")
9561         if self.op.disk_template not in constants.DISK_TEMPLATES:
9562           raise errors.OpPrereqError("Disk template specified in configuration"
9563                                      " file is not one of the allowed values:"
9564                                      " %s" % " ".join(constants.DISK_TEMPLATES))
9565       else:
9566         raise errors.OpPrereqError("No disk template specified and the export"
9567                                    " is missing the disk_template information",
9568                                    errors.ECODE_INVAL)
9569
9570     if not self.op.disks:
9571       disks = []
9572       # TODO: import the disk iv_name too
9573       for idx in range(constants.MAX_DISKS):
9574         if einfo.has_option(constants.INISECT_INS, "disk%d_size" % idx):
9575           disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
9576           disks.append({constants.IDISK_SIZE: disk_sz})
9577       self.op.disks = disks
9578       if not disks and self.op.disk_template != constants.DT_DISKLESS:
9579         raise errors.OpPrereqError("No disk info specified and the export"
9580                                    " is missing the disk information",
9581                                    errors.ECODE_INVAL)
9582
9583     if not self.op.nics:
9584       nics = []
9585       for idx in range(constants.MAX_NICS):
9586         if einfo.has_option(constants.INISECT_INS, "nic%d_mac" % idx):
9587           ndict = {}
9588           for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
9589             v = einfo.get(constants.INISECT_INS, "nic%d_%s" % (idx, name))
9590             ndict[name] = v
9591           nics.append(ndict)
9592         else:
9593           break
9594       self.op.nics = nics
9595
9596     if not self.op.tags and einfo.has_option(constants.INISECT_INS, "tags"):
9597       self.op.tags = einfo.get(constants.INISECT_INS, "tags").split()
9598
9599     if (self.op.hypervisor is None and
9600         einfo.has_option(constants.INISECT_INS, "hypervisor")):
9601       self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
9602
9603     if einfo.has_section(constants.INISECT_HYP):
9604       # use the export parameters but do not override the ones
9605       # specified by the user
9606       for name, value in einfo.items(constants.INISECT_HYP):
9607         if name not in self.op.hvparams:
9608           self.op.hvparams[name] = value
9609
9610     if einfo.has_section(constants.INISECT_BEP):
9611       # use the parameters, without overriding
9612       for name, value in einfo.items(constants.INISECT_BEP):
9613         if name not in self.op.beparams:
9614           self.op.beparams[name] = value
9615         # Compatibility for the old "memory" be param
9616         if name == constants.BE_MEMORY:
9617           if constants.BE_MAXMEM not in self.op.beparams:
9618             self.op.beparams[constants.BE_MAXMEM] = value
9619           if constants.BE_MINMEM not in self.op.beparams:
9620             self.op.beparams[constants.BE_MINMEM] = value
9621     else:
9622       # try to read the parameters old style, from the main section
9623       for name in constants.BES_PARAMETERS:
9624         if (name not in self.op.beparams and
9625             einfo.has_option(constants.INISECT_INS, name)):
9626           self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
9627
9628     if einfo.has_section(constants.INISECT_OSP):
9629       # use the parameters, without overriding
9630       for name, value in einfo.items(constants.INISECT_OSP):
9631         if name not in self.op.osparams:
9632           self.op.osparams[name] = value
9633
9634   def _RevertToDefaults(self, cluster):
9635     """Revert the instance parameters to the default values.
9636
9637     """
9638     # hvparams
9639     hv_defs = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type, {})
9640     for name in self.op.hvparams.keys():
9641       if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
9642         del self.op.hvparams[name]
9643     # beparams
9644     be_defs = cluster.SimpleFillBE({})
9645     for name in self.op.beparams.keys():
9646       if name in be_defs and be_defs[name] == self.op.beparams[name]:
9647         del self.op.beparams[name]
9648     # nic params
9649     nic_defs = cluster.SimpleFillNIC({})
9650     for nic in self.op.nics:
9651       for name in constants.NICS_PARAMETERS:
9652         if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
9653           del nic[name]
9654     # osparams
9655     os_defs = cluster.SimpleFillOS(self.op.os_type, {})
9656     for name in self.op.osparams.keys():
9657       if name in os_defs and os_defs[name] == self.op.osparams[name]:
9658         del self.op.osparams[name]
9659
9660   def _CalculateFileStorageDir(self):
9661     """Calculate final instance file storage dir.
9662
9663     """
9664     # file storage dir calculation/check
9665     self.instance_file_storage_dir = None
9666     if self.op.disk_template in constants.DTS_FILEBASED:
9667       # build the full file storage dir path
9668       joinargs = []
9669
9670       if self.op.disk_template == constants.DT_SHARED_FILE:
9671         get_fsd_fn = self.cfg.GetSharedFileStorageDir
9672       else:
9673         get_fsd_fn = self.cfg.GetFileStorageDir
9674
9675       cfg_storagedir = get_fsd_fn()
9676       if not cfg_storagedir:
9677         raise errors.OpPrereqError("Cluster file storage dir not defined")
9678       joinargs.append(cfg_storagedir)
9679
9680       if self.op.file_storage_dir is not None:
9681         joinargs.append(self.op.file_storage_dir)
9682
9683       joinargs.append(self.op.instance_name)
9684
9685       # pylint: disable=W0142
9686       self.instance_file_storage_dir = utils.PathJoin(*joinargs)
9687
9688   def CheckPrereq(self): # pylint: disable=R0914
9689     """Check prerequisites.
9690
9691     """
9692     self._CalculateFileStorageDir()
9693
9694     if self.op.mode == constants.INSTANCE_IMPORT:
9695       export_info = self._ReadExportInfo()
9696       self._ReadExportParams(export_info)
9697       self._old_instance_name = export_info.get(constants.INISECT_INS, "name")
9698     else:
9699       self._old_instance_name = None
9700
9701     if (not self.cfg.GetVGName() and
9702         self.op.disk_template not in constants.DTS_NOT_LVM):
9703       raise errors.OpPrereqError("Cluster does not support lvm-based"
9704                                  " instances", errors.ECODE_STATE)
9705
9706     if (self.op.hypervisor is None or
9707         self.op.hypervisor == constants.VALUE_AUTO):
9708       self.op.hypervisor = self.cfg.GetHypervisorType()
9709
9710     cluster = self.cfg.GetClusterInfo()
9711     enabled_hvs = cluster.enabled_hypervisors
9712     if self.op.hypervisor not in enabled_hvs:
9713       raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
9714                                  " cluster (%s)" % (self.op.hypervisor,
9715                                   ",".join(enabled_hvs)),
9716                                  errors.ECODE_STATE)
9717
9718     # Check tag validity
9719     for tag in self.op.tags:
9720       objects.TaggableObject.ValidateTag(tag)
9721
9722     # check hypervisor parameter syntax (locally)
9723     utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
9724     filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type,
9725                                       self.op.hvparams)
9726     hv_type = hypervisor.GetHypervisor(self.op.hypervisor)
9727     hv_type.CheckParameterSyntax(filled_hvp)
9728     self.hv_full = filled_hvp
9729     # check that we don't specify global parameters on an instance
9730     _CheckGlobalHvParams(self.op.hvparams)
9731
9732     # fill and remember the beparams dict
9733     default_beparams = cluster.beparams[constants.PP_DEFAULT]
9734     for param, value in self.op.beparams.iteritems():
9735       if value == constants.VALUE_AUTO:
9736         self.op.beparams[param] = default_beparams[param]
9737     objects.UpgradeBeParams(self.op.beparams)
9738     utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
9739     self.be_full = cluster.SimpleFillBE(self.op.beparams)
9740
9741     # build os parameters
9742     self.os_full = cluster.SimpleFillOS(self.op.os_type, self.op.osparams)
9743
9744     # now that hvp/bep are in final format, let's reset to defaults,
9745     # if told to do so
9746     if self.op.identify_defaults:
9747       self._RevertToDefaults(cluster)
9748
9749     # NIC buildup
9750     self.nics = []
9751     for idx, nic in enumerate(self.op.nics):
9752       nic_mode_req = nic.get(constants.INIC_MODE, None)
9753       nic_mode = nic_mode_req
9754       if nic_mode is None or nic_mode == constants.VALUE_AUTO:
9755         nic_mode = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_MODE]
9756
9757       # in routed mode, for the first nic, the default ip is 'auto'
9758       if nic_mode == constants.NIC_MODE_ROUTED and idx == 0:
9759         default_ip_mode = constants.VALUE_AUTO
9760       else:
9761         default_ip_mode = constants.VALUE_NONE
9762
9763       # ip validity checks
9764       ip = nic.get(constants.INIC_IP, default_ip_mode)
9765       if ip is None or ip.lower() == constants.VALUE_NONE:
9766         nic_ip = None
9767       elif ip.lower() == constants.VALUE_AUTO:
9768         if not self.op.name_check:
9769           raise errors.OpPrereqError("IP address set to auto but name checks"
9770                                      " have been skipped",
9771                                      errors.ECODE_INVAL)
9772         nic_ip = self.hostname1.ip
9773       else:
9774         if not netutils.IPAddress.IsValid(ip):
9775           raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
9776                                      errors.ECODE_INVAL)
9777         nic_ip = ip
9778
9779       # TODO: check the ip address for uniqueness
9780       if nic_mode == constants.NIC_MODE_ROUTED and not nic_ip:
9781         raise errors.OpPrereqError("Routed nic mode requires an ip address",
9782                                    errors.ECODE_INVAL)
9783
9784       # MAC address verification
9785       mac = nic.get(constants.INIC_MAC, constants.VALUE_AUTO)
9786       if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
9787         mac = utils.NormalizeAndValidateMac(mac)
9788
9789         try:
9790           self.cfg.ReserveMAC(mac, self.proc.GetECId())
9791         except errors.ReservationError:
9792           raise errors.OpPrereqError("MAC address %s already in use"
9793                                      " in cluster" % mac,
9794                                      errors.ECODE_NOTUNIQUE)
9795
9796       #  Build nic parameters
9797       link = nic.get(constants.INIC_LINK, None)
9798       if link == constants.VALUE_AUTO:
9799         link = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_LINK]
9800       nicparams = {}
9801       if nic_mode_req:
9802         nicparams[constants.NIC_MODE] = nic_mode
9803       if link:
9804         nicparams[constants.NIC_LINK] = link
9805
9806       check_params = cluster.SimpleFillNIC(nicparams)
9807       objects.NIC.CheckParameterSyntax(check_params)
9808       self.nics.append(objects.NIC(mac=mac, ip=nic_ip, nicparams=nicparams))
9809
9810     # disk checks/pre-build
9811     default_vg = self.cfg.GetVGName()
9812     self.disks = []
9813     for disk in self.op.disks:
9814       mode = disk.get(constants.IDISK_MODE, constants.DISK_RDWR)
9815       if mode not in constants.DISK_ACCESS_SET:
9816         raise errors.OpPrereqError("Invalid disk access mode '%s'" %
9817                                    mode, errors.ECODE_INVAL)
9818       size = disk.get(constants.IDISK_SIZE, None)
9819       if size is None:
9820         raise errors.OpPrereqError("Missing disk size", errors.ECODE_INVAL)
9821       try:
9822         size = int(size)
9823       except (TypeError, ValueError):
9824         raise errors.OpPrereqError("Invalid disk size '%s'" % size,
9825                                    errors.ECODE_INVAL)
9826
9827       data_vg = disk.get(constants.IDISK_VG, default_vg)
9828       new_disk = {
9829         constants.IDISK_SIZE: size,
9830         constants.IDISK_MODE: mode,
9831         constants.IDISK_VG: data_vg,
9832         }
9833       if constants.IDISK_METAVG in disk:
9834         new_disk[constants.IDISK_METAVG] = disk[constants.IDISK_METAVG]
9835       if constants.IDISK_ADOPT in disk:
9836         new_disk[constants.IDISK_ADOPT] = disk[constants.IDISK_ADOPT]
9837       self.disks.append(new_disk)
9838
9839     if self.op.mode == constants.INSTANCE_IMPORT:
9840       disk_images = []
9841       for idx in range(len(self.disks)):
9842         option = "disk%d_dump" % idx
9843         if export_info.has_option(constants.INISECT_INS, option):
9844           # FIXME: are the old os-es, disk sizes, etc. useful?
9845           export_name = export_info.get(constants.INISECT_INS, option)
9846           image = utils.PathJoin(self.op.src_path, export_name)
9847           disk_images.append(image)
9848         else:
9849           disk_images.append(False)
9850
9851       self.src_images = disk_images
9852
9853       if self.op.instance_name == self._old_instance_name:
9854         for idx, nic in enumerate(self.nics):
9855           if nic.mac == constants.VALUE_AUTO:
9856             nic_mac_ini = "nic%d_mac" % idx
9857             nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
9858
9859     # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
9860
9861     # ip ping checks (we use the same ip that was resolved in ExpandNames)
9862     if self.op.ip_check:
9863       if netutils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
9864         raise errors.OpPrereqError("IP %s of instance %s already in use" %
9865                                    (self.check_ip, self.op.instance_name),
9866                                    errors.ECODE_NOTUNIQUE)
9867
9868     #### mac address generation
9869     # By generating here the mac address both the allocator and the hooks get
9870     # the real final mac address rather than the 'auto' or 'generate' value.
9871     # There is a race condition between the generation and the instance object
9872     # creation, which means that we know the mac is valid now, but we're not
9873     # sure it will be when we actually add the instance. If things go bad
9874     # adding the instance will abort because of a duplicate mac, and the
9875     # creation job will fail.
9876     for nic in self.nics:
9877       if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
9878         nic.mac = self.cfg.GenerateMAC(self.proc.GetECId())
9879
9880     #### allocator run
9881
9882     if self.op.iallocator is not None:
9883       self._RunAllocator()
9884
9885     # Release all unneeded node locks
9886     _ReleaseLocks(self, locking.LEVEL_NODE,
9887                   keep=filter(None, [self.op.pnode, self.op.snode,
9888                                      self.op.src_node]))
9889     _ReleaseLocks(self, locking.LEVEL_NODE_RES,
9890                   keep=filter(None, [self.op.pnode, self.op.snode,
9891                                      self.op.src_node]))
9892
9893     #### node related checks
9894
9895     # check primary node
9896     self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode)
9897     assert self.pnode is not None, \
9898       "Cannot retrieve locked node %s" % self.op.pnode
9899     if pnode.offline:
9900       raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
9901                                  pnode.name, errors.ECODE_STATE)
9902     if pnode.drained:
9903       raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
9904                                  pnode.name, errors.ECODE_STATE)
9905     if not pnode.vm_capable:
9906       raise errors.OpPrereqError("Cannot use non-vm_capable primary node"
9907                                  " '%s'" % pnode.name, errors.ECODE_STATE)
9908
9909     self.secondaries = []
9910
9911     # mirror node verification
9912     if self.op.disk_template in constants.DTS_INT_MIRROR:
9913       if self.op.snode == pnode.name:
9914         raise errors.OpPrereqError("The secondary node cannot be the"
9915                                    " primary node", errors.ECODE_INVAL)
9916       _CheckNodeOnline(self, self.op.snode)
9917       _CheckNodeNotDrained(self, self.op.snode)
9918       _CheckNodeVmCapable(self, self.op.snode)
9919       self.secondaries.append(self.op.snode)
9920
9921       snode = self.cfg.GetNodeInfo(self.op.snode)
9922       if pnode.group != snode.group:
9923         self.LogWarning("The primary and secondary nodes are in two"
9924                         " different node groups; the disk parameters"
9925                         " from the first disk's node group will be"
9926                         " used")
9927
9928     nodenames = [pnode.name] + self.secondaries
9929
9930     # Verify instance specs
9931     spindle_use = self.be_full.get(constants.BE_SPINDLE_USE, None)
9932     ispec = {
9933       constants.ISPEC_MEM_SIZE: self.be_full.get(constants.BE_MAXMEM, None),
9934       constants.ISPEC_CPU_COUNT: self.be_full.get(constants.BE_VCPUS, None),
9935       constants.ISPEC_DISK_COUNT: len(self.disks),
9936       constants.ISPEC_DISK_SIZE: [disk["size"] for disk in self.disks],
9937       constants.ISPEC_NIC_COUNT: len(self.nics),
9938       constants.ISPEC_SPINDLE_USE: spindle_use,
9939       }
9940
9941     group_info = self.cfg.GetNodeGroup(pnode.group)
9942     ipolicy = _CalculateGroupIPolicy(cluster, group_info)
9943     res = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec)
9944     if not self.op.ignore_ipolicy and res:
9945       raise errors.OpPrereqError(("Instance allocation to group %s violates"
9946                                   " policy: %s") % (pnode.group,
9947                                                     utils.CommaJoin(res)),
9948                                   errors.ECODE_INVAL)
9949
9950     if not self.adopt_disks:
9951       if self.op.disk_template == constants.DT_RBD:
9952         # _CheckRADOSFreeSpace() is just a placeholder.
9953         # Any function that checks prerequisites can be placed here.
9954         # Check if there is enough space on the RADOS cluster.
9955         _CheckRADOSFreeSpace()
9956       else:
9957         # Check lv size requirements, if not adopting
9958         req_sizes = _ComputeDiskSizePerVG(self.op.disk_template, self.disks)
9959         _CheckNodesFreeDiskPerVG(self, nodenames, req_sizes)
9960
9961     elif self.op.disk_template == constants.DT_PLAIN: # Check the adoption data
9962       all_lvs = set(["%s/%s" % (disk[constants.IDISK_VG],
9963                                 disk[constants.IDISK_ADOPT])
9964                      for disk in self.disks])
9965       if len(all_lvs) != len(self.disks):
9966         raise errors.OpPrereqError("Duplicate volume names given for adoption",
9967                                    errors.ECODE_INVAL)
9968       for lv_name in all_lvs:
9969         try:
9970           # FIXME: lv_name here is "vg/lv" need to ensure that other calls
9971           # to ReserveLV uses the same syntax
9972           self.cfg.ReserveLV(lv_name, self.proc.GetECId())
9973         except errors.ReservationError:
9974           raise errors.OpPrereqError("LV named %s used by another instance" %
9975                                      lv_name, errors.ECODE_NOTUNIQUE)
9976
9977       vg_names = self.rpc.call_vg_list([pnode.name])[pnode.name]
9978       vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
9979
9980       node_lvs = self.rpc.call_lv_list([pnode.name],
9981                                        vg_names.payload.keys())[pnode.name]
9982       node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
9983       node_lvs = node_lvs.payload
9984
9985       delta = all_lvs.difference(node_lvs.keys())
9986       if delta:
9987         raise errors.OpPrereqError("Missing logical volume(s): %s" %
9988                                    utils.CommaJoin(delta),
9989                                    errors.ECODE_INVAL)
9990       online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
9991       if online_lvs:
9992         raise errors.OpPrereqError("Online logical volumes found, cannot"
9993                                    " adopt: %s" % utils.CommaJoin(online_lvs),
9994                                    errors.ECODE_STATE)
9995       # update the size of disk based on what is found
9996       for dsk in self.disks:
9997         dsk[constants.IDISK_SIZE] = \
9998           int(float(node_lvs["%s/%s" % (dsk[constants.IDISK_VG],
9999                                         dsk[constants.IDISK_ADOPT])][0]))
10000
10001     elif self.op.disk_template == constants.DT_BLOCK:
10002       # Normalize and de-duplicate device paths
10003       all_disks = set([os.path.abspath(disk[constants.IDISK_ADOPT])
10004                        for disk in self.disks])
10005       if len(all_disks) != len(self.disks):
10006         raise errors.OpPrereqError("Duplicate disk names given for adoption",
10007                                    errors.ECODE_INVAL)
10008       baddisks = [d for d in all_disks
10009                   if not d.startswith(constants.ADOPTABLE_BLOCKDEV_ROOT)]
10010       if baddisks:
10011         raise errors.OpPrereqError("Device node(s) %s lie outside %s and"
10012                                    " cannot be adopted" %
10013                                    (", ".join(baddisks),
10014                                     constants.ADOPTABLE_BLOCKDEV_ROOT),
10015                                    errors.ECODE_INVAL)
10016
10017       node_disks = self.rpc.call_bdev_sizes([pnode.name],
10018                                             list(all_disks))[pnode.name]
10019       node_disks.Raise("Cannot get block device information from node %s" %
10020                        pnode.name)
10021       node_disks = node_disks.payload
10022       delta = all_disks.difference(node_disks.keys())
10023       if delta:
10024         raise errors.OpPrereqError("Missing block device(s): %s" %
10025                                    utils.CommaJoin(delta),
10026                                    errors.ECODE_INVAL)
10027       for dsk in self.disks:
10028         dsk[constants.IDISK_SIZE] = \
10029           int(float(node_disks[dsk[constants.IDISK_ADOPT]]))
10030
10031     _CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
10032
10033     _CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant)
10034     # check OS parameters (remotely)
10035     _CheckOSParams(self, True, nodenames, self.op.os_type, self.os_full)
10036
10037     _CheckNicsBridgesExist(self, self.nics, self.pnode.name)
10038
10039     # memory check on primary node
10040     #TODO(dynmem): use MINMEM for checking
10041     if self.op.start:
10042       _CheckNodeFreeMemory(self, self.pnode.name,
10043                            "creating instance %s" % self.op.instance_name,
10044                            self.be_full[constants.BE_MAXMEM],
10045                            self.op.hypervisor)
10046
10047     self.dry_run_result = list(nodenames)
10048
10049   def Exec(self, feedback_fn):
10050     """Create and add the instance to the cluster.
10051
10052     """
10053     instance = self.op.instance_name
10054     pnode_name = self.pnode.name
10055
10056     assert not (self.owned_locks(locking.LEVEL_NODE_RES) -
10057                 self.owned_locks(locking.LEVEL_NODE)), \
10058       "Node locks differ from node resource locks"
10059
10060     ht_kind = self.op.hypervisor
10061     if ht_kind in constants.HTS_REQ_PORT:
10062       network_port = self.cfg.AllocatePort()
10063     else:
10064       network_port = None
10065
10066     # This is ugly but we got a chicken-egg problem here
10067     # We can only take the group disk parameters, as the instance
10068     # has no disks yet (we are generating them right here).
10069     node = self.cfg.GetNodeInfo(pnode_name)
10070     nodegroup = self.cfg.GetNodeGroup(node.group)
10071     disks = _GenerateDiskTemplate(self,
10072                                   self.op.disk_template,
10073                                   instance, pnode_name,
10074                                   self.secondaries,
10075                                   self.disks,
10076                                   self.instance_file_storage_dir,
10077                                   self.op.file_driver,
10078                                   0,
10079                                   feedback_fn,
10080                                   self.cfg.GetGroupDiskParams(nodegroup))
10081
10082     iobj = objects.Instance(name=instance, os=self.op.os_type,
10083                             primary_node=pnode_name,
10084                             nics=self.nics, disks=disks,
10085                             disk_template=self.op.disk_template,
10086                             admin_state=constants.ADMINST_DOWN,
10087                             network_port=network_port,
10088                             beparams=self.op.beparams,
10089                             hvparams=self.op.hvparams,
10090                             hypervisor=self.op.hypervisor,
10091                             osparams=self.op.osparams,
10092                             )
10093
10094     if self.op.tags:
10095       for tag in self.op.tags:
10096         iobj.AddTag(tag)
10097
10098     if self.adopt_disks:
10099       if self.op.disk_template == constants.DT_PLAIN:
10100         # rename LVs to the newly-generated names; we need to construct
10101         # 'fake' LV disks with the old data, plus the new unique_id
10102         tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
10103         rename_to = []
10104         for t_dsk, a_dsk in zip(tmp_disks, self.disks):
10105           rename_to.append(t_dsk.logical_id)
10106           t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
10107           self.cfg.SetDiskID(t_dsk, pnode_name)
10108         result = self.rpc.call_blockdev_rename(pnode_name,
10109                                                zip(tmp_disks, rename_to))
10110         result.Raise("Failed to rename adoped LVs")
10111     else:
10112       feedback_fn("* creating instance disks...")
10113       try:
10114         _CreateDisks(self, iobj)
10115       except errors.OpExecError:
10116         self.LogWarning("Device creation failed, reverting...")
10117         try:
10118           _RemoveDisks(self, iobj)
10119         finally:
10120           self.cfg.ReleaseDRBDMinors(instance)
10121           raise
10122
10123     feedback_fn("adding instance %s to cluster config" % instance)
10124
10125     self.cfg.AddInstance(iobj, self.proc.GetECId())
10126
10127     # Declare that we don't want to remove the instance lock anymore, as we've
10128     # added the instance to the config
10129     del self.remove_locks[locking.LEVEL_INSTANCE]
10130
10131     if self.op.mode == constants.INSTANCE_IMPORT:
10132       # Release unused nodes
10133       _ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node])
10134     else:
10135       # Release all nodes
10136       _ReleaseLocks(self, locking.LEVEL_NODE)
10137
10138     disk_abort = False
10139     if not self.adopt_disks and self.cfg.GetClusterInfo().prealloc_wipe_disks:
10140       feedback_fn("* wiping instance disks...")
10141       try:
10142         _WipeDisks(self, iobj)
10143       except errors.OpExecError, err:
10144         logging.exception("Wiping disks failed")
10145         self.LogWarning("Wiping instance disks failed (%s)", err)
10146         disk_abort = True
10147
10148     if disk_abort:
10149       # Something is already wrong with the disks, don't do anything else
10150       pass
10151     elif self.op.wait_for_sync:
10152       disk_abort = not _WaitForSync(self, iobj)
10153     elif iobj.disk_template in constants.DTS_INT_MIRROR:
10154       # make sure the disks are not degraded (still sync-ing is ok)
10155       feedback_fn("* checking mirrors status")
10156       disk_abort = not _WaitForSync(self, iobj, oneshot=True)
10157     else:
10158       disk_abort = False
10159
10160     if disk_abort:
10161       _RemoveDisks(self, iobj)
10162       self.cfg.RemoveInstance(iobj.name)
10163       # Make sure the instance lock gets removed
10164       self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
10165       raise errors.OpExecError("There are some degraded disks for"
10166                                " this instance")
10167
10168     # Release all node resource locks
10169     _ReleaseLocks(self, locking.LEVEL_NODE_RES)
10170
10171     if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
10172       # we need to set the disks ID to the primary node, since the
10173       # preceding code might or might have not done it, depending on
10174       # disk template and other options
10175       for disk in iobj.disks:
10176         self.cfg.SetDiskID(disk, pnode_name)
10177       if self.op.mode == constants.INSTANCE_CREATE:
10178         if not self.op.no_install:
10179           pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
10180                         not self.op.wait_for_sync)
10181           if pause_sync:
10182             feedback_fn("* pausing disk sync to install instance OS")
10183             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
10184                                                               (iobj.disks,
10185                                                                iobj), True)
10186             for idx, success in enumerate(result.payload):
10187               if not success:
10188                 logging.warn("pause-sync of instance %s for disk %d failed",
10189                              instance, idx)
10190
10191           feedback_fn("* running the instance OS create scripts...")
10192           # FIXME: pass debug option from opcode to backend
10193           os_add_result = \
10194             self.rpc.call_instance_os_add(pnode_name, (iobj, None), False,
10195                                           self.op.debug_level)
10196           if pause_sync:
10197             feedback_fn("* resuming disk sync")
10198             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
10199                                                               (iobj.disks,
10200                                                                iobj), False)
10201             for idx, success in enumerate(result.payload):
10202               if not success:
10203                 logging.warn("resume-sync of instance %s for disk %d failed",
10204                              instance, idx)
10205
10206           os_add_result.Raise("Could not add os for instance %s"
10207                               " on node %s" % (instance, pnode_name))
10208
10209       else:
10210         if self.op.mode == constants.INSTANCE_IMPORT:
10211           feedback_fn("* running the instance OS import scripts...")
10212
10213           transfers = []
10214
10215           for idx, image in enumerate(self.src_images):
10216             if not image:
10217               continue
10218
10219             # FIXME: pass debug option from opcode to backend
10220             dt = masterd.instance.DiskTransfer("disk/%s" % idx,
10221                                                constants.IEIO_FILE, (image, ),
10222                                                constants.IEIO_SCRIPT,
10223                                                (iobj.disks[idx], idx),
10224                                                None)
10225             transfers.append(dt)
10226
10227           import_result = \
10228             masterd.instance.TransferInstanceData(self, feedback_fn,
10229                                                   self.op.src_node, pnode_name,
10230                                                   self.pnode.secondary_ip,
10231                                                   iobj, transfers)
10232           if not compat.all(import_result):
10233             self.LogWarning("Some disks for instance %s on node %s were not"
10234                             " imported successfully" % (instance, pnode_name))
10235
10236           rename_from = self._old_instance_name
10237
10238         elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
10239           feedback_fn("* preparing remote import...")
10240           # The source cluster will stop the instance before attempting to make
10241           # a connection. In some cases stopping an instance can take a long
10242           # time, hence the shutdown timeout is added to the connection
10243           # timeout.
10244           connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
10245                              self.op.source_shutdown_timeout)
10246           timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
10247
10248           assert iobj.primary_node == self.pnode.name
10249           disk_results = \
10250             masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
10251                                           self.source_x509_ca,
10252                                           self._cds, timeouts)
10253           if not compat.all(disk_results):
10254             # TODO: Should the instance still be started, even if some disks
10255             # failed to import (valid for local imports, too)?
10256             self.LogWarning("Some disks for instance %s on node %s were not"
10257                             " imported successfully" % (instance, pnode_name))
10258
10259           rename_from = self.source_instance_name
10260
10261         else:
10262           # also checked in the prereq part
10263           raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
10264                                        % self.op.mode)
10265
10266         # Run rename script on newly imported instance
10267         assert iobj.name == instance
10268         feedback_fn("Running rename script for %s" % instance)
10269         result = self.rpc.call_instance_run_rename(pnode_name, iobj,
10270                                                    rename_from,
10271                                                    self.op.debug_level)
10272         if result.fail_msg:
10273           self.LogWarning("Failed to run rename script for %s on node"
10274                           " %s: %s" % (instance, pnode_name, result.fail_msg))
10275
10276     assert not self.owned_locks(locking.LEVEL_NODE_RES)
10277
10278     if self.op.start:
10279       iobj.admin_state = constants.ADMINST_UP
10280       self.cfg.Update(iobj, feedback_fn)
10281       logging.info("Starting instance %s on node %s", instance, pnode_name)
10282       feedback_fn("* starting instance...")
10283       result = self.rpc.call_instance_start(pnode_name, (iobj, None, None),
10284                                             False)
10285       result.Raise("Could not start instance")
10286
10287     return list(iobj.all_nodes)
10288
10289
10290 def _CheckRADOSFreeSpace():
10291   """Compute disk size requirements inside the RADOS cluster.
10292
10293   """
10294   # For the RADOS cluster we assume there is always enough space.
10295   pass
10296
10297
10298 class LUInstanceConsole(NoHooksLU):
10299   """Connect to an instance's console.
10300
10301   This is somewhat special in that it returns the command line that
10302   you need to run on the master node in order to connect to the
10303   console.
10304
10305   """
10306   REQ_BGL = False
10307
10308   def ExpandNames(self):
10309     self.share_locks = _ShareAll()
10310     self._ExpandAndLockInstance()
10311
10312   def CheckPrereq(self):
10313     """Check prerequisites.
10314
10315     This checks that the instance is in the cluster.
10316
10317     """
10318     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
10319     assert self.instance is not None, \
10320       "Cannot retrieve locked instance %s" % self.op.instance_name
10321     _CheckNodeOnline(self, self.instance.primary_node)
10322
10323   def Exec(self, feedback_fn):
10324     """Connect to the console of an instance
10325
10326     """
10327     instance = self.instance
10328     node = instance.primary_node
10329
10330     node_insts = self.rpc.call_instance_list([node],
10331                                              [instance.hypervisor])[node]
10332     node_insts.Raise("Can't get node information from %s" % node)
10333
10334     if instance.name not in node_insts.payload:
10335       if instance.admin_state == constants.ADMINST_UP:
10336         state = constants.INSTST_ERRORDOWN
10337       elif instance.admin_state == constants.ADMINST_DOWN:
10338         state = constants.INSTST_ADMINDOWN
10339       else:
10340         state = constants.INSTST_ADMINOFFLINE
10341       raise errors.OpExecError("Instance %s is not running (state %s)" %
10342                                (instance.name, state))
10343
10344     logging.debug("Connecting to console of %s on %s", instance.name, node)
10345
10346     return _GetInstanceConsole(self.cfg.GetClusterInfo(), instance)
10347
10348
10349 def _GetInstanceConsole(cluster, instance):
10350   """Returns console information for an instance.
10351
10352   @type cluster: L{objects.Cluster}
10353   @type instance: L{objects.Instance}
10354   @rtype: dict
10355
10356   """
10357   hyper = hypervisor.GetHypervisor(instance.hypervisor)
10358   # beparams and hvparams are passed separately, to avoid editing the
10359   # instance and then saving the defaults in the instance itself.
10360   hvparams = cluster.FillHV(instance)
10361   beparams = cluster.FillBE(instance)
10362   console = hyper.GetInstanceConsole(instance, hvparams, beparams)
10363
10364   assert console.instance == instance.name
10365   assert console.Validate()
10366
10367   return console.ToDict()
10368
10369
10370 class LUInstanceReplaceDisks(LogicalUnit):
10371   """Replace the disks of an instance.
10372
10373   """
10374   HPATH = "mirrors-replace"
10375   HTYPE = constants.HTYPE_INSTANCE
10376   REQ_BGL = False
10377
10378   def CheckArguments(self):
10379     TLReplaceDisks.CheckArguments(self.op.mode, self.op.remote_node,
10380                                   self.op.iallocator)
10381
10382   def ExpandNames(self):
10383     self._ExpandAndLockInstance()
10384
10385     assert locking.LEVEL_NODE not in self.needed_locks
10386     assert locking.LEVEL_NODE_RES not in self.needed_locks
10387     assert locking.LEVEL_NODEGROUP not in self.needed_locks
10388
10389     assert self.op.iallocator is None or self.op.remote_node is None, \
10390       "Conflicting options"
10391
10392     if self.op.remote_node is not None:
10393       self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
10394
10395       # Warning: do not remove the locking of the new secondary here
10396       # unless DRBD8.AddChildren is changed to work in parallel;
10397       # currently it doesn't since parallel invocations of
10398       # FindUnusedMinor will conflict
10399       self.needed_locks[locking.LEVEL_NODE] = [self.op.remote_node]
10400       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
10401     else:
10402       self.needed_locks[locking.LEVEL_NODE] = []
10403       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
10404
10405       if self.op.iallocator is not None:
10406         # iallocator will select a new node in the same group
10407         self.needed_locks[locking.LEVEL_NODEGROUP] = []
10408
10409     self.needed_locks[locking.LEVEL_NODE_RES] = []
10410
10411     self.replacer = TLReplaceDisks(self, self.op.instance_name, self.op.mode,
10412                                    self.op.iallocator, self.op.remote_node,
10413                                    self.op.disks, False, self.op.early_release,
10414                                    self.op.ignore_ipolicy)
10415
10416     self.tasklets = [self.replacer]
10417
10418   def DeclareLocks(self, level):
10419     if level == locking.LEVEL_NODEGROUP:
10420       assert self.op.remote_node is None
10421       assert self.op.iallocator is not None
10422       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
10423
10424       self.share_locks[locking.LEVEL_NODEGROUP] = 1
10425       # Lock all groups used by instance optimistically; this requires going
10426       # via the node before it's locked, requiring verification later on
10427       self.needed_locks[locking.LEVEL_NODEGROUP] = \
10428         self.cfg.GetInstanceNodeGroups(self.op.instance_name)
10429
10430     elif level == locking.LEVEL_NODE:
10431       if self.op.iallocator is not None:
10432         assert self.op.remote_node is None
10433         assert not self.needed_locks[locking.LEVEL_NODE]
10434
10435         # Lock member nodes of all locked groups
10436         self.needed_locks[locking.LEVEL_NODE] = [node_name
10437           for group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
10438           for node_name in self.cfg.GetNodeGroup(group_uuid).members]
10439       else:
10440         self._LockInstancesNodes()
10441     elif level == locking.LEVEL_NODE_RES:
10442       # Reuse node locks
10443       self.needed_locks[locking.LEVEL_NODE_RES] = \
10444         self.needed_locks[locking.LEVEL_NODE]
10445
10446   def BuildHooksEnv(self):
10447     """Build hooks env.
10448
10449     This runs on the master, the primary and all the secondaries.
10450
10451     """
10452     instance = self.replacer.instance
10453     env = {
10454       "MODE": self.op.mode,
10455       "NEW_SECONDARY": self.op.remote_node,
10456       "OLD_SECONDARY": instance.secondary_nodes[0],
10457       }
10458     env.update(_BuildInstanceHookEnvByObject(self, instance))
10459     return env
10460
10461   def BuildHooksNodes(self):
10462     """Build hooks nodes.
10463
10464     """
10465     instance = self.replacer.instance
10466     nl = [
10467       self.cfg.GetMasterNode(),
10468       instance.primary_node,
10469       ]
10470     if self.op.remote_node is not None:
10471       nl.append(self.op.remote_node)
10472     return nl, nl
10473
10474   def CheckPrereq(self):
10475     """Check prerequisites.
10476
10477     """
10478     assert (self.glm.is_owned(locking.LEVEL_NODEGROUP) or
10479             self.op.iallocator is None)
10480
10481     # Verify if node group locks are still correct
10482     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
10483     if owned_groups:
10484       _CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
10485
10486     return LogicalUnit.CheckPrereq(self)
10487
10488
10489 class TLReplaceDisks(Tasklet):
10490   """Replaces disks for an instance.
10491
10492   Note: Locking is not within the scope of this class.
10493
10494   """
10495   def __init__(self, lu, instance_name, mode, iallocator_name, remote_node,
10496                disks, delay_iallocator, early_release, ignore_ipolicy):
10497     """Initializes this class.
10498
10499     """
10500     Tasklet.__init__(self, lu)
10501
10502     # Parameters
10503     self.instance_name = instance_name
10504     self.mode = mode
10505     self.iallocator_name = iallocator_name
10506     self.remote_node = remote_node
10507     self.disks = disks
10508     self.delay_iallocator = delay_iallocator
10509     self.early_release = early_release
10510     self.ignore_ipolicy = ignore_ipolicy
10511
10512     # Runtime data
10513     self.instance = None
10514     self.new_node = None
10515     self.target_node = None
10516     self.other_node = None
10517     self.remote_node_info = None
10518     self.node_secondary_ip = None
10519
10520   @staticmethod
10521   def CheckArguments(mode, remote_node, iallocator):
10522     """Helper function for users of this class.
10523
10524     """
10525     # check for valid parameter combination
10526     if mode == constants.REPLACE_DISK_CHG:
10527       if remote_node is None and iallocator is None:
10528         raise errors.OpPrereqError("When changing the secondary either an"
10529                                    " iallocator script must be used or the"
10530                                    " new node given", errors.ECODE_INVAL)
10531
10532       if remote_node is not None and iallocator is not None:
10533         raise errors.OpPrereqError("Give either the iallocator or the new"
10534                                    " secondary, not both", errors.ECODE_INVAL)
10535
10536     elif remote_node is not None or iallocator is not None:
10537       # Not replacing the secondary
10538       raise errors.OpPrereqError("The iallocator and new node options can"
10539                                  " only be used when changing the"
10540                                  " secondary node", errors.ECODE_INVAL)
10541
10542   @staticmethod
10543   def _RunAllocator(lu, iallocator_name, instance_name, relocate_from):
10544     """Compute a new secondary node using an IAllocator.
10545
10546     """
10547     ial = IAllocator(lu.cfg, lu.rpc,
10548                      mode=constants.IALLOCATOR_MODE_RELOC,
10549                      name=instance_name,
10550                      relocate_from=list(relocate_from))
10551
10552     ial.Run(iallocator_name)
10553
10554     if not ial.success:
10555       raise errors.OpPrereqError("Can't compute nodes using iallocator '%s':"
10556                                  " %s" % (iallocator_name, ial.info),
10557                                  errors.ECODE_NORES)
10558
10559     if len(ial.result) != ial.required_nodes:
10560       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
10561                                  " of nodes (%s), required %s" %
10562                                  (iallocator_name,
10563                                   len(ial.result), ial.required_nodes),
10564                                  errors.ECODE_FAULT)
10565
10566     remote_node_name = ial.result[0]
10567
10568     lu.LogInfo("Selected new secondary for instance '%s': %s",
10569                instance_name, remote_node_name)
10570
10571     return remote_node_name
10572
10573   def _FindFaultyDisks(self, node_name):
10574     """Wrapper for L{_FindFaultyInstanceDisks}.
10575
10576     """
10577     return _FindFaultyInstanceDisks(self.cfg, self.rpc, self.instance,
10578                                     node_name, True)
10579
10580   def _CheckDisksActivated(self, instance):
10581     """Checks if the instance disks are activated.
10582
10583     @param instance: The instance to check disks
10584     @return: True if they are activated, False otherwise
10585
10586     """
10587     nodes = instance.all_nodes
10588
10589     for idx, dev in enumerate(instance.disks):
10590       for node in nodes:
10591         self.lu.LogInfo("Checking disk/%d on %s", idx, node)
10592         self.cfg.SetDiskID(dev, node)
10593
10594         result = _BlockdevFind(self, node, dev, instance)
10595
10596         if result.offline:
10597           continue
10598         elif result.fail_msg or not result.payload:
10599           return False
10600
10601     return True
10602
10603   def CheckPrereq(self):
10604     """Check prerequisites.
10605
10606     This checks that the instance is in the cluster.
10607
10608     """
10609     self.instance = instance = self.cfg.GetInstanceInfo(self.instance_name)
10610     assert instance is not None, \
10611       "Cannot retrieve locked instance %s" % self.instance_name
10612
10613     if instance.disk_template != constants.DT_DRBD8:
10614       raise errors.OpPrereqError("Can only run replace disks for DRBD8-based"
10615                                  " instances", errors.ECODE_INVAL)
10616
10617     if len(instance.secondary_nodes) != 1:
10618       raise errors.OpPrereqError("The instance has a strange layout,"
10619                                  " expected one secondary but found %d" %
10620                                  len(instance.secondary_nodes),
10621                                  errors.ECODE_FAULT)
10622
10623     if not self.delay_iallocator:
10624       self._CheckPrereq2()
10625
10626   def _CheckPrereq2(self):
10627     """Check prerequisites, second part.
10628
10629     This function should always be part of CheckPrereq. It was separated and is
10630     now called from Exec because during node evacuation iallocator was only
10631     called with an unmodified cluster model, not taking planned changes into
10632     account.
10633
10634     """
10635     instance = self.instance
10636     secondary_node = instance.secondary_nodes[0]
10637
10638     if self.iallocator_name is None:
10639       remote_node = self.remote_node
10640     else:
10641       remote_node = self._RunAllocator(self.lu, self.iallocator_name,
10642                                        instance.name, instance.secondary_nodes)
10643
10644     if remote_node is None:
10645       self.remote_node_info = None
10646     else:
10647       assert remote_node in self.lu.owned_locks(locking.LEVEL_NODE), \
10648              "Remote node '%s' is not locked" % remote_node
10649
10650       self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
10651       assert self.remote_node_info is not None, \
10652         "Cannot retrieve locked node %s" % remote_node
10653
10654     if remote_node == self.instance.primary_node:
10655       raise errors.OpPrereqError("The specified node is the primary node of"
10656                                  " the instance", errors.ECODE_INVAL)
10657
10658     if remote_node == secondary_node:
10659       raise errors.OpPrereqError("The specified node is already the"
10660                                  " secondary node of the instance",
10661                                  errors.ECODE_INVAL)
10662
10663     if self.disks and self.mode in (constants.REPLACE_DISK_AUTO,
10664                                     constants.REPLACE_DISK_CHG):
10665       raise errors.OpPrereqError("Cannot specify disks to be replaced",
10666                                  errors.ECODE_INVAL)
10667
10668     if self.mode == constants.REPLACE_DISK_AUTO:
10669       if not self._CheckDisksActivated(instance):
10670         raise errors.OpPrereqError("Please run activate-disks on instance %s"
10671                                    " first" % self.instance_name,
10672                                    errors.ECODE_STATE)
10673       faulty_primary = self._FindFaultyDisks(instance.primary_node)
10674       faulty_secondary = self._FindFaultyDisks(secondary_node)
10675
10676       if faulty_primary and faulty_secondary:
10677         raise errors.OpPrereqError("Instance %s has faulty disks on more than"
10678                                    " one node and can not be repaired"
10679                                    " automatically" % self.instance_name,
10680                                    errors.ECODE_STATE)
10681
10682       if faulty_primary:
10683         self.disks = faulty_primary
10684         self.target_node = instance.primary_node
10685         self.other_node = secondary_node
10686         check_nodes = [self.target_node, self.other_node]
10687       elif faulty_secondary:
10688         self.disks = faulty_secondary
10689         self.target_node = secondary_node
10690         self.other_node = instance.primary_node
10691         check_nodes = [self.target_node, self.other_node]
10692       else:
10693         self.disks = []
10694         check_nodes = []
10695
10696     else:
10697       # Non-automatic modes
10698       if self.mode == constants.REPLACE_DISK_PRI:
10699         self.target_node = instance.primary_node
10700         self.other_node = secondary_node
10701         check_nodes = [self.target_node, self.other_node]
10702
10703       elif self.mode == constants.REPLACE_DISK_SEC:
10704         self.target_node = secondary_node
10705         self.other_node = instance.primary_node
10706         check_nodes = [self.target_node, self.other_node]
10707
10708       elif self.mode == constants.REPLACE_DISK_CHG:
10709         self.new_node = remote_node
10710         self.other_node = instance.primary_node
10711         self.target_node = secondary_node
10712         check_nodes = [self.new_node, self.other_node]
10713
10714         _CheckNodeNotDrained(self.lu, remote_node)
10715         _CheckNodeVmCapable(self.lu, remote_node)
10716
10717         old_node_info = self.cfg.GetNodeInfo(secondary_node)
10718         assert old_node_info is not None
10719         if old_node_info.offline and not self.early_release:
10720           # doesn't make sense to delay the release
10721           self.early_release = True
10722           self.lu.LogInfo("Old secondary %s is offline, automatically enabling"
10723                           " early-release mode", secondary_node)
10724
10725       else:
10726         raise errors.ProgrammerError("Unhandled disk replace mode (%s)" %
10727                                      self.mode)
10728
10729       # If not specified all disks should be replaced
10730       if not self.disks:
10731         self.disks = range(len(self.instance.disks))
10732
10733     # TODO: This is ugly, but right now we can't distinguish between internal
10734     # submitted opcode and external one. We should fix that.
10735     if self.remote_node_info:
10736       # We change the node, lets verify it still meets instance policy
10737       new_group_info = self.cfg.GetNodeGroup(self.remote_node_info.group)
10738       ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(),
10739                                        new_group_info)
10740       _CheckTargetNodeIPolicy(self, ipolicy, instance, self.remote_node_info,
10741                               ignore=self.ignore_ipolicy)
10742
10743     for node in check_nodes:
10744       _CheckNodeOnline(self.lu, node)
10745
10746     touched_nodes = frozenset(node_name for node_name in [self.new_node,
10747                                                           self.other_node,
10748                                                           self.target_node]
10749                               if node_name is not None)
10750
10751     # Release unneeded node and node resource locks
10752     _ReleaseLocks(self.lu, locking.LEVEL_NODE, keep=touched_nodes)
10753     _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES, keep=touched_nodes)
10754
10755     # Release any owned node group
10756     if self.lu.glm.is_owned(locking.LEVEL_NODEGROUP):
10757       _ReleaseLocks(self.lu, locking.LEVEL_NODEGROUP)
10758
10759     # Check whether disks are valid
10760     for disk_idx in self.disks:
10761       instance.FindDisk(disk_idx)
10762
10763     # Get secondary node IP addresses
10764     self.node_secondary_ip = dict((name, node.secondary_ip) for (name, node)
10765                                   in self.cfg.GetMultiNodeInfo(touched_nodes))
10766
10767   def Exec(self, feedback_fn):
10768     """Execute disk replacement.
10769
10770     This dispatches the disk replacement to the appropriate handler.
10771
10772     """
10773     if self.delay_iallocator:
10774       self._CheckPrereq2()
10775
10776     if __debug__:
10777       # Verify owned locks before starting operation
10778       owned_nodes = self.lu.owned_locks(locking.LEVEL_NODE)
10779       assert set(owned_nodes) == set(self.node_secondary_ip), \
10780           ("Incorrect node locks, owning %s, expected %s" %
10781            (owned_nodes, self.node_secondary_ip.keys()))
10782       assert (self.lu.owned_locks(locking.LEVEL_NODE) ==
10783               self.lu.owned_locks(locking.LEVEL_NODE_RES))
10784
10785       owned_instances = self.lu.owned_locks(locking.LEVEL_INSTANCE)
10786       assert list(owned_instances) == [self.instance_name], \
10787           "Instance '%s' not locked" % self.instance_name
10788
10789       assert not self.lu.glm.is_owned(locking.LEVEL_NODEGROUP), \
10790           "Should not own any node group lock at this point"
10791
10792     if not self.disks:
10793       feedback_fn("No disks need replacement")
10794       return
10795
10796     feedback_fn("Replacing disk(s) %s for %s" %
10797                 (utils.CommaJoin(self.disks), self.instance.name))
10798
10799     activate_disks = (self.instance.admin_state != constants.ADMINST_UP)
10800
10801     # Activate the instance disks if we're replacing them on a down instance
10802     if activate_disks:
10803       _StartInstanceDisks(self.lu, self.instance, True)
10804
10805     try:
10806       # Should we replace the secondary node?
10807       if self.new_node is not None:
10808         fn = self._ExecDrbd8Secondary
10809       else:
10810         fn = self._ExecDrbd8DiskOnly
10811
10812       result = fn(feedback_fn)
10813     finally:
10814       # Deactivate the instance disks if we're replacing them on a
10815       # down instance
10816       if activate_disks:
10817         _SafeShutdownInstanceDisks(self.lu, self.instance)
10818
10819     assert not self.lu.owned_locks(locking.LEVEL_NODE)
10820
10821     if __debug__:
10822       # Verify owned locks
10823       owned_nodes = self.lu.owned_locks(locking.LEVEL_NODE_RES)
10824       nodes = frozenset(self.node_secondary_ip)
10825       assert ((self.early_release and not owned_nodes) or
10826               (not self.early_release and not (set(owned_nodes) - nodes))), \
10827         ("Not owning the correct locks, early_release=%s, owned=%r,"
10828          " nodes=%r" % (self.early_release, owned_nodes, nodes))
10829
10830     return result
10831
10832   def _CheckVolumeGroup(self, nodes):
10833     self.lu.LogInfo("Checking volume groups")
10834
10835     vgname = self.cfg.GetVGName()
10836
10837     # Make sure volume group exists on all involved nodes
10838     results = self.rpc.call_vg_list(nodes)
10839     if not results:
10840       raise errors.OpExecError("Can't list volume groups on the nodes")
10841
10842     for node in nodes:
10843       res = results[node]
10844       res.Raise("Error checking node %s" % node)
10845       if vgname not in res.payload:
10846         raise errors.OpExecError("Volume group '%s' not found on node %s" %
10847                                  (vgname, node))
10848
10849   def _CheckDisksExistence(self, nodes):
10850     # Check disk existence
10851     for idx, dev in enumerate(self.instance.disks):
10852       if idx not in self.disks:
10853         continue
10854
10855       for node in nodes:
10856         self.lu.LogInfo("Checking disk/%d on %s" % (idx, node))
10857         self.cfg.SetDiskID(dev, node)
10858
10859         result = _BlockdevFind(self, node, dev, self.instance)
10860
10861         msg = result.fail_msg
10862         if msg or not result.payload:
10863           if not msg:
10864             msg = "disk not found"
10865           raise errors.OpExecError("Can't find disk/%d on node %s: %s" %
10866                                    (idx, node, msg))
10867
10868   def _CheckDisksConsistency(self, node_name, on_primary, ldisk):
10869     for idx, dev in enumerate(self.instance.disks):
10870       if idx not in self.disks:
10871         continue
10872
10873       self.lu.LogInfo("Checking disk/%d consistency on node %s" %
10874                       (idx, node_name))
10875
10876       if not _CheckDiskConsistency(self.lu, self.instance, dev, node_name,
10877                                    on_primary, ldisk=ldisk):
10878         raise errors.OpExecError("Node %s has degraded storage, unsafe to"
10879                                  " replace disks for instance %s" %
10880                                  (node_name, self.instance.name))
10881
10882   def _CreateNewStorage(self, node_name):
10883     """Create new storage on the primary or secondary node.
10884
10885     This is only used for same-node replaces, not for changing the
10886     secondary node, hence we don't want to modify the existing disk.
10887
10888     """
10889     iv_names = {}
10890
10891     disks = _AnnotateDiskParams(self.instance, self.instance.disks, self.cfg)
10892     for idx, dev in enumerate(disks):
10893       if idx not in self.disks:
10894         continue
10895
10896       self.lu.LogInfo("Adding storage on %s for disk/%d" % (node_name, idx))
10897
10898       self.cfg.SetDiskID(dev, node_name)
10899
10900       lv_names = [".disk%d_%s" % (idx, suffix) for suffix in ["data", "meta"]]
10901       names = _GenerateUniqueNames(self.lu, lv_names)
10902
10903       (data_disk, meta_disk) = dev.children
10904       vg_data = data_disk.logical_id[0]
10905       lv_data = objects.Disk(dev_type=constants.LD_LV, size=dev.size,
10906                              logical_id=(vg_data, names[0]),
10907                              params=data_disk.params)
10908       vg_meta = meta_disk.logical_id[0]
10909       lv_meta = objects.Disk(dev_type=constants.LD_LV, size=DRBD_META_SIZE,
10910                              logical_id=(vg_meta, names[1]),
10911                              params=meta_disk.params)
10912
10913       new_lvs = [lv_data, lv_meta]
10914       old_lvs = [child.Copy() for child in dev.children]
10915       iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
10916
10917       # we pass force_create=True to force the LVM creation
10918       for new_lv in new_lvs:
10919         _CreateBlockDevInner(self.lu, node_name, self.instance, new_lv, True,
10920                              _GetInstanceInfoText(self.instance), False)
10921
10922     return iv_names
10923
10924   def _CheckDevices(self, node_name, iv_names):
10925     for name, (dev, _, _) in iv_names.iteritems():
10926       self.cfg.SetDiskID(dev, node_name)
10927
10928       result = _BlockdevFind(self, node_name, dev, self.instance)
10929
10930       msg = result.fail_msg
10931       if msg or not result.payload:
10932         if not msg:
10933           msg = "disk not found"
10934         raise errors.OpExecError("Can't find DRBD device %s: %s" %
10935                                  (name, msg))
10936
10937       if result.payload.is_degraded:
10938         raise errors.OpExecError("DRBD device %s is degraded!" % name)
10939
10940   def _RemoveOldStorage(self, node_name, iv_names):
10941     for name, (_, old_lvs, _) in iv_names.iteritems():
10942       self.lu.LogInfo("Remove logical volumes for %s" % name)
10943
10944       for lv in old_lvs:
10945         self.cfg.SetDiskID(lv, node_name)
10946
10947         msg = self.rpc.call_blockdev_remove(node_name, lv).fail_msg
10948         if msg:
10949           self.lu.LogWarning("Can't remove old LV: %s" % msg,
10950                              hint="remove unused LVs manually")
10951
10952   def _ExecDrbd8DiskOnly(self, feedback_fn): # pylint: disable=W0613
10953     """Replace a disk on the primary or secondary for DRBD 8.
10954
10955     The algorithm for replace is quite complicated:
10956
10957       1. for each disk to be replaced:
10958
10959         1. create new LVs on the target node with unique names
10960         1. detach old LVs from the drbd device
10961         1. rename old LVs to name_replaced.<time_t>
10962         1. rename new LVs to old LVs
10963         1. attach the new LVs (with the old names now) to the drbd device
10964
10965       1. wait for sync across all devices
10966
10967       1. for each modified disk:
10968
10969         1. remove old LVs (which have the name name_replaces.<time_t>)
10970
10971     Failures are not very well handled.
10972
10973     """
10974     steps_total = 6
10975
10976     # Step: check device activation
10977     self.lu.LogStep(1, steps_total, "Check device existence")
10978     self._CheckDisksExistence([self.other_node, self.target_node])
10979     self._CheckVolumeGroup([self.target_node, self.other_node])
10980
10981     # Step: check other node consistency
10982     self.lu.LogStep(2, steps_total, "Check peer consistency")
10983     self._CheckDisksConsistency(self.other_node,
10984                                 self.other_node == self.instance.primary_node,
10985                                 False)
10986
10987     # Step: create new storage
10988     self.lu.LogStep(3, steps_total, "Allocate new storage")
10989     iv_names = self._CreateNewStorage(self.target_node)
10990
10991     # Step: for each lv, detach+rename*2+attach
10992     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
10993     for dev, old_lvs, new_lvs in iv_names.itervalues():
10994       self.lu.LogInfo("Detaching %s drbd from local storage" % dev.iv_name)
10995
10996       result = self.rpc.call_blockdev_removechildren(self.target_node, dev,
10997                                                      old_lvs)
10998       result.Raise("Can't detach drbd from local storage on node"
10999                    " %s for device %s" % (self.target_node, dev.iv_name))
11000       #dev.children = []
11001       #cfg.Update(instance)
11002
11003       # ok, we created the new LVs, so now we know we have the needed
11004       # storage; as such, we proceed on the target node to rename
11005       # old_lv to _old, and new_lv to old_lv; note that we rename LVs
11006       # using the assumption that logical_id == physical_id (which in
11007       # turn is the unique_id on that node)
11008
11009       # FIXME(iustin): use a better name for the replaced LVs
11010       temp_suffix = int(time.time())
11011       ren_fn = lambda d, suff: (d.physical_id[0],
11012                                 d.physical_id[1] + "_replaced-%s" % suff)
11013
11014       # Build the rename list based on what LVs exist on the node
11015       rename_old_to_new = []
11016       for to_ren in old_lvs:
11017         result = self.rpc.call_blockdev_find(self.target_node, to_ren)
11018         if not result.fail_msg and result.payload:
11019           # device exists
11020           rename_old_to_new.append((to_ren, ren_fn(to_ren, temp_suffix)))
11021
11022       self.lu.LogInfo("Renaming the old LVs on the target node")
11023       result = self.rpc.call_blockdev_rename(self.target_node,
11024                                              rename_old_to_new)
11025       result.Raise("Can't rename old LVs on node %s" % self.target_node)
11026
11027       # Now we rename the new LVs to the old LVs
11028       self.lu.LogInfo("Renaming the new LVs on the target node")
11029       rename_new_to_old = [(new, old.physical_id)
11030                            for old, new in zip(old_lvs, new_lvs)]
11031       result = self.rpc.call_blockdev_rename(self.target_node,
11032                                              rename_new_to_old)
11033       result.Raise("Can't rename new LVs on node %s" % self.target_node)
11034
11035       # Intermediate steps of in memory modifications
11036       for old, new in zip(old_lvs, new_lvs):
11037         new.logical_id = old.logical_id
11038         self.cfg.SetDiskID(new, self.target_node)
11039
11040       # We need to modify old_lvs so that removal later removes the
11041       # right LVs, not the newly added ones; note that old_lvs is a
11042       # copy here
11043       for disk in old_lvs:
11044         disk.logical_id = ren_fn(disk, temp_suffix)
11045         self.cfg.SetDiskID(disk, self.target_node)
11046
11047       # Now that the new lvs have the old name, we can add them to the device
11048       self.lu.LogInfo("Adding new mirror component on %s" % self.target_node)
11049       result = self.rpc.call_blockdev_addchildren(self.target_node,
11050                                                   (dev, self.instance), new_lvs)
11051       msg = result.fail_msg
11052       if msg:
11053         for new_lv in new_lvs:
11054           msg2 = self.rpc.call_blockdev_remove(self.target_node,
11055                                                new_lv).fail_msg
11056           if msg2:
11057             self.lu.LogWarning("Can't rollback device %s: %s", dev, msg2,
11058                                hint=("cleanup manually the unused logical"
11059                                      "volumes"))
11060         raise errors.OpExecError("Can't add local storage to drbd: %s" % msg)
11061
11062     cstep = itertools.count(5)
11063
11064     if self.early_release:
11065       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11066       self._RemoveOldStorage(self.target_node, iv_names)
11067       # TODO: Check if releasing locks early still makes sense
11068       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
11069     else:
11070       # Release all resource locks except those used by the instance
11071       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES,
11072                     keep=self.node_secondary_ip.keys())
11073
11074     # Release all node locks while waiting for sync
11075     _ReleaseLocks(self.lu, locking.LEVEL_NODE)
11076
11077     # TODO: Can the instance lock be downgraded here? Take the optional disk
11078     # shutdown in the caller into consideration.
11079
11080     # Wait for sync
11081     # This can fail as the old devices are degraded and _WaitForSync
11082     # does a combined result over all disks, so we don't check its return value
11083     self.lu.LogStep(cstep.next(), steps_total, "Sync devices")
11084     _WaitForSync(self.lu, self.instance)
11085
11086     # Check all devices manually
11087     self._CheckDevices(self.instance.primary_node, iv_names)
11088
11089     # Step: remove old storage
11090     if not self.early_release:
11091       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11092       self._RemoveOldStorage(self.target_node, iv_names)
11093
11094   def _ExecDrbd8Secondary(self, feedback_fn):
11095     """Replace the secondary node for DRBD 8.
11096
11097     The algorithm for replace is quite complicated:
11098       - for all disks of the instance:
11099         - create new LVs on the new node with same names
11100         - shutdown the drbd device on the old secondary
11101         - disconnect the drbd network on the primary
11102         - create the drbd device on the new secondary
11103         - network attach the drbd on the primary, using an artifice:
11104           the drbd code for Attach() will connect to the network if it
11105           finds a device which is connected to the good local disks but
11106           not network enabled
11107       - wait for sync across all devices
11108       - remove all disks from the old secondary
11109
11110     Failures are not very well handled.
11111
11112     """
11113     steps_total = 6
11114
11115     pnode = self.instance.primary_node
11116
11117     # Step: check device activation
11118     self.lu.LogStep(1, steps_total, "Check device existence")
11119     self._CheckDisksExistence([self.instance.primary_node])
11120     self._CheckVolumeGroup([self.instance.primary_node])
11121
11122     # Step: check other node consistency
11123     self.lu.LogStep(2, steps_total, "Check peer consistency")
11124     self._CheckDisksConsistency(self.instance.primary_node, True, True)
11125
11126     # Step: create new storage
11127     self.lu.LogStep(3, steps_total, "Allocate new storage")
11128     disks = _AnnotateDiskParams(self.instance, self.instance.disks, self.cfg)
11129     for idx, dev in enumerate(disks):
11130       self.lu.LogInfo("Adding new local storage on %s for disk/%d" %
11131                       (self.new_node, idx))
11132       # we pass force_create=True to force LVM creation
11133       for new_lv in dev.children:
11134         _CreateBlockDevInner(self.lu, self.new_node, self.instance, new_lv,
11135                              True, _GetInstanceInfoText(self.instance), False)
11136
11137     # Step 4: dbrd minors and drbd setups changes
11138     # after this, we must manually remove the drbd minors on both the
11139     # error and the success paths
11140     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
11141     minors = self.cfg.AllocateDRBDMinor([self.new_node
11142                                          for dev in self.instance.disks],
11143                                         self.instance.name)
11144     logging.debug("Allocated minors %r", minors)
11145
11146     iv_names = {}
11147     for idx, (dev, new_minor) in enumerate(zip(self.instance.disks, minors)):
11148       self.lu.LogInfo("activating a new drbd on %s for disk/%d" %
11149                       (self.new_node, idx))
11150       # create new devices on new_node; note that we create two IDs:
11151       # one without port, so the drbd will be activated without
11152       # networking information on the new node at this stage, and one
11153       # with network, for the latter activation in step 4
11154       (o_node1, o_node2, o_port, o_minor1, o_minor2, o_secret) = dev.logical_id
11155       if self.instance.primary_node == o_node1:
11156         p_minor = o_minor1
11157       else:
11158         assert self.instance.primary_node == o_node2, "Three-node instance?"
11159         p_minor = o_minor2
11160
11161       new_alone_id = (self.instance.primary_node, self.new_node, None,
11162                       p_minor, new_minor, o_secret)
11163       new_net_id = (self.instance.primary_node, self.new_node, o_port,
11164                     p_minor, new_minor, o_secret)
11165
11166       iv_names[idx] = (dev, dev.children, new_net_id)
11167       logging.debug("Allocated new_minor: %s, new_logical_id: %s", new_minor,
11168                     new_net_id)
11169       new_drbd = objects.Disk(dev_type=constants.LD_DRBD8,
11170                               logical_id=new_alone_id,
11171                               children=dev.children,
11172                               size=dev.size,
11173                               params={})
11174       (anno_new_drbd,) = _AnnotateDiskParams(self.instance, [new_drbd],
11175                                              self.cfg)
11176       try:
11177         _CreateSingleBlockDev(self.lu, self.new_node, self.instance,
11178                               anno_new_drbd,
11179                               _GetInstanceInfoText(self.instance), False)
11180       except errors.GenericError:
11181         self.cfg.ReleaseDRBDMinors(self.instance.name)
11182         raise
11183
11184     # We have new devices, shutdown the drbd on the old secondary
11185     for idx, dev in enumerate(self.instance.disks):
11186       self.lu.LogInfo("Shutting down drbd for disk/%d on old node" % idx)
11187       self.cfg.SetDiskID(dev, self.target_node)
11188       msg = self.rpc.call_blockdev_shutdown(self.target_node,
11189                                             (dev, self.instance)).fail_msg
11190       if msg:
11191         self.lu.LogWarning("Failed to shutdown drbd for disk/%d on old"
11192                            "node: %s" % (idx, msg),
11193                            hint=("Please cleanup this device manually as"
11194                                  " soon as possible"))
11195
11196     self.lu.LogInfo("Detaching primary drbds from the network (=> standalone)")
11197     result = self.rpc.call_drbd_disconnect_net([pnode], self.node_secondary_ip,
11198                                                self.instance.disks)[pnode]
11199
11200     msg = result.fail_msg
11201     if msg:
11202       # detaches didn't succeed (unlikely)
11203       self.cfg.ReleaseDRBDMinors(self.instance.name)
11204       raise errors.OpExecError("Can't detach the disks from the network on"
11205                                " old node: %s" % (msg,))
11206
11207     # if we managed to detach at least one, we update all the disks of
11208     # the instance to point to the new secondary
11209     self.lu.LogInfo("Updating instance configuration")
11210     for dev, _, new_logical_id in iv_names.itervalues():
11211       dev.logical_id = new_logical_id
11212       self.cfg.SetDiskID(dev, self.instance.primary_node)
11213
11214     self.cfg.Update(self.instance, feedback_fn)
11215
11216     # Release all node locks (the configuration has been updated)
11217     _ReleaseLocks(self.lu, locking.LEVEL_NODE)
11218
11219     # and now perform the drbd attach
11220     self.lu.LogInfo("Attaching primary drbds to new secondary"
11221                     " (standalone => connected)")
11222     result = self.rpc.call_drbd_attach_net([self.instance.primary_node,
11223                                             self.new_node],
11224                                            self.node_secondary_ip,
11225                                            (self.instance.disks, self.instance),
11226                                            self.instance.name,
11227                                            False)
11228     for to_node, to_result in result.items():
11229       msg = to_result.fail_msg
11230       if msg:
11231         self.lu.LogWarning("Can't attach drbd disks on node %s: %s",
11232                            to_node, msg,
11233                            hint=("please do a gnt-instance info to see the"
11234                                  " status of disks"))
11235
11236     cstep = itertools.count(5)
11237
11238     if self.early_release:
11239       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11240       self._RemoveOldStorage(self.target_node, iv_names)
11241       # TODO: Check if releasing locks early still makes sense
11242       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
11243     else:
11244       # Release all resource locks except those used by the instance
11245       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES,
11246                     keep=self.node_secondary_ip.keys())
11247
11248     # TODO: Can the instance lock be downgraded here? Take the optional disk
11249     # shutdown in the caller into consideration.
11250
11251     # Wait for sync
11252     # This can fail as the old devices are degraded and _WaitForSync
11253     # does a combined result over all disks, so we don't check its return value
11254     self.lu.LogStep(cstep.next(), steps_total, "Sync devices")
11255     _WaitForSync(self.lu, self.instance)
11256
11257     # Check all devices manually
11258     self._CheckDevices(self.instance.primary_node, iv_names)
11259
11260     # Step: remove old storage
11261     if not self.early_release:
11262       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11263       self._RemoveOldStorage(self.target_node, iv_names)
11264
11265
11266 class LURepairNodeStorage(NoHooksLU):
11267   """Repairs the volume group on a node.
11268
11269   """
11270   REQ_BGL = False
11271
11272   def CheckArguments(self):
11273     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
11274
11275     storage_type = self.op.storage_type
11276
11277     if (constants.SO_FIX_CONSISTENCY not in
11278         constants.VALID_STORAGE_OPERATIONS.get(storage_type, [])):
11279       raise errors.OpPrereqError("Storage units of type '%s' can not be"
11280                                  " repaired" % storage_type,
11281                                  errors.ECODE_INVAL)
11282
11283   def ExpandNames(self):
11284     self.needed_locks = {
11285       locking.LEVEL_NODE: [self.op.node_name],
11286       }
11287
11288   def _CheckFaultyDisks(self, instance, node_name):
11289     """Ensure faulty disks abort the opcode or at least warn."""
11290     try:
11291       if _FindFaultyInstanceDisks(self.cfg, self.rpc, instance,
11292                                   node_name, True):
11293         raise errors.OpPrereqError("Instance '%s' has faulty disks on"
11294                                    " node '%s'" % (instance.name, node_name),
11295                                    errors.ECODE_STATE)
11296     except errors.OpPrereqError, err:
11297       if self.op.ignore_consistency:
11298         self.proc.LogWarning(str(err.args[0]))
11299       else:
11300         raise
11301
11302   def CheckPrereq(self):
11303     """Check prerequisites.
11304
11305     """
11306     # Check whether any instance on this node has faulty disks
11307     for inst in _GetNodeInstances(self.cfg, self.op.node_name):
11308       if inst.admin_state != constants.ADMINST_UP:
11309         continue
11310       check_nodes = set(inst.all_nodes)
11311       check_nodes.discard(self.op.node_name)
11312       for inst_node_name in check_nodes:
11313         self._CheckFaultyDisks(inst, inst_node_name)
11314
11315   def Exec(self, feedback_fn):
11316     feedback_fn("Repairing storage unit '%s' on %s ..." %
11317                 (self.op.name, self.op.node_name))
11318
11319     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
11320     result = self.rpc.call_storage_execute(self.op.node_name,
11321                                            self.op.storage_type, st_args,
11322                                            self.op.name,
11323                                            constants.SO_FIX_CONSISTENCY)
11324     result.Raise("Failed to repair storage unit '%s' on %s" %
11325                  (self.op.name, self.op.node_name))
11326
11327
11328 class LUNodeEvacuate(NoHooksLU):
11329   """Evacuates instances off a list of nodes.
11330
11331   """
11332   REQ_BGL = False
11333
11334   _MODE2IALLOCATOR = {
11335     constants.NODE_EVAC_PRI: constants.IALLOCATOR_NEVAC_PRI,
11336     constants.NODE_EVAC_SEC: constants.IALLOCATOR_NEVAC_SEC,
11337     constants.NODE_EVAC_ALL: constants.IALLOCATOR_NEVAC_ALL,
11338     }
11339   assert frozenset(_MODE2IALLOCATOR.keys()) == constants.NODE_EVAC_MODES
11340   assert (frozenset(_MODE2IALLOCATOR.values()) ==
11341           constants.IALLOCATOR_NEVAC_MODES)
11342
11343   def CheckArguments(self):
11344     _CheckIAllocatorOrNode(self, "iallocator", "remote_node")
11345
11346   def ExpandNames(self):
11347     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
11348
11349     if self.op.remote_node is not None:
11350       self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
11351       assert self.op.remote_node
11352
11353       if self.op.remote_node == self.op.node_name:
11354         raise errors.OpPrereqError("Can not use evacuated node as a new"
11355                                    " secondary node", errors.ECODE_INVAL)
11356
11357       if self.op.mode != constants.NODE_EVAC_SEC:
11358         raise errors.OpPrereqError("Without the use of an iallocator only"
11359                                    " secondary instances can be evacuated",
11360                                    errors.ECODE_INVAL)
11361
11362     # Declare locks
11363     self.share_locks = _ShareAll()
11364     self.needed_locks = {
11365       locking.LEVEL_INSTANCE: [],
11366       locking.LEVEL_NODEGROUP: [],
11367       locking.LEVEL_NODE: [],
11368       }
11369
11370     # Determine nodes (via group) optimistically, needs verification once locks
11371     # have been acquired
11372     self.lock_nodes = self._DetermineNodes()
11373
11374   def _DetermineNodes(self):
11375     """Gets the list of nodes to operate on.
11376
11377     """
11378     if self.op.remote_node is None:
11379       # Iallocator will choose any node(s) in the same group
11380       group_nodes = self.cfg.GetNodeGroupMembersByNodes([self.op.node_name])
11381     else:
11382       group_nodes = frozenset([self.op.remote_node])
11383
11384     # Determine nodes to be locked
11385     return set([self.op.node_name]) | group_nodes
11386
11387   def _DetermineInstances(self):
11388     """Builds list of instances to operate on.
11389
11390     """
11391     assert self.op.mode in constants.NODE_EVAC_MODES
11392
11393     if self.op.mode == constants.NODE_EVAC_PRI:
11394       # Primary instances only
11395       inst_fn = _GetNodePrimaryInstances
11396       assert self.op.remote_node is None, \
11397         "Evacuating primary instances requires iallocator"
11398     elif self.op.mode == constants.NODE_EVAC_SEC:
11399       # Secondary instances only
11400       inst_fn = _GetNodeSecondaryInstances
11401     else:
11402       # All instances
11403       assert self.op.mode == constants.NODE_EVAC_ALL
11404       inst_fn = _GetNodeInstances
11405       # TODO: In 2.6, change the iallocator interface to take an evacuation mode
11406       # per instance
11407       raise errors.OpPrereqError("Due to an issue with the iallocator"
11408                                  " interface it is not possible to evacuate"
11409                                  " all instances at once; specify explicitly"
11410                                  " whether to evacuate primary or secondary"
11411                                  " instances",
11412                                  errors.ECODE_INVAL)
11413
11414     return inst_fn(self.cfg, self.op.node_name)
11415
11416   def DeclareLocks(self, level):
11417     if level == locking.LEVEL_INSTANCE:
11418       # Lock instances optimistically, needs verification once node and group
11419       # locks have been acquired
11420       self.needed_locks[locking.LEVEL_INSTANCE] = \
11421         set(i.name for i in self._DetermineInstances())
11422
11423     elif level == locking.LEVEL_NODEGROUP:
11424       # Lock node groups for all potential target nodes optimistically, needs
11425       # verification once nodes have been acquired
11426       self.needed_locks[locking.LEVEL_NODEGROUP] = \
11427         self.cfg.GetNodeGroupsFromNodes(self.lock_nodes)
11428
11429     elif level == locking.LEVEL_NODE:
11430       self.needed_locks[locking.LEVEL_NODE] = self.lock_nodes
11431
11432   def CheckPrereq(self):
11433     # Verify locks
11434     owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
11435     owned_nodes = self.owned_locks(locking.LEVEL_NODE)
11436     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
11437
11438     need_nodes = self._DetermineNodes()
11439
11440     if not owned_nodes.issuperset(need_nodes):
11441       raise errors.OpPrereqError("Nodes in same group as '%s' changed since"
11442                                  " locks were acquired, current nodes are"
11443                                  " are '%s', used to be '%s'; retry the"
11444                                  " operation" %
11445                                  (self.op.node_name,
11446                                   utils.CommaJoin(need_nodes),
11447                                   utils.CommaJoin(owned_nodes)),
11448                                  errors.ECODE_STATE)
11449
11450     wanted_groups = self.cfg.GetNodeGroupsFromNodes(owned_nodes)
11451     if owned_groups != wanted_groups:
11452       raise errors.OpExecError("Node groups changed since locks were acquired,"
11453                                " current groups are '%s', used to be '%s';"
11454                                " retry the operation" %
11455                                (utils.CommaJoin(wanted_groups),
11456                                 utils.CommaJoin(owned_groups)))
11457
11458     # Determine affected instances
11459     self.instances = self._DetermineInstances()
11460     self.instance_names = [i.name for i in self.instances]
11461
11462     if set(self.instance_names) != owned_instances:
11463       raise errors.OpExecError("Instances on node '%s' changed since locks"
11464                                " were acquired, current instances are '%s',"
11465                                " used to be '%s'; retry the operation" %
11466                                (self.op.node_name,
11467                                 utils.CommaJoin(self.instance_names),
11468                                 utils.CommaJoin(owned_instances)))
11469
11470     if self.instance_names:
11471       self.LogInfo("Evacuating instances from node '%s': %s",
11472                    self.op.node_name,
11473                    utils.CommaJoin(utils.NiceSort(self.instance_names)))
11474     else:
11475       self.LogInfo("No instances to evacuate from node '%s'",
11476                    self.op.node_name)
11477
11478     if self.op.remote_node is not None:
11479       for i in self.instances:
11480         if i.primary_node == self.op.remote_node:
11481           raise errors.OpPrereqError("Node %s is the primary node of"
11482                                      " instance %s, cannot use it as"
11483                                      " secondary" %
11484                                      (self.op.remote_node, i.name),
11485                                      errors.ECODE_INVAL)
11486
11487   def Exec(self, feedback_fn):
11488     assert (self.op.iallocator is not None) ^ (self.op.remote_node is not None)
11489
11490     if not self.instance_names:
11491       # No instances to evacuate
11492       jobs = []
11493
11494     elif self.op.iallocator is not None:
11495       # TODO: Implement relocation to other group
11496       ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_NODE_EVAC,
11497                        evac_mode=self._MODE2IALLOCATOR[self.op.mode],
11498                        instances=list(self.instance_names))
11499
11500       ial.Run(self.op.iallocator)
11501
11502       if not ial.success:
11503         raise errors.OpPrereqError("Can't compute node evacuation using"
11504                                    " iallocator '%s': %s" %
11505                                    (self.op.iallocator, ial.info),
11506                                    errors.ECODE_NORES)
11507
11508       jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, True)
11509
11510     elif self.op.remote_node is not None:
11511       assert self.op.mode == constants.NODE_EVAC_SEC
11512       jobs = [
11513         [opcodes.OpInstanceReplaceDisks(instance_name=instance_name,
11514                                         remote_node=self.op.remote_node,
11515                                         disks=[],
11516                                         mode=constants.REPLACE_DISK_CHG,
11517                                         early_release=self.op.early_release)]
11518         for instance_name in self.instance_names
11519         ]
11520
11521     else:
11522       raise errors.ProgrammerError("No iallocator or remote node")
11523
11524     return ResultWithJobs(jobs)
11525
11526
11527 def _SetOpEarlyRelease(early_release, op):
11528   """Sets C{early_release} flag on opcodes if available.
11529
11530   """
11531   try:
11532     op.early_release = early_release
11533   except AttributeError:
11534     assert not isinstance(op, opcodes.OpInstanceReplaceDisks)
11535
11536   return op
11537
11538
11539 def _NodeEvacDest(use_nodes, group, nodes):
11540   """Returns group or nodes depending on caller's choice.
11541
11542   """
11543   if use_nodes:
11544     return utils.CommaJoin(nodes)
11545   else:
11546     return group
11547
11548
11549 def _LoadNodeEvacResult(lu, alloc_result, early_release, use_nodes):
11550   """Unpacks the result of change-group and node-evacuate iallocator requests.
11551
11552   Iallocator modes L{constants.IALLOCATOR_MODE_NODE_EVAC} and
11553   L{constants.IALLOCATOR_MODE_CHG_GROUP}.
11554
11555   @type lu: L{LogicalUnit}
11556   @param lu: Logical unit instance
11557   @type alloc_result: tuple/list
11558   @param alloc_result: Result from iallocator
11559   @type early_release: bool
11560   @param early_release: Whether to release locks early if possible
11561   @type use_nodes: bool
11562   @param use_nodes: Whether to display node names instead of groups
11563
11564   """
11565   (moved, failed, jobs) = alloc_result
11566
11567   if failed:
11568     failreason = utils.CommaJoin("%s (%s)" % (name, reason)
11569                                  for (name, reason) in failed)
11570     lu.LogWarning("Unable to evacuate instances %s", failreason)
11571     raise errors.OpExecError("Unable to evacuate instances %s" % failreason)
11572
11573   if moved:
11574     lu.LogInfo("Instances to be moved: %s",
11575                utils.CommaJoin("%s (to %s)" %
11576                                (name, _NodeEvacDest(use_nodes, group, nodes))
11577                                for (name, group, nodes) in moved))
11578
11579   return [map(compat.partial(_SetOpEarlyRelease, early_release),
11580               map(opcodes.OpCode.LoadOpCode, ops))
11581           for ops in jobs]
11582
11583
11584 class LUInstanceGrowDisk(LogicalUnit):
11585   """Grow a disk of an instance.
11586
11587   """
11588   HPATH = "disk-grow"
11589   HTYPE = constants.HTYPE_INSTANCE
11590   REQ_BGL = False
11591
11592   def ExpandNames(self):
11593     self._ExpandAndLockInstance()
11594     self.needed_locks[locking.LEVEL_NODE] = []
11595     self.needed_locks[locking.LEVEL_NODE_RES] = []
11596     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
11597     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
11598
11599   def DeclareLocks(self, level):
11600     if level == locking.LEVEL_NODE:
11601       self._LockInstancesNodes()
11602     elif level == locking.LEVEL_NODE_RES:
11603       # Copy node locks
11604       self.needed_locks[locking.LEVEL_NODE_RES] = \
11605         self.needed_locks[locking.LEVEL_NODE][:]
11606
11607   def BuildHooksEnv(self):
11608     """Build hooks env.
11609
11610     This runs on the master, the primary and all the secondaries.
11611
11612     """
11613     env = {
11614       "DISK": self.op.disk,
11615       "AMOUNT": self.op.amount,
11616       "ABSOLUTE": self.op.absolute,
11617       }
11618     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
11619     return env
11620
11621   def BuildHooksNodes(self):
11622     """Build hooks nodes.
11623
11624     """
11625     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
11626     return (nl, nl)
11627
11628   def CheckPrereq(self):
11629     """Check prerequisites.
11630
11631     This checks that the instance is in the cluster.
11632
11633     """
11634     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
11635     assert instance is not None, \
11636       "Cannot retrieve locked instance %s" % self.op.instance_name
11637     nodenames = list(instance.all_nodes)
11638     for node in nodenames:
11639       _CheckNodeOnline(self, node)
11640
11641     self.instance = instance
11642
11643     if instance.disk_template not in constants.DTS_GROWABLE:
11644       raise errors.OpPrereqError("Instance's disk layout does not support"
11645                                  " growing", errors.ECODE_INVAL)
11646
11647     self.disk = instance.FindDisk(self.op.disk)
11648
11649     if self.op.absolute:
11650       self.target = self.op.amount
11651       self.delta = self.target - self.disk.size
11652       if self.delta < 0:
11653         raise errors.OpPrereqError("Requested size (%s) is smaller than "
11654                                    "current disk size (%s)" %
11655                                    (utils.FormatUnit(self.target, "h"),
11656                                     utils.FormatUnit(self.disk.size, "h")),
11657                                    errors.ECODE_STATE)
11658     else:
11659       self.delta = self.op.amount
11660       self.target = self.disk.size + self.delta
11661       if self.delta < 0:
11662         raise errors.OpPrereqError("Requested increment (%s) is negative" %
11663                                    utils.FormatUnit(self.delta, "h"),
11664                                    errors.ECODE_INVAL)
11665
11666     if instance.disk_template not in (constants.DT_FILE,
11667                                       constants.DT_SHARED_FILE,
11668                                       constants.DT_RBD):
11669       # TODO: check the free disk space for file, when that feature will be
11670       # supported
11671       _CheckNodesFreeDiskPerVG(self, nodenames,
11672                                self.disk.ComputeGrowth(self.delta))
11673
11674   def Exec(self, feedback_fn):
11675     """Execute disk grow.
11676
11677     """
11678     instance = self.instance
11679     disk = self.disk
11680
11681     assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
11682     assert (self.owned_locks(locking.LEVEL_NODE) ==
11683             self.owned_locks(locking.LEVEL_NODE_RES))
11684
11685     disks_ok, _ = _AssembleInstanceDisks(self, self.instance, disks=[disk])
11686     if not disks_ok:
11687       raise errors.OpExecError("Cannot activate block device to grow")
11688
11689     feedback_fn("Growing disk %s of instance '%s' by %s to %s" %
11690                 (self.op.disk, instance.name,
11691                  utils.FormatUnit(self.delta, "h"),
11692                  utils.FormatUnit(self.target, "h")))
11693
11694     # First run all grow ops in dry-run mode
11695     for node in instance.all_nodes:
11696       self.cfg.SetDiskID(disk, node)
11697       result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
11698                                            True, True)
11699       result.Raise("Grow request failed to node %s" % node)
11700
11701     # We know that (as far as we can test) operations across different
11702     # nodes will succeed, time to run it for real on the backing storage
11703     for node in instance.all_nodes:
11704       self.cfg.SetDiskID(disk, node)
11705       result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
11706                                            False, True)
11707       result.Raise("Grow request failed to node %s" % node)
11708
11709     # And now execute it for logical storage, on the primary node
11710     node = instance.primary_node
11711     self.cfg.SetDiskID(disk, node)
11712     result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
11713                                          False, False)
11714     result.Raise("Grow request failed to node %s" % node)
11715
11716     disk.RecordGrow(self.delta)
11717     self.cfg.Update(instance, feedback_fn)
11718
11719     # Changes have been recorded, release node lock
11720     _ReleaseLocks(self, locking.LEVEL_NODE)
11721
11722     # Downgrade lock while waiting for sync
11723     self.glm.downgrade(locking.LEVEL_INSTANCE)
11724
11725     if self.op.wait_for_sync:
11726       disk_abort = not _WaitForSync(self, instance, disks=[disk])
11727       if disk_abort:
11728         self.proc.LogWarning("Disk sync-ing has not returned a good"
11729                              " status; please check the instance")
11730       if instance.admin_state != constants.ADMINST_UP:
11731         _SafeShutdownInstanceDisks(self, instance, disks=[disk])
11732     elif instance.admin_state != constants.ADMINST_UP:
11733       self.proc.LogWarning("Not shutting down the disk even if the instance is"
11734                            " not supposed to be running because no wait for"
11735                            " sync mode was requested")
11736
11737     assert self.owned_locks(locking.LEVEL_NODE_RES)
11738     assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
11739
11740
11741 class LUInstanceQueryData(NoHooksLU):
11742   """Query runtime instance data.
11743
11744   """
11745   REQ_BGL = False
11746
11747   def ExpandNames(self):
11748     self.needed_locks = {}
11749
11750     # Use locking if requested or when non-static information is wanted
11751     if not (self.op.static or self.op.use_locking):
11752       self.LogWarning("Non-static data requested, locks need to be acquired")
11753       self.op.use_locking = True
11754
11755     if self.op.instances or not self.op.use_locking:
11756       # Expand instance names right here
11757       self.wanted_names = _GetWantedInstances(self, self.op.instances)
11758     else:
11759       # Will use acquired locks
11760       self.wanted_names = None
11761
11762     if self.op.use_locking:
11763       self.share_locks = _ShareAll()
11764
11765       if self.wanted_names is None:
11766         self.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET
11767       else:
11768         self.needed_locks[locking.LEVEL_INSTANCE] = self.wanted_names
11769
11770       self.needed_locks[locking.LEVEL_NODEGROUP] = []
11771       self.needed_locks[locking.LEVEL_NODE] = []
11772       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
11773
11774   def DeclareLocks(self, level):
11775     if self.op.use_locking:
11776       if level == locking.LEVEL_NODEGROUP:
11777         owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
11778
11779         # Lock all groups used by instances optimistically; this requires going
11780         # via the node before it's locked, requiring verification later on
11781         self.needed_locks[locking.LEVEL_NODEGROUP] = \
11782           frozenset(group_uuid
11783                     for instance_name in owned_instances
11784                     for group_uuid in
11785                       self.cfg.GetInstanceNodeGroups(instance_name))
11786
11787       elif level == locking.LEVEL_NODE:
11788         self._LockInstancesNodes()
11789
11790   def CheckPrereq(self):
11791     """Check prerequisites.
11792
11793     This only checks the optional instance list against the existing names.
11794
11795     """
11796     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
11797     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
11798     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
11799
11800     if self.wanted_names is None:
11801       assert self.op.use_locking, "Locking was not used"
11802       self.wanted_names = owned_instances
11803
11804     instances = dict(self.cfg.GetMultiInstanceInfo(self.wanted_names))
11805
11806     if self.op.use_locking:
11807       _CheckInstancesNodeGroups(self.cfg, instances, owned_groups, owned_nodes,
11808                                 None)
11809     else:
11810       assert not (owned_instances or owned_groups or owned_nodes)
11811
11812     self.wanted_instances = instances.values()
11813
11814   def _ComputeBlockdevStatus(self, node, instance, dev):
11815     """Returns the status of a block device
11816
11817     """
11818     if self.op.static or not node:
11819       return None
11820
11821     self.cfg.SetDiskID(dev, node)
11822
11823     result = self.rpc.call_blockdev_find(node, dev)
11824     if result.offline:
11825       return None
11826
11827     result.Raise("Can't compute disk status for %s" % instance.name)
11828
11829     status = result.payload
11830     if status is None:
11831       return None
11832
11833     return (status.dev_path, status.major, status.minor,
11834             status.sync_percent, status.estimated_time,
11835             status.is_degraded, status.ldisk_status)
11836
11837   def _ComputeDiskStatus(self, instance, snode, dev):
11838     """Compute block device status.
11839
11840     """
11841     (anno_dev,) = _AnnotateDiskParams(instance, [dev], self.cfg)
11842
11843     return self._ComputeDiskStatusInner(instance, snode, anno_dev)
11844
11845   def _ComputeDiskStatusInner(self, instance, snode, dev):
11846     """Compute block device status.
11847
11848     @attention: The device has to be annotated already.
11849
11850     """
11851     if dev.dev_type in constants.LDS_DRBD:
11852       # we change the snode then (otherwise we use the one passed in)
11853       if dev.logical_id[0] == instance.primary_node:
11854         snode = dev.logical_id[1]
11855       else:
11856         snode = dev.logical_id[0]
11857
11858     dev_pstatus = self._ComputeBlockdevStatus(instance.primary_node,
11859                                               instance, dev)
11860     dev_sstatus = self._ComputeBlockdevStatus(snode, instance, dev)
11861
11862     if dev.children:
11863       dev_children = map(compat.partial(self._ComputeDiskStatusInner,
11864                                         instance, snode),
11865                          dev.children)
11866     else:
11867       dev_children = []
11868
11869     return {
11870       "iv_name": dev.iv_name,
11871       "dev_type": dev.dev_type,
11872       "logical_id": dev.logical_id,
11873       "physical_id": dev.physical_id,
11874       "pstatus": dev_pstatus,
11875       "sstatus": dev_sstatus,
11876       "children": dev_children,
11877       "mode": dev.mode,
11878       "size": dev.size,
11879       }
11880
11881   def Exec(self, feedback_fn):
11882     """Gather and return data"""
11883     result = {}
11884
11885     cluster = self.cfg.GetClusterInfo()
11886
11887     node_names = itertools.chain(*(i.all_nodes for i in self.wanted_instances))
11888     nodes = dict(self.cfg.GetMultiNodeInfo(node_names))
11889
11890     groups = dict(self.cfg.GetMultiNodeGroupInfo(node.group
11891                                                  for node in nodes.values()))
11892
11893     group2name_fn = lambda uuid: groups[uuid].name
11894
11895     for instance in self.wanted_instances:
11896       pnode = nodes[instance.primary_node]
11897
11898       if self.op.static or pnode.offline:
11899         remote_state = None
11900         if pnode.offline:
11901           self.LogWarning("Primary node %s is marked offline, returning static"
11902                           " information only for instance %s" %
11903                           (pnode.name, instance.name))
11904       else:
11905         remote_info = self.rpc.call_instance_info(instance.primary_node,
11906                                                   instance.name,
11907                                                   instance.hypervisor)
11908         remote_info.Raise("Error checking node %s" % instance.primary_node)
11909         remote_info = remote_info.payload
11910         if remote_info and "state" in remote_info:
11911           remote_state = "up"
11912         else:
11913           if instance.admin_state == constants.ADMINST_UP:
11914             remote_state = "down"
11915           else:
11916             remote_state = instance.admin_state
11917
11918       disks = map(compat.partial(self._ComputeDiskStatus, instance, None),
11919                   instance.disks)
11920
11921       snodes_group_uuids = [nodes[snode_name].group
11922                             for snode_name in instance.secondary_nodes]
11923
11924       result[instance.name] = {
11925         "name": instance.name,
11926         "config_state": instance.admin_state,
11927         "run_state": remote_state,
11928         "pnode": instance.primary_node,
11929         "pnode_group_uuid": pnode.group,
11930         "pnode_group_name": group2name_fn(pnode.group),
11931         "snodes": instance.secondary_nodes,
11932         "snodes_group_uuids": snodes_group_uuids,
11933         "snodes_group_names": map(group2name_fn, snodes_group_uuids),
11934         "os": instance.os,
11935         # this happens to be the same format used for hooks
11936         "nics": _NICListToTuple(self, instance.nics),
11937         "disk_template": instance.disk_template,
11938         "disks": disks,
11939         "hypervisor": instance.hypervisor,
11940         "network_port": instance.network_port,
11941         "hv_instance": instance.hvparams,
11942         "hv_actual": cluster.FillHV(instance, skip_globals=True),
11943         "be_instance": instance.beparams,
11944         "be_actual": cluster.FillBE(instance),
11945         "os_instance": instance.osparams,
11946         "os_actual": cluster.SimpleFillOS(instance.os, instance.osparams),
11947         "serial_no": instance.serial_no,
11948         "mtime": instance.mtime,
11949         "ctime": instance.ctime,
11950         "uuid": instance.uuid,
11951         }
11952
11953     return result
11954
11955
11956 def PrepareContainerMods(mods, private_fn):
11957   """Prepares a list of container modifications by adding a private data field.
11958
11959   @type mods: list of tuples; (operation, index, parameters)
11960   @param mods: List of modifications
11961   @type private_fn: callable or None
11962   @param private_fn: Callable for constructing a private data field for a
11963     modification
11964   @rtype: list
11965
11966   """
11967   if private_fn is None:
11968     fn = lambda: None
11969   else:
11970     fn = private_fn
11971
11972   return [(op, idx, params, fn()) for (op, idx, params) in mods]
11973
11974
11975 #: Type description for changes as returned by L{ApplyContainerMods}'s
11976 #: callbacks
11977 _TApplyContModsCbChanges = \
11978   ht.TMaybeListOf(ht.TAnd(ht.TIsLength(2), ht.TItems([
11979     ht.TNonEmptyString,
11980     ht.TAny,
11981     ])))
11982
11983
11984 def ApplyContainerMods(kind, container, chgdesc, mods,
11985                        create_fn, modify_fn, remove_fn):
11986   """Applies descriptions in C{mods} to C{container}.
11987
11988   @type kind: string
11989   @param kind: One-word item description
11990   @type container: list
11991   @param container: Container to modify
11992   @type chgdesc: None or list
11993   @param chgdesc: List of applied changes
11994   @type mods: list
11995   @param mods: Modifications as returned by L{PrepareContainerMods}
11996   @type create_fn: callable
11997   @param create_fn: Callback for creating a new item (L{constants.DDM_ADD});
11998     receives absolute item index, parameters and private data object as added
11999     by L{PrepareContainerMods}, returns tuple containing new item and changes
12000     as list
12001   @type modify_fn: callable
12002   @param modify_fn: Callback for modifying an existing item
12003     (L{constants.DDM_MODIFY}); receives absolute item index, item, parameters
12004     and private data object as added by L{PrepareContainerMods}, returns
12005     changes as list
12006   @type remove_fn: callable
12007   @param remove_fn: Callback on removing item; receives absolute item index,
12008     item and private data object as added by L{PrepareContainerMods}
12009
12010   """
12011   for (op, idx, params, private) in mods:
12012     if idx == -1:
12013       # Append
12014       absidx = len(container) - 1
12015     elif idx < 0:
12016       raise IndexError("Not accepting negative indices other than -1")
12017     elif idx > len(container):
12018       raise IndexError("Got %s index %s, but there are only %s" %
12019                        (kind, idx, len(container)))
12020     else:
12021       absidx = idx
12022
12023     changes = None
12024
12025     if op == constants.DDM_ADD:
12026       # Calculate where item will be added
12027       if idx == -1:
12028         addidx = len(container)
12029       else:
12030         addidx = idx
12031
12032       if create_fn is None:
12033         item = params
12034       else:
12035         (item, changes) = create_fn(addidx, params, private)
12036
12037       if idx == -1:
12038         container.append(item)
12039       else:
12040         assert idx >= 0
12041         assert idx <= len(container)
12042         # list.insert does so before the specified index
12043         container.insert(idx, item)
12044     else:
12045       # Retrieve existing item
12046       try:
12047         item = container[absidx]
12048       except IndexError:
12049         raise IndexError("Invalid %s index %s" % (kind, idx))
12050
12051       if op == constants.DDM_REMOVE:
12052         assert not params
12053
12054         if remove_fn is not None:
12055           remove_fn(absidx, item, private)
12056
12057         changes = [("%s/%s" % (kind, absidx), "remove")]
12058
12059         assert container[absidx] == item
12060         del container[absidx]
12061       elif op == constants.DDM_MODIFY:
12062         if modify_fn is not None:
12063           changes = modify_fn(absidx, item, params, private)
12064       else:
12065         raise errors.ProgrammerError("Unhandled operation '%s'" % op)
12066
12067     assert _TApplyContModsCbChanges(changes)
12068
12069     if not (chgdesc is None or changes is None):
12070       chgdesc.extend(changes)
12071
12072
12073 def _UpdateIvNames(base_index, disks):
12074   """Updates the C{iv_name} attribute of disks.
12075
12076   @type disks: list of L{objects.Disk}
12077
12078   """
12079   for (idx, disk) in enumerate(disks):
12080     disk.iv_name = "disk/%s" % (base_index + idx, )
12081
12082
12083 class _InstNicModPrivate:
12084   """Data structure for network interface modifications.
12085
12086   Used by L{LUInstanceSetParams}.
12087
12088   """
12089   def __init__(self):
12090     self.params = None
12091     self.filled = None
12092
12093
12094 class LUInstanceSetParams(LogicalUnit):
12095   """Modifies an instances's parameters.
12096
12097   """
12098   HPATH = "instance-modify"
12099   HTYPE = constants.HTYPE_INSTANCE
12100   REQ_BGL = False
12101
12102   @staticmethod
12103   def _UpgradeDiskNicMods(kind, mods, verify_fn):
12104     assert ht.TList(mods)
12105     assert not mods or len(mods[0]) in (2, 3)
12106
12107     if mods and len(mods[0]) == 2:
12108       result = []
12109
12110       addremove = 0
12111       for op, params in mods:
12112         if op in (constants.DDM_ADD, constants.DDM_REMOVE):
12113           result.append((op, -1, params))
12114           addremove += 1
12115
12116           if addremove > 1:
12117             raise errors.OpPrereqError("Only one %s add or remove operation is"
12118                                        " supported at a time" % kind,
12119                                        errors.ECODE_INVAL)
12120         else:
12121           result.append((constants.DDM_MODIFY, op, params))
12122
12123       assert verify_fn(result)
12124     else:
12125       result = mods
12126
12127     return result
12128
12129   @staticmethod
12130   def _CheckMods(kind, mods, key_types, item_fn):
12131     """Ensures requested disk/NIC modifications are valid.
12132
12133     """
12134     for (op, _, params) in mods:
12135       assert ht.TDict(params)
12136
12137       utils.ForceDictType(params, key_types)
12138
12139       if op == constants.DDM_REMOVE:
12140         if params:
12141           raise errors.OpPrereqError("No settings should be passed when"
12142                                      " removing a %s" % kind,
12143                                      errors.ECODE_INVAL)
12144       elif op in (constants.DDM_ADD, constants.DDM_MODIFY):
12145         item_fn(op, params)
12146       else:
12147         raise errors.ProgrammerError("Unhandled operation '%s'" % op)
12148
12149   @staticmethod
12150   def _VerifyDiskModification(op, params):
12151     """Verifies a disk modification.
12152
12153     """
12154     if op == constants.DDM_ADD:
12155       mode = params.setdefault(constants.IDISK_MODE, constants.DISK_RDWR)
12156       if mode not in constants.DISK_ACCESS_SET:
12157         raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
12158                                    errors.ECODE_INVAL)
12159
12160       size = params.get(constants.IDISK_SIZE, None)
12161       if size is None:
12162         raise errors.OpPrereqError("Required disk parameter '%s' missing" %
12163                                    constants.IDISK_SIZE, errors.ECODE_INVAL)
12164
12165       try:
12166         size = int(size)
12167       except (TypeError, ValueError), err:
12168         raise errors.OpPrereqError("Invalid disk size parameter: %s" % err,
12169                                    errors.ECODE_INVAL)
12170
12171       params[constants.IDISK_SIZE] = size
12172
12173     elif op == constants.DDM_MODIFY and constants.IDISK_SIZE in params:
12174       raise errors.OpPrereqError("Disk size change not possible, use"
12175                                  " grow-disk", errors.ECODE_INVAL)
12176
12177   @staticmethod
12178   def _VerifyNicModification(op, params):
12179     """Verifies a network interface modification.
12180
12181     """
12182     if op in (constants.DDM_ADD, constants.DDM_MODIFY):
12183       ip = params.get(constants.INIC_IP, None)
12184       if ip is None:
12185         pass
12186       elif ip.lower() == constants.VALUE_NONE:
12187         params[constants.INIC_IP] = None
12188       elif not netutils.IPAddress.IsValid(ip):
12189         raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
12190                                    errors.ECODE_INVAL)
12191
12192       bridge = params.get("bridge", None)
12193       link = params.get(constants.INIC_LINK, None)
12194       if bridge and link:
12195         raise errors.OpPrereqError("Cannot pass 'bridge' and 'link'"
12196                                    " at the same time", errors.ECODE_INVAL)
12197       elif bridge and bridge.lower() == constants.VALUE_NONE:
12198         params["bridge"] = None
12199       elif link and link.lower() == constants.VALUE_NONE:
12200         params[constants.INIC_LINK] = None
12201
12202       if op == constants.DDM_ADD:
12203         macaddr = params.get(constants.INIC_MAC, None)
12204         if macaddr is None:
12205           params[constants.INIC_MAC] = constants.VALUE_AUTO
12206
12207       if constants.INIC_MAC in params:
12208         macaddr = params[constants.INIC_MAC]
12209         if macaddr not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
12210           macaddr = utils.NormalizeAndValidateMac(macaddr)
12211
12212         if op == constants.DDM_MODIFY and macaddr == constants.VALUE_AUTO:
12213           raise errors.OpPrereqError("'auto' is not a valid MAC address when"
12214                                      " modifying an existing NIC",
12215                                      errors.ECODE_INVAL)
12216
12217   def CheckArguments(self):
12218     if not (self.op.nics or self.op.disks or self.op.disk_template or
12219             self.op.hvparams or self.op.beparams or self.op.os_name or
12220             self.op.offline is not None or self.op.runtime_mem):
12221       raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
12222
12223     if self.op.hvparams:
12224       _CheckGlobalHvParams(self.op.hvparams)
12225
12226     self.op.disks = \
12227       self._UpgradeDiskNicMods("disk", self.op.disks,
12228         opcodes.OpInstanceSetParams.TestDiskModifications)
12229     self.op.nics = \
12230       self._UpgradeDiskNicMods("NIC", self.op.nics,
12231         opcodes.OpInstanceSetParams.TestNicModifications)
12232
12233     # Check disk modifications
12234     self._CheckMods("disk", self.op.disks, constants.IDISK_PARAMS_TYPES,
12235                     self._VerifyDiskModification)
12236
12237     if self.op.disks and self.op.disk_template is not None:
12238       raise errors.OpPrereqError("Disk template conversion and other disk"
12239                                  " changes not supported at the same time",
12240                                  errors.ECODE_INVAL)
12241
12242     if (self.op.disk_template and
12243         self.op.disk_template in constants.DTS_INT_MIRROR and
12244         self.op.remote_node is None):
12245       raise errors.OpPrereqError("Changing the disk template to a mirrored"
12246                                  " one requires specifying a secondary node",
12247                                  errors.ECODE_INVAL)
12248
12249     # Check NIC modifications
12250     self._CheckMods("NIC", self.op.nics, constants.INIC_PARAMS_TYPES,
12251                     self._VerifyNicModification)
12252
12253   def ExpandNames(self):
12254     self._ExpandAndLockInstance()
12255     # Can't even acquire node locks in shared mode as upcoming changes in
12256     # Ganeti 2.6 will start to modify the node object on disk conversion
12257     self.needed_locks[locking.LEVEL_NODE] = []
12258     self.needed_locks[locking.LEVEL_NODE_RES] = []
12259     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
12260
12261   def DeclareLocks(self, level):
12262     # TODO: Acquire group lock in shared mode (disk parameters)
12263     if level == locking.LEVEL_NODE:
12264       self._LockInstancesNodes()
12265       if self.op.disk_template and self.op.remote_node:
12266         self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
12267         self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
12268     elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
12269       # Copy node locks
12270       self.needed_locks[locking.LEVEL_NODE_RES] = \
12271         self.needed_locks[locking.LEVEL_NODE][:]
12272
12273   def BuildHooksEnv(self):
12274     """Build hooks env.
12275
12276     This runs on the master, primary and secondaries.
12277
12278     """
12279     args = dict()
12280     if constants.BE_MINMEM in self.be_new:
12281       args["minmem"] = self.be_new[constants.BE_MINMEM]
12282     if constants.BE_MAXMEM in self.be_new:
12283       args["maxmem"] = self.be_new[constants.BE_MAXMEM]
12284     if constants.BE_VCPUS in self.be_new:
12285       args["vcpus"] = self.be_new[constants.BE_VCPUS]
12286     # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
12287     # information at all.
12288
12289     if self._new_nics is not None:
12290       nics = []
12291
12292       for nic in self._new_nics:
12293         nicparams = self.cluster.SimpleFillNIC(nic.nicparams)
12294         mode = nicparams[constants.NIC_MODE]
12295         link = nicparams[constants.NIC_LINK]
12296         nics.append((nic.ip, nic.mac, mode, link))
12297
12298       args["nics"] = nics
12299
12300     env = _BuildInstanceHookEnvByObject(self, self.instance, override=args)
12301     if self.op.disk_template:
12302       env["NEW_DISK_TEMPLATE"] = self.op.disk_template
12303     if self.op.runtime_mem:
12304       env["RUNTIME_MEMORY"] = self.op.runtime_mem
12305
12306     return env
12307
12308   def BuildHooksNodes(self):
12309     """Build hooks nodes.
12310
12311     """
12312     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
12313     return (nl, nl)
12314
12315   def _PrepareNicModification(self, params, private, old_ip, old_params,
12316                               cluster, pnode):
12317     update_params_dict = dict([(key, params[key])
12318                                for key in constants.NICS_PARAMETERS
12319                                if key in params])
12320
12321     if "bridge" in params:
12322       update_params_dict[constants.NIC_LINK] = params["bridge"]
12323
12324     new_params = _GetUpdatedParams(old_params, update_params_dict)
12325     utils.ForceDictType(new_params, constants.NICS_PARAMETER_TYPES)
12326
12327     new_filled_params = cluster.SimpleFillNIC(new_params)
12328     objects.NIC.CheckParameterSyntax(new_filled_params)
12329
12330     new_mode = new_filled_params[constants.NIC_MODE]
12331     if new_mode == constants.NIC_MODE_BRIDGED:
12332       bridge = new_filled_params[constants.NIC_LINK]
12333       msg = self.rpc.call_bridges_exist(pnode, [bridge]).fail_msg
12334       if msg:
12335         msg = "Error checking bridges on node '%s': %s" % (pnode, msg)
12336         if self.op.force:
12337           self.warn.append(msg)
12338         else:
12339           raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
12340
12341     elif new_mode == constants.NIC_MODE_ROUTED:
12342       ip = params.get(constants.INIC_IP, old_ip)
12343       if ip is None:
12344         raise errors.OpPrereqError("Cannot set the NIC IP address to None"
12345                                    " on a routed NIC", errors.ECODE_INVAL)
12346
12347     if constants.INIC_MAC in params:
12348       mac = params[constants.INIC_MAC]
12349       if mac is None:
12350         raise errors.OpPrereqError("Cannot unset the NIC MAC address",
12351                                    errors.ECODE_INVAL)
12352       elif mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
12353         # otherwise generate the MAC address
12354         params[constants.INIC_MAC] = \
12355           self.cfg.GenerateMAC(self.proc.GetECId())
12356       else:
12357         # or validate/reserve the current one
12358         try:
12359           self.cfg.ReserveMAC(mac, self.proc.GetECId())
12360         except errors.ReservationError:
12361           raise errors.OpPrereqError("MAC address '%s' already in use"
12362                                      " in cluster" % mac,
12363                                      errors.ECODE_NOTUNIQUE)
12364
12365     private.params = new_params
12366     private.filled = new_filled_params
12367
12368   def CheckPrereq(self):
12369     """Check prerequisites.
12370
12371     This only checks the instance list against the existing names.
12372
12373     """
12374     # checking the new params on the primary/secondary nodes
12375
12376     instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
12377     cluster = self.cluster = self.cfg.GetClusterInfo()
12378     assert self.instance is not None, \
12379       "Cannot retrieve locked instance %s" % self.op.instance_name
12380     pnode = instance.primary_node
12381     nodelist = list(instance.all_nodes)
12382     pnode_info = self.cfg.GetNodeInfo(pnode)
12383     self.diskparams = self.cfg.GetInstanceDiskParams(instance)
12384
12385     # Prepare disk/NIC modifications
12386     self.diskmod = PrepareContainerMods(self.op.disks, None)
12387     self.nicmod = PrepareContainerMods(self.op.nics, _InstNicModPrivate)
12388
12389     # OS change
12390     if self.op.os_name and not self.op.force:
12391       _CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
12392                       self.op.force_variant)
12393       instance_os = self.op.os_name
12394     else:
12395       instance_os = instance.os
12396
12397     assert not (self.op.disk_template and self.op.disks), \
12398       "Can't modify disk template and apply disk changes at the same time"
12399
12400     if self.op.disk_template:
12401       if instance.disk_template == self.op.disk_template:
12402         raise errors.OpPrereqError("Instance already has disk template %s" %
12403                                    instance.disk_template, errors.ECODE_INVAL)
12404
12405       if (instance.disk_template,
12406           self.op.disk_template) not in self._DISK_CONVERSIONS:
12407         raise errors.OpPrereqError("Unsupported disk template conversion from"
12408                                    " %s to %s" % (instance.disk_template,
12409                                                   self.op.disk_template),
12410                                    errors.ECODE_INVAL)
12411       _CheckInstanceState(self, instance, INSTANCE_DOWN,
12412                           msg="cannot change disk template")
12413       if self.op.disk_template in constants.DTS_INT_MIRROR:
12414         if self.op.remote_node == pnode:
12415           raise errors.OpPrereqError("Given new secondary node %s is the same"
12416                                      " as the primary node of the instance" %
12417                                      self.op.remote_node, errors.ECODE_STATE)
12418         _CheckNodeOnline(self, self.op.remote_node)
12419         _CheckNodeNotDrained(self, self.op.remote_node)
12420         # FIXME: here we assume that the old instance type is DT_PLAIN
12421         assert instance.disk_template == constants.DT_PLAIN
12422         disks = [{constants.IDISK_SIZE: d.size,
12423                   constants.IDISK_VG: d.logical_id[0]}
12424                  for d in instance.disks]
12425         required = _ComputeDiskSizePerVG(self.op.disk_template, disks)
12426         _CheckNodesFreeDiskPerVG(self, [self.op.remote_node], required)
12427
12428         snode_info = self.cfg.GetNodeInfo(self.op.remote_node)
12429         snode_group = self.cfg.GetNodeGroup(snode_info.group)
12430         ipolicy = _CalculateGroupIPolicy(cluster, snode_group)
12431         _CheckTargetNodeIPolicy(self, ipolicy, instance, snode_info,
12432                                 ignore=self.op.ignore_ipolicy)
12433         if pnode_info.group != snode_info.group:
12434           self.LogWarning("The primary and secondary nodes are in two"
12435                           " different node groups; the disk parameters"
12436                           " from the first disk's node group will be"
12437                           " used")
12438
12439     # hvparams processing
12440     if self.op.hvparams:
12441       hv_type = instance.hypervisor
12442       i_hvdict = _GetUpdatedParams(instance.hvparams, self.op.hvparams)
12443       utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
12444       hv_new = cluster.SimpleFillHV(hv_type, instance.os, i_hvdict)
12445
12446       # local check
12447       hypervisor.GetHypervisor(hv_type).CheckParameterSyntax(hv_new)
12448       _CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
12449       self.hv_proposed = self.hv_new = hv_new # the new actual values
12450       self.hv_inst = i_hvdict # the new dict (without defaults)
12451     else:
12452       self.hv_proposed = cluster.SimpleFillHV(instance.hypervisor, instance.os,
12453                                               instance.hvparams)
12454       self.hv_new = self.hv_inst = {}
12455
12456     # beparams processing
12457     if self.op.beparams:
12458       i_bedict = _GetUpdatedParams(instance.beparams, self.op.beparams,
12459                                    use_none=True)
12460       objects.UpgradeBeParams(i_bedict)
12461       utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
12462       be_new = cluster.SimpleFillBE(i_bedict)
12463       self.be_proposed = self.be_new = be_new # the new actual values
12464       self.be_inst = i_bedict # the new dict (without defaults)
12465     else:
12466       self.be_new = self.be_inst = {}
12467       self.be_proposed = cluster.SimpleFillBE(instance.beparams)
12468     be_old = cluster.FillBE(instance)
12469
12470     # CPU param validation -- checking every time a parameter is
12471     # changed to cover all cases where either CPU mask or vcpus have
12472     # changed
12473     if (constants.BE_VCPUS in self.be_proposed and
12474         constants.HV_CPU_MASK in self.hv_proposed):
12475       cpu_list = \
12476         utils.ParseMultiCpuMask(self.hv_proposed[constants.HV_CPU_MASK])
12477       # Verify mask is consistent with number of vCPUs. Can skip this
12478       # test if only 1 entry in the CPU mask, which means same mask
12479       # is applied to all vCPUs.
12480       if (len(cpu_list) > 1 and
12481           len(cpu_list) != self.be_proposed[constants.BE_VCPUS]):
12482         raise errors.OpPrereqError("Number of vCPUs [%d] does not match the"
12483                                    " CPU mask [%s]" %
12484                                    (self.be_proposed[constants.BE_VCPUS],
12485                                     self.hv_proposed[constants.HV_CPU_MASK]),
12486                                    errors.ECODE_INVAL)
12487
12488       # Only perform this test if a new CPU mask is given
12489       if constants.HV_CPU_MASK in self.hv_new:
12490         # Calculate the largest CPU number requested
12491         max_requested_cpu = max(map(max, cpu_list))
12492         # Check that all of the instance's nodes have enough physical CPUs to
12493         # satisfy the requested CPU mask
12494         _CheckNodesPhysicalCPUs(self, instance.all_nodes,
12495                                 max_requested_cpu + 1, instance.hypervisor)
12496
12497     # osparams processing
12498     if self.op.osparams:
12499       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
12500       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
12501       self.os_inst = i_osdict # the new dict (without defaults)
12502     else:
12503       self.os_inst = {}
12504
12505     self.warn = []
12506
12507     #TODO(dynmem): do the appropriate check involving MINMEM
12508     if (constants.BE_MAXMEM in self.op.beparams and not self.op.force and
12509         be_new[constants.BE_MAXMEM] > be_old[constants.BE_MAXMEM]):
12510       mem_check_list = [pnode]
12511       if be_new[constants.BE_AUTO_BALANCE]:
12512         # either we changed auto_balance to yes or it was from before
12513         mem_check_list.extend(instance.secondary_nodes)
12514       instance_info = self.rpc.call_instance_info(pnode, instance.name,
12515                                                   instance.hypervisor)
12516       nodeinfo = self.rpc.call_node_info(mem_check_list, None,
12517                                          [instance.hypervisor])
12518       pninfo = nodeinfo[pnode]
12519       msg = pninfo.fail_msg
12520       if msg:
12521         # Assume the primary node is unreachable and go ahead
12522         self.warn.append("Can't get info from primary node %s: %s" %
12523                          (pnode, msg))
12524       else:
12525         (_, _, (pnhvinfo, )) = pninfo.payload
12526         if not isinstance(pnhvinfo.get("memory_free", None), int):
12527           self.warn.append("Node data from primary node %s doesn't contain"
12528                            " free memory information" % pnode)
12529         elif instance_info.fail_msg:
12530           self.warn.append("Can't get instance runtime information: %s" %
12531                           instance_info.fail_msg)
12532         else:
12533           if instance_info.payload:
12534             current_mem = int(instance_info.payload["memory"])
12535           else:
12536             # Assume instance not running
12537             # (there is a slight race condition here, but it's not very
12538             # probable, and we have no other way to check)
12539             # TODO: Describe race condition
12540             current_mem = 0
12541           #TODO(dynmem): do the appropriate check involving MINMEM
12542           miss_mem = (be_new[constants.BE_MAXMEM] - current_mem -
12543                       pnhvinfo["memory_free"])
12544           if miss_mem > 0:
12545             raise errors.OpPrereqError("This change will prevent the instance"
12546                                        " from starting, due to %d MB of memory"
12547                                        " missing on its primary node" %
12548                                        miss_mem,
12549                                        errors.ECODE_NORES)
12550
12551       if be_new[constants.BE_AUTO_BALANCE]:
12552         for node, nres in nodeinfo.items():
12553           if node not in instance.secondary_nodes:
12554             continue
12555           nres.Raise("Can't get info from secondary node %s" % node,
12556                      prereq=True, ecode=errors.ECODE_STATE)
12557           (_, _, (nhvinfo, )) = nres.payload
12558           if not isinstance(nhvinfo.get("memory_free", None), int):
12559             raise errors.OpPrereqError("Secondary node %s didn't return free"
12560                                        " memory information" % node,
12561                                        errors.ECODE_STATE)
12562           #TODO(dynmem): do the appropriate check involving MINMEM
12563           elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
12564             raise errors.OpPrereqError("This change will prevent the instance"
12565                                        " from failover to its secondary node"
12566                                        " %s, due to not enough memory" % node,
12567                                        errors.ECODE_STATE)
12568
12569     if self.op.runtime_mem:
12570       remote_info = self.rpc.call_instance_info(instance.primary_node,
12571                                                 instance.name,
12572                                                 instance.hypervisor)
12573       remote_info.Raise("Error checking node %s" % instance.primary_node)
12574       if not remote_info.payload: # not running already
12575         raise errors.OpPrereqError("Instance %s is not running" % instance.name,
12576                                    errors.ECODE_STATE)
12577
12578       current_memory = remote_info.payload["memory"]
12579       if (not self.op.force and
12580            (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or
12581             self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])):
12582         raise errors.OpPrereqError("Instance %s must have memory between %d"
12583                                    " and %d MB of memory unless --force is"
12584                                    " given" % (instance.name,
12585                                     self.be_proposed[constants.BE_MINMEM],
12586                                     self.be_proposed[constants.BE_MAXMEM]),
12587                                    errors.ECODE_INVAL)
12588
12589       if self.op.runtime_mem > current_memory:
12590         _CheckNodeFreeMemory(self, instance.primary_node,
12591                              "ballooning memory for instance %s" %
12592                              instance.name,
12593                              self.op.memory - current_memory,
12594                              instance.hypervisor)
12595
12596     if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
12597       raise errors.OpPrereqError("Disk operations not supported for"
12598                                  " diskless instances",
12599                                  errors.ECODE_INVAL)
12600
12601     def _PrepareNicCreate(_, params, private):
12602       self._PrepareNicModification(params, private, None, {}, cluster, pnode)
12603       return (None, None)
12604
12605     def _PrepareNicMod(_, nic, params, private):
12606       self._PrepareNicModification(params, private, nic.ip,
12607                                    nic.nicparams, cluster, pnode)
12608       return None
12609
12610     # Verify NIC changes (operating on copy)
12611     nics = instance.nics[:]
12612     ApplyContainerMods("NIC", nics, None, self.nicmod,
12613                        _PrepareNicCreate, _PrepareNicMod, None)
12614     if len(nics) > constants.MAX_NICS:
12615       raise errors.OpPrereqError("Instance has too many network interfaces"
12616                                  " (%d), cannot add more" % constants.MAX_NICS,
12617                                  errors.ECODE_STATE)
12618
12619     # Verify disk changes (operating on a copy)
12620     disks = instance.disks[:]
12621     ApplyContainerMods("disk", disks, None, self.diskmod, None, None, None)
12622     if len(disks) > constants.MAX_DISKS:
12623       raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
12624                                  " more" % constants.MAX_DISKS,
12625                                  errors.ECODE_STATE)
12626
12627     if self.op.offline is not None:
12628       if self.op.offline:
12629         msg = "can't change to offline"
12630       else:
12631         msg = "can't change to online"
12632       _CheckInstanceState(self, instance, CAN_CHANGE_INSTANCE_OFFLINE, msg=msg)
12633
12634     # Pre-compute NIC changes (necessary to use result in hooks)
12635     self._nic_chgdesc = []
12636     if self.nicmod:
12637       # Operate on copies as this is still in prereq
12638       nics = [nic.Copy() for nic in instance.nics]
12639       ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
12640                          self._CreateNewNic, self._ApplyNicMods, None)
12641       self._new_nics = nics
12642     else:
12643       self._new_nics = None
12644
12645   def _ConvertPlainToDrbd(self, feedback_fn):
12646     """Converts an instance from plain to drbd.
12647
12648     """
12649     feedback_fn("Converting template to drbd")
12650     instance = self.instance
12651     pnode = instance.primary_node
12652     snode = self.op.remote_node
12653
12654     assert instance.disk_template == constants.DT_PLAIN
12655
12656     # create a fake disk info for _GenerateDiskTemplate
12657     disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
12658                   constants.IDISK_VG: d.logical_id[0]}
12659                  for d in instance.disks]
12660     new_disks = _GenerateDiskTemplate(self, self.op.disk_template,
12661                                       instance.name, pnode, [snode],
12662                                       disk_info, None, None, 0, feedback_fn,
12663                                       self.diskparams)
12664     anno_disks = rpc.AnnotateDiskParams(constants.DT_DRBD8, new_disks,
12665                                         self.diskparams)
12666     info = _GetInstanceInfoText(instance)
12667     feedback_fn("Creating additional volumes...")
12668     # first, create the missing data and meta devices
12669     for disk in anno_disks:
12670       # unfortunately this is... not too nice
12671       _CreateSingleBlockDev(self, pnode, instance, disk.children[1],
12672                             info, True)
12673       for child in disk.children:
12674         _CreateSingleBlockDev(self, snode, instance, child, info, True)
12675     # at this stage, all new LVs have been created, we can rename the
12676     # old ones
12677     feedback_fn("Renaming original volumes...")
12678     rename_list = [(o, n.children[0].logical_id)
12679                    for (o, n) in zip(instance.disks, new_disks)]
12680     result = self.rpc.call_blockdev_rename(pnode, rename_list)
12681     result.Raise("Failed to rename original LVs")
12682
12683     feedback_fn("Initializing DRBD devices...")
12684     # all child devices are in place, we can now create the DRBD devices
12685     for disk in anno_disks:
12686       for node in [pnode, snode]:
12687         f_create = node == pnode
12688         _CreateSingleBlockDev(self, node, instance, disk, info, f_create)
12689
12690     # at this point, the instance has been modified
12691     instance.disk_template = constants.DT_DRBD8
12692     instance.disks = new_disks
12693     self.cfg.Update(instance, feedback_fn)
12694
12695     # Release node locks while waiting for sync
12696     _ReleaseLocks(self, locking.LEVEL_NODE)
12697
12698     # disks are created, waiting for sync
12699     disk_abort = not _WaitForSync(self, instance,
12700                                   oneshot=not self.op.wait_for_sync)
12701     if disk_abort:
12702       raise errors.OpExecError("There are some degraded disks for"
12703                                " this instance, please cleanup manually")
12704
12705     # Node resource locks will be released by caller
12706
12707   def _ConvertDrbdToPlain(self, feedback_fn):
12708     """Converts an instance from drbd to plain.
12709
12710     """
12711     instance = self.instance
12712
12713     assert len(instance.secondary_nodes) == 1
12714     assert instance.disk_template == constants.DT_DRBD8
12715
12716     pnode = instance.primary_node
12717     snode = instance.secondary_nodes[0]
12718     feedback_fn("Converting template to plain")
12719
12720     old_disks = _AnnotateDiskParams(instance, instance.disks, self.cfg)
12721     new_disks = [d.children[0] for d in instance.disks]
12722
12723     # copy over size and mode
12724     for parent, child in zip(old_disks, new_disks):
12725       child.size = parent.size
12726       child.mode = parent.mode
12727
12728     # this is a DRBD disk, return its port to the pool
12729     # NOTE: this must be done right before the call to cfg.Update!
12730     for disk in old_disks:
12731       tcp_port = disk.logical_id[2]
12732       self.cfg.AddTcpUdpPort(tcp_port)
12733
12734     # update instance structure
12735     instance.disks = new_disks
12736     instance.disk_template = constants.DT_PLAIN
12737     self.cfg.Update(instance, feedback_fn)
12738
12739     # Release locks in case removing disks takes a while
12740     _ReleaseLocks(self, locking.LEVEL_NODE)
12741
12742     feedback_fn("Removing volumes on the secondary node...")
12743     for disk in old_disks:
12744       self.cfg.SetDiskID(disk, snode)
12745       msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
12746       if msg:
12747         self.LogWarning("Could not remove block device %s on node %s,"
12748                         " continuing anyway: %s", disk.iv_name, snode, msg)
12749
12750     feedback_fn("Removing unneeded volumes on the primary node...")
12751     for idx, disk in enumerate(old_disks):
12752       meta = disk.children[1]
12753       self.cfg.SetDiskID(meta, pnode)
12754       msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
12755       if msg:
12756         self.LogWarning("Could not remove metadata for disk %d on node %s,"
12757                         " continuing anyway: %s", idx, pnode, msg)
12758
12759   def _CreateNewDisk(self, idx, params, _):
12760     """Creates a new disk.
12761
12762     """
12763     instance = self.instance
12764
12765     # add a new disk
12766     if instance.disk_template in constants.DTS_FILEBASED:
12767       (file_driver, file_path) = instance.disks[0].logical_id
12768       file_path = os.path.dirname(file_path)
12769     else:
12770       file_driver = file_path = None
12771
12772     disk = \
12773       _GenerateDiskTemplate(self, instance.disk_template, instance.name,
12774                             instance.primary_node, instance.secondary_nodes,
12775                             [params], file_path, file_driver, idx,
12776                             self.Log, self.diskparams)[0]
12777
12778     info = _GetInstanceInfoText(instance)
12779
12780     logging.info("Creating volume %s for instance %s",
12781                  disk.iv_name, instance.name)
12782     # Note: this needs to be kept in sync with _CreateDisks
12783     #HARDCODE
12784     for node in instance.all_nodes:
12785       f_create = (node == instance.primary_node)
12786       try:
12787         _CreateBlockDev(self, node, instance, disk, f_create, info, f_create)
12788       except errors.OpExecError, err:
12789         self.LogWarning("Failed to create volume %s (%s) on node '%s': %s",
12790                         disk.iv_name, disk, node, err)
12791
12792     return (disk, [
12793       ("disk/%d" % idx, "add:size=%s,mode=%s" % (disk.size, disk.mode)),
12794       ])
12795
12796   @staticmethod
12797   def _ModifyDisk(idx, disk, params, _):
12798     """Modifies a disk.
12799
12800     """
12801     disk.mode = params[constants.IDISK_MODE]
12802
12803     return [
12804       ("disk.mode/%d" % idx, disk.mode),
12805       ]
12806
12807   def _RemoveDisk(self, idx, root, _):
12808     """Removes a disk.
12809
12810     """
12811     (anno_disk,) = _AnnotateDiskParams(self.instance, [root], self.cfg)
12812     for node, disk in anno_disk.ComputeNodeTree(self.instance.primary_node):
12813       self.cfg.SetDiskID(disk, node)
12814       msg = self.rpc.call_blockdev_remove(node, disk).fail_msg
12815       if msg:
12816         self.LogWarning("Could not remove disk/%d on node '%s': %s,"
12817                         " continuing anyway", idx, node, msg)
12818
12819     # if this is a DRBD disk, return its port to the pool
12820     if root.dev_type in constants.LDS_DRBD:
12821       self.cfg.AddTcpUdpPort(root.logical_id[2])
12822
12823   @staticmethod
12824   def _CreateNewNic(idx, params, private):
12825     """Creates data structure for a new network interface.
12826
12827     """
12828     mac = params[constants.INIC_MAC]
12829     ip = params.get(constants.INIC_IP, None)
12830     nicparams = private.params
12831
12832     return (objects.NIC(mac=mac, ip=ip, nicparams=nicparams), [
12833       ("nic.%d" % idx,
12834        "add:mac=%s,ip=%s,mode=%s,link=%s" %
12835        (mac, ip, private.filled[constants.NIC_MODE],
12836        private.filled[constants.NIC_LINK])),
12837       ])
12838
12839   @staticmethod
12840   def _ApplyNicMods(idx, nic, params, private):
12841     """Modifies a network interface.
12842
12843     """
12844     changes = []
12845
12846     for key in [constants.INIC_MAC, constants.INIC_IP]:
12847       if key in params:
12848         changes.append(("nic.%s/%d" % (key, idx), params[key]))
12849         setattr(nic, key, params[key])
12850
12851     if private.params:
12852       nic.nicparams = private.params
12853
12854       for (key, val) in params.items():
12855         changes.append(("nic.%s/%d" % (key, idx), val))
12856
12857     return changes
12858
12859   def Exec(self, feedback_fn):
12860     """Modifies an instance.
12861
12862     All parameters take effect only at the next restart of the instance.
12863
12864     """
12865     # Process here the warnings from CheckPrereq, as we don't have a
12866     # feedback_fn there.
12867     # TODO: Replace with self.LogWarning
12868     for warn in self.warn:
12869       feedback_fn("WARNING: %s" % warn)
12870
12871     assert ((self.op.disk_template is None) ^
12872             bool(self.owned_locks(locking.LEVEL_NODE_RES))), \
12873       "Not owning any node resource locks"
12874
12875     result = []
12876     instance = self.instance
12877
12878     # runtime memory
12879     if self.op.runtime_mem:
12880       rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
12881                                                      instance,
12882                                                      self.op.runtime_mem)
12883       rpcres.Raise("Cannot modify instance runtime memory")
12884       result.append(("runtime_memory", self.op.runtime_mem))
12885
12886     # Apply disk changes
12887     ApplyContainerMods("disk", instance.disks, result, self.diskmod,
12888                        self._CreateNewDisk, self._ModifyDisk, self._RemoveDisk)
12889     _UpdateIvNames(0, instance.disks)
12890
12891     if self.op.disk_template:
12892       if __debug__:
12893         check_nodes = set(instance.all_nodes)
12894         if self.op.remote_node:
12895           check_nodes.add(self.op.remote_node)
12896         for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
12897           owned = self.owned_locks(level)
12898           assert not (check_nodes - owned), \
12899             ("Not owning the correct locks, owning %r, expected at least %r" %
12900              (owned, check_nodes))
12901
12902       r_shut = _ShutdownInstanceDisks(self, instance)
12903       if not r_shut:
12904         raise errors.OpExecError("Cannot shutdown instance disks, unable to"
12905                                  " proceed with disk template conversion")
12906       mode = (instance.disk_template, self.op.disk_template)
12907       try:
12908         self._DISK_CONVERSIONS[mode](self, feedback_fn)
12909       except:
12910         self.cfg.ReleaseDRBDMinors(instance.name)
12911         raise
12912       result.append(("disk_template", self.op.disk_template))
12913
12914       assert instance.disk_template == self.op.disk_template, \
12915         ("Expected disk template '%s', found '%s'" %
12916          (self.op.disk_template, instance.disk_template))
12917
12918     # Release node and resource locks if there are any (they might already have
12919     # been released during disk conversion)
12920     _ReleaseLocks(self, locking.LEVEL_NODE)
12921     _ReleaseLocks(self, locking.LEVEL_NODE_RES)
12922
12923     # Apply NIC changes
12924     if self._new_nics is not None:
12925       instance.nics = self._new_nics
12926       result.extend(self._nic_chgdesc)
12927
12928     # hvparams changes
12929     if self.op.hvparams:
12930       instance.hvparams = self.hv_inst
12931       for key, val in self.op.hvparams.iteritems():
12932         result.append(("hv/%s" % key, val))
12933
12934     # beparams changes
12935     if self.op.beparams:
12936       instance.beparams = self.be_inst
12937       for key, val in self.op.beparams.iteritems():
12938         result.append(("be/%s" % key, val))
12939
12940     # OS change
12941     if self.op.os_name:
12942       instance.os = self.op.os_name
12943
12944     # osparams changes
12945     if self.op.osparams:
12946       instance.osparams = self.os_inst
12947       for key, val in self.op.osparams.iteritems():
12948         result.append(("os/%s" % key, val))
12949
12950     if self.op.offline is None:
12951       # Ignore
12952       pass
12953     elif self.op.offline:
12954       # Mark instance as offline
12955       self.cfg.MarkInstanceOffline(instance.name)
12956       result.append(("admin_state", constants.ADMINST_OFFLINE))
12957     else:
12958       # Mark instance as online, but stopped
12959       self.cfg.MarkInstanceDown(instance.name)
12960       result.append(("admin_state", constants.ADMINST_DOWN))
12961
12962     self.cfg.Update(instance, feedback_fn)
12963
12964     assert not (self.owned_locks(locking.LEVEL_NODE_RES) or
12965                 self.owned_locks(locking.LEVEL_NODE)), \
12966       "All node locks should have been released by now"
12967
12968     return result
12969
12970   _DISK_CONVERSIONS = {
12971     (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
12972     (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
12973     }
12974
12975
12976 class LUInstanceChangeGroup(LogicalUnit):
12977   HPATH = "instance-change-group"
12978   HTYPE = constants.HTYPE_INSTANCE
12979   REQ_BGL = False
12980
12981   def ExpandNames(self):
12982     self.share_locks = _ShareAll()
12983     self.needed_locks = {
12984       locking.LEVEL_NODEGROUP: [],
12985       locking.LEVEL_NODE: [],
12986       }
12987
12988     self._ExpandAndLockInstance()
12989
12990     if self.op.target_groups:
12991       self.req_target_uuids = map(self.cfg.LookupNodeGroup,
12992                                   self.op.target_groups)
12993     else:
12994       self.req_target_uuids = None
12995
12996     self.op.iallocator = _GetDefaultIAllocator(self.cfg, self.op.iallocator)
12997
12998   def DeclareLocks(self, level):
12999     if level == locking.LEVEL_NODEGROUP:
13000       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
13001
13002       if self.req_target_uuids:
13003         lock_groups = set(self.req_target_uuids)
13004
13005         # Lock all groups used by instance optimistically; this requires going
13006         # via the node before it's locked, requiring verification later on
13007         instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_name)
13008         lock_groups.update(instance_groups)
13009       else:
13010         # No target groups, need to lock all of them
13011         lock_groups = locking.ALL_SET
13012
13013       self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
13014
13015     elif level == locking.LEVEL_NODE:
13016       if self.req_target_uuids:
13017         # Lock all nodes used by instances
13018         self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
13019         self._LockInstancesNodes()
13020
13021         # Lock all nodes in all potential target groups
13022         lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) -
13023                        self.cfg.GetInstanceNodeGroups(self.op.instance_name))
13024         member_nodes = [node_name
13025                         for group in lock_groups
13026                         for node_name in self.cfg.GetNodeGroup(group).members]
13027         self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
13028       else:
13029         # Lock all nodes as all groups are potential targets
13030         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13031
13032   def CheckPrereq(self):
13033     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
13034     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
13035     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
13036
13037     assert (self.req_target_uuids is None or
13038             owned_groups.issuperset(self.req_target_uuids))
13039     assert owned_instances == set([self.op.instance_name])
13040
13041     # Get instance information
13042     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
13043
13044     # Check if node groups for locked instance are still correct
13045     assert owned_nodes.issuperset(self.instance.all_nodes), \
13046       ("Instance %s's nodes changed while we kept the lock" %
13047        self.op.instance_name)
13048
13049     inst_groups = _CheckInstanceNodeGroups(self.cfg, self.op.instance_name,
13050                                            owned_groups)
13051
13052     if self.req_target_uuids:
13053       # User requested specific target groups
13054       self.target_uuids = frozenset(self.req_target_uuids)
13055     else:
13056       # All groups except those used by the instance are potential targets
13057       self.target_uuids = owned_groups - inst_groups
13058
13059     conflicting_groups = self.target_uuids & inst_groups
13060     if conflicting_groups:
13061       raise errors.OpPrereqError("Can't use group(s) '%s' as targets, they are"
13062                                  " used by the instance '%s'" %
13063                                  (utils.CommaJoin(conflicting_groups),
13064                                   self.op.instance_name),
13065                                  errors.ECODE_INVAL)
13066
13067     if not self.target_uuids:
13068       raise errors.OpPrereqError("There are no possible target groups",
13069                                  errors.ECODE_INVAL)
13070
13071   def BuildHooksEnv(self):
13072     """Build hooks env.
13073
13074     """
13075     assert self.target_uuids
13076
13077     env = {
13078       "TARGET_GROUPS": " ".join(self.target_uuids),
13079       }
13080
13081     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
13082
13083     return env
13084
13085   def BuildHooksNodes(self):
13086     """Build hooks nodes.
13087
13088     """
13089     mn = self.cfg.GetMasterNode()
13090     return ([mn], [mn])
13091
13092   def Exec(self, feedback_fn):
13093     instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
13094
13095     assert instances == [self.op.instance_name], "Instance not locked"
13096
13097     ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_CHG_GROUP,
13098                      instances=instances, target_groups=list(self.target_uuids))
13099
13100     ial.Run(self.op.iallocator)
13101
13102     if not ial.success:
13103       raise errors.OpPrereqError("Can't compute solution for changing group of"
13104                                  " instance '%s' using iallocator '%s': %s" %
13105                                  (self.op.instance_name, self.op.iallocator,
13106                                   ial.info),
13107                                  errors.ECODE_NORES)
13108
13109     jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
13110
13111     self.LogInfo("Iallocator returned %s job(s) for changing group of"
13112                  " instance '%s'", len(jobs), self.op.instance_name)
13113
13114     return ResultWithJobs(jobs)
13115
13116
13117 class LUBackupQuery(NoHooksLU):
13118   """Query the exports list
13119
13120   """
13121   REQ_BGL = False
13122
13123   def CheckArguments(self):
13124     self.expq = _ExportQuery(qlang.MakeSimpleFilter("node", self.op.nodes),
13125                              ["node", "export"], self.op.use_locking)
13126
13127   def ExpandNames(self):
13128     self.expq.ExpandNames(self)
13129
13130   def DeclareLocks(self, level):
13131     self.expq.DeclareLocks(self, level)
13132
13133   def Exec(self, feedback_fn):
13134     result = {}
13135
13136     for (node, expname) in self.expq.OldStyleQuery(self):
13137       if expname is None:
13138         result[node] = False
13139       else:
13140         result.setdefault(node, []).append(expname)
13141
13142     return result
13143
13144
13145 class _ExportQuery(_QueryBase):
13146   FIELDS = query.EXPORT_FIELDS
13147
13148   #: The node name is not a unique key for this query
13149   SORT_FIELD = "node"
13150
13151   def ExpandNames(self, lu):
13152     lu.needed_locks = {}
13153
13154     # The following variables interact with _QueryBase._GetNames
13155     if self.names:
13156       self.wanted = _GetWantedNodes(lu, self.names)
13157     else:
13158       self.wanted = locking.ALL_SET
13159
13160     self.do_locking = self.use_locking
13161
13162     if self.do_locking:
13163       lu.share_locks = _ShareAll()
13164       lu.needed_locks = {
13165         locking.LEVEL_NODE: self.wanted,
13166         }
13167
13168   def DeclareLocks(self, lu, level):
13169     pass
13170
13171   def _GetQueryData(self, lu):
13172     """Computes the list of nodes and their attributes.
13173
13174     """
13175     # Locking is not used
13176     # TODO
13177     assert not (compat.any(lu.glm.is_owned(level)
13178                            for level in locking.LEVELS
13179                            if level != locking.LEVEL_CLUSTER) or
13180                 self.do_locking or self.use_locking)
13181
13182     nodes = self._GetNames(lu, lu.cfg.GetNodeList(), locking.LEVEL_NODE)
13183
13184     result = []
13185
13186     for (node, nres) in lu.rpc.call_export_list(nodes).items():
13187       if nres.fail_msg:
13188         result.append((node, None))
13189       else:
13190         result.extend((node, expname) for expname in nres.payload)
13191
13192     return result
13193
13194
13195 class LUBackupPrepare(NoHooksLU):
13196   """Prepares an instance for an export and returns useful information.
13197
13198   """
13199   REQ_BGL = False
13200
13201   def ExpandNames(self):
13202     self._ExpandAndLockInstance()
13203
13204   def CheckPrereq(self):
13205     """Check prerequisites.
13206
13207     """
13208     instance_name = self.op.instance_name
13209
13210     self.instance = self.cfg.GetInstanceInfo(instance_name)
13211     assert self.instance is not None, \
13212           "Cannot retrieve locked instance %s" % self.op.instance_name
13213     _CheckNodeOnline(self, self.instance.primary_node)
13214
13215     self._cds = _GetClusterDomainSecret()
13216
13217   def Exec(self, feedback_fn):
13218     """Prepares an instance for an export.
13219
13220     """
13221     instance = self.instance
13222
13223     if self.op.mode == constants.EXPORT_MODE_REMOTE:
13224       salt = utils.GenerateSecret(8)
13225
13226       feedback_fn("Generating X509 certificate on %s" % instance.primary_node)
13227       result = self.rpc.call_x509_cert_create(instance.primary_node,
13228                                               constants.RIE_CERT_VALIDITY)
13229       result.Raise("Can't create X509 key and certificate on %s" % result.node)
13230
13231       (name, cert_pem) = result.payload
13232
13233       cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
13234                                              cert_pem)
13235
13236       return {
13237         "handshake": masterd.instance.ComputeRemoteExportHandshake(self._cds),
13238         "x509_key_name": (name, utils.Sha1Hmac(self._cds, name, salt=salt),
13239                           salt),
13240         "x509_ca": utils.SignX509Certificate(cert, self._cds, salt),
13241         }
13242
13243     return None
13244
13245
13246 class LUBackupExport(LogicalUnit):
13247   """Export an instance to an image in the cluster.
13248
13249   """
13250   HPATH = "instance-export"
13251   HTYPE = constants.HTYPE_INSTANCE
13252   REQ_BGL = False
13253
13254   def CheckArguments(self):
13255     """Check the arguments.
13256
13257     """
13258     self.x509_key_name = self.op.x509_key_name
13259     self.dest_x509_ca_pem = self.op.destination_x509_ca
13260
13261     if self.op.mode == constants.EXPORT_MODE_REMOTE:
13262       if not self.x509_key_name:
13263         raise errors.OpPrereqError("Missing X509 key name for encryption",
13264                                    errors.ECODE_INVAL)
13265
13266       if not self.dest_x509_ca_pem:
13267         raise errors.OpPrereqError("Missing destination X509 CA",
13268                                    errors.ECODE_INVAL)
13269
13270   def ExpandNames(self):
13271     self._ExpandAndLockInstance()
13272
13273     # Lock all nodes for local exports
13274     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13275       # FIXME: lock only instance primary and destination node
13276       #
13277       # Sad but true, for now we have do lock all nodes, as we don't know where
13278       # the previous export might be, and in this LU we search for it and
13279       # remove it from its current node. In the future we could fix this by:
13280       #  - making a tasklet to search (share-lock all), then create the
13281       #    new one, then one to remove, after
13282       #  - removing the removal operation altogether
13283       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13284
13285   def DeclareLocks(self, level):
13286     """Last minute lock declaration."""
13287     # All nodes are locked anyway, so nothing to do here.
13288
13289   def BuildHooksEnv(self):
13290     """Build hooks env.
13291
13292     This will run on the master, primary node and target node.
13293
13294     """
13295     env = {
13296       "EXPORT_MODE": self.op.mode,
13297       "EXPORT_NODE": self.op.target_node,
13298       "EXPORT_DO_SHUTDOWN": self.op.shutdown,
13299       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
13300       # TODO: Generic function for boolean env variables
13301       "REMOVE_INSTANCE": str(bool(self.op.remove_instance)),
13302       }
13303
13304     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
13305
13306     return env
13307
13308   def BuildHooksNodes(self):
13309     """Build hooks nodes.
13310
13311     """
13312     nl = [self.cfg.GetMasterNode(), self.instance.primary_node]
13313
13314     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13315       nl.append(self.op.target_node)
13316
13317     return (nl, nl)
13318
13319   def CheckPrereq(self):
13320     """Check prerequisites.
13321
13322     This checks that the instance and node names are valid.
13323
13324     """
13325     instance_name = self.op.instance_name
13326
13327     self.instance = self.cfg.GetInstanceInfo(instance_name)
13328     assert self.instance is not None, \
13329           "Cannot retrieve locked instance %s" % self.op.instance_name
13330     _CheckNodeOnline(self, self.instance.primary_node)
13331
13332     if (self.op.remove_instance and
13333         self.instance.admin_state == constants.ADMINST_UP and
13334         not self.op.shutdown):
13335       raise errors.OpPrereqError("Can not remove instance without shutting it"
13336                                  " down before")
13337
13338     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13339       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
13340       self.dst_node = self.cfg.GetNodeInfo(self.op.target_node)
13341       assert self.dst_node is not None
13342
13343       _CheckNodeOnline(self, self.dst_node.name)
13344       _CheckNodeNotDrained(self, self.dst_node.name)
13345
13346       self._cds = None
13347       self.dest_disk_info = None
13348       self.dest_x509_ca = None
13349
13350     elif self.op.mode == constants.EXPORT_MODE_REMOTE:
13351       self.dst_node = None
13352
13353       if len(self.op.target_node) != len(self.instance.disks):
13354         raise errors.OpPrereqError(("Received destination information for %s"
13355                                     " disks, but instance %s has %s disks") %
13356                                    (len(self.op.target_node), instance_name,
13357                                     len(self.instance.disks)),
13358                                    errors.ECODE_INVAL)
13359
13360       cds = _GetClusterDomainSecret()
13361
13362       # Check X509 key name
13363       try:
13364         (key_name, hmac_digest, hmac_salt) = self.x509_key_name
13365       except (TypeError, ValueError), err:
13366         raise errors.OpPrereqError("Invalid data for X509 key name: %s" % err)
13367
13368       if not utils.VerifySha1Hmac(cds, key_name, hmac_digest, salt=hmac_salt):
13369         raise errors.OpPrereqError("HMAC for X509 key name is wrong",
13370                                    errors.ECODE_INVAL)
13371
13372       # Load and verify CA
13373       try:
13374         (cert, _) = utils.LoadSignedX509Certificate(self.dest_x509_ca_pem, cds)
13375       except OpenSSL.crypto.Error, err:
13376         raise errors.OpPrereqError("Unable to load destination X509 CA (%s)" %
13377                                    (err, ), errors.ECODE_INVAL)
13378
13379       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
13380       if errcode is not None:
13381         raise errors.OpPrereqError("Invalid destination X509 CA (%s)" %
13382                                    (msg, ), errors.ECODE_INVAL)
13383
13384       self.dest_x509_ca = cert
13385
13386       # Verify target information
13387       disk_info = []
13388       for idx, disk_data in enumerate(self.op.target_node):
13389         try:
13390           (host, port, magic) = \
13391             masterd.instance.CheckRemoteExportDiskInfo(cds, idx, disk_data)
13392         except errors.GenericError, err:
13393           raise errors.OpPrereqError("Target info for disk %s: %s" %
13394                                      (idx, err), errors.ECODE_INVAL)
13395
13396         disk_info.append((host, port, magic))
13397
13398       assert len(disk_info) == len(self.op.target_node)
13399       self.dest_disk_info = disk_info
13400
13401     else:
13402       raise errors.ProgrammerError("Unhandled export mode %r" %
13403                                    self.op.mode)
13404
13405     # instance disk type verification
13406     # TODO: Implement export support for file-based disks
13407     for disk in self.instance.disks:
13408       if disk.dev_type == constants.LD_FILE:
13409         raise errors.OpPrereqError("Export not supported for instances with"
13410                                    " file-based disks", errors.ECODE_INVAL)
13411
13412   def _CleanupExports(self, feedback_fn):
13413     """Removes exports of current instance from all other nodes.
13414
13415     If an instance in a cluster with nodes A..D was exported to node C, its
13416     exports will be removed from the nodes A, B and D.
13417
13418     """
13419     assert self.op.mode != constants.EXPORT_MODE_REMOTE
13420
13421     nodelist = self.cfg.GetNodeList()
13422     nodelist.remove(self.dst_node.name)
13423
13424     # on one-node clusters nodelist will be empty after the removal
13425     # if we proceed the backup would be removed because OpBackupQuery
13426     # substitutes an empty list with the full cluster node list.
13427     iname = self.instance.name
13428     if nodelist:
13429       feedback_fn("Removing old exports for instance %s" % iname)
13430       exportlist = self.rpc.call_export_list(nodelist)
13431       for node in exportlist:
13432         if exportlist[node].fail_msg:
13433           continue
13434         if iname in exportlist[node].payload:
13435           msg = self.rpc.call_export_remove(node, iname).fail_msg
13436           if msg:
13437             self.LogWarning("Could not remove older export for instance %s"
13438                             " on node %s: %s", iname, node, msg)
13439
13440   def Exec(self, feedback_fn):
13441     """Export an instance to an image in the cluster.
13442
13443     """
13444     assert self.op.mode in constants.EXPORT_MODES
13445
13446     instance = self.instance
13447     src_node = instance.primary_node
13448
13449     if self.op.shutdown:
13450       # shutdown the instance, but not the disks
13451       feedback_fn("Shutting down instance %s" % instance.name)
13452       result = self.rpc.call_instance_shutdown(src_node, instance,
13453                                                self.op.shutdown_timeout)
13454       # TODO: Maybe ignore failures if ignore_remove_failures is set
13455       result.Raise("Could not shutdown instance %s on"
13456                    " node %s" % (instance.name, src_node))
13457
13458     # set the disks ID correctly since call_instance_start needs the
13459     # correct drbd minor to create the symlinks
13460     for disk in instance.disks:
13461       self.cfg.SetDiskID(disk, src_node)
13462
13463     activate_disks = (instance.admin_state != constants.ADMINST_UP)
13464
13465     if activate_disks:
13466       # Activate the instance disks if we'exporting a stopped instance
13467       feedback_fn("Activating disks for %s" % instance.name)
13468       _StartInstanceDisks(self, instance, None)
13469
13470     try:
13471       helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
13472                                                      instance)
13473
13474       helper.CreateSnapshots()
13475       try:
13476         if (self.op.shutdown and
13477             instance.admin_state == constants.ADMINST_UP and
13478             not self.op.remove_instance):
13479           assert not activate_disks
13480           feedback_fn("Starting instance %s" % instance.name)
13481           result = self.rpc.call_instance_start(src_node,
13482                                                 (instance, None, None), False)
13483           msg = result.fail_msg
13484           if msg:
13485             feedback_fn("Failed to start instance: %s" % msg)
13486             _ShutdownInstanceDisks(self, instance)
13487             raise errors.OpExecError("Could not start instance: %s" % msg)
13488
13489         if self.op.mode == constants.EXPORT_MODE_LOCAL:
13490           (fin_resu, dresults) = helper.LocalExport(self.dst_node)
13491         elif self.op.mode == constants.EXPORT_MODE_REMOTE:
13492           connect_timeout = constants.RIE_CONNECT_TIMEOUT
13493           timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
13494
13495           (key_name, _, _) = self.x509_key_name
13496
13497           dest_ca_pem = \
13498             OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
13499                                             self.dest_x509_ca)
13500
13501           (fin_resu, dresults) = helper.RemoteExport(self.dest_disk_info,
13502                                                      key_name, dest_ca_pem,
13503                                                      timeouts)
13504       finally:
13505         helper.Cleanup()
13506
13507       # Check for backwards compatibility
13508       assert len(dresults) == len(instance.disks)
13509       assert compat.all(isinstance(i, bool) for i in dresults), \
13510              "Not all results are boolean: %r" % dresults
13511
13512     finally:
13513       if activate_disks:
13514         feedback_fn("Deactivating disks for %s" % instance.name)
13515         _ShutdownInstanceDisks(self, instance)
13516
13517     if not (compat.all(dresults) and fin_resu):
13518       failures = []
13519       if not fin_resu:
13520         failures.append("export finalization")
13521       if not compat.all(dresults):
13522         fdsk = utils.CommaJoin(idx for (idx, dsk) in enumerate(dresults)
13523                                if not dsk)
13524         failures.append("disk export: disk(s) %s" % fdsk)
13525
13526       raise errors.OpExecError("Export failed, errors in %s" %
13527                                utils.CommaJoin(failures))
13528
13529     # At this point, the export was successful, we can cleanup/finish
13530
13531     # Remove instance if requested
13532     if self.op.remove_instance:
13533       feedback_fn("Removing instance %s" % instance.name)
13534       _RemoveInstance(self, feedback_fn, instance,
13535                       self.op.ignore_remove_failures)
13536
13537     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13538       self._CleanupExports(feedback_fn)
13539
13540     return fin_resu, dresults
13541
13542
13543 class LUBackupRemove(NoHooksLU):
13544   """Remove exports related to the named instance.
13545
13546   """
13547   REQ_BGL = False
13548
13549   def ExpandNames(self):
13550     self.needed_locks = {}
13551     # We need all nodes to be locked in order for RemoveExport to work, but we
13552     # don't need to lock the instance itself, as nothing will happen to it (and
13553     # we can remove exports also for a removed instance)
13554     self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13555
13556   def Exec(self, feedback_fn):
13557     """Remove any export.
13558
13559     """
13560     instance_name = self.cfg.ExpandInstanceName(self.op.instance_name)
13561     # If the instance was not found we'll try with the name that was passed in.
13562     # This will only work if it was an FQDN, though.
13563     fqdn_warn = False
13564     if not instance_name:
13565       fqdn_warn = True
13566       instance_name = self.op.instance_name
13567
13568     locked_nodes = self.owned_locks(locking.LEVEL_NODE)
13569     exportlist = self.rpc.call_export_list(locked_nodes)
13570     found = False
13571     for node in exportlist:
13572       msg = exportlist[node].fail_msg
13573       if msg:
13574         self.LogWarning("Failed to query node %s (continuing): %s", node, msg)
13575         continue
13576       if instance_name in exportlist[node].payload:
13577         found = True
13578         result = self.rpc.call_export_remove(node, instance_name)
13579         msg = result.fail_msg
13580         if msg:
13581           logging.error("Could not remove export for instance %s"
13582                         " on node %s: %s", instance_name, node, msg)
13583
13584     if fqdn_warn and not found:
13585       feedback_fn("Export not found. If trying to remove an export belonging"
13586                   " to a deleted instance please use its Fully Qualified"
13587                   " Domain Name.")
13588
13589
13590 class LUGroupAdd(LogicalUnit):
13591   """Logical unit for creating node groups.
13592
13593   """
13594   HPATH = "group-add"
13595   HTYPE = constants.HTYPE_GROUP
13596   REQ_BGL = False
13597
13598   def ExpandNames(self):
13599     # We need the new group's UUID here so that we can create and acquire the
13600     # corresponding lock. Later, in Exec(), we'll indicate to cfg.AddNodeGroup
13601     # that it should not check whether the UUID exists in the configuration.
13602     self.group_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
13603     self.needed_locks = {}
13604     self.add_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
13605
13606   def CheckPrereq(self):
13607     """Check prerequisites.
13608
13609     This checks that the given group name is not an existing node group
13610     already.
13611
13612     """
13613     try:
13614       existing_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13615     except errors.OpPrereqError:
13616       pass
13617     else:
13618       raise errors.OpPrereqError("Desired group name '%s' already exists as a"
13619                                  " node group (UUID: %s)" %
13620                                  (self.op.group_name, existing_uuid),
13621                                  errors.ECODE_EXISTS)
13622
13623     if self.op.ndparams:
13624       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
13625
13626     if self.op.hv_state:
13627       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None)
13628     else:
13629       self.new_hv_state = None
13630
13631     if self.op.disk_state:
13632       self.new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state, None)
13633     else:
13634       self.new_disk_state = None
13635
13636     if self.op.diskparams:
13637       for templ in constants.DISK_TEMPLATES:
13638         if templ in self.op.diskparams:
13639           utils.ForceDictType(self.op.diskparams[templ],
13640                               constants.DISK_DT_TYPES)
13641       self.new_diskparams = self.op.diskparams
13642       try:
13643         utils.VerifyDictOptions(self.new_diskparams, constants.DISK_DT_DEFAULTS)
13644       except errors.OpPrereqError, err:
13645         raise errors.OpPrereqError("While verify diskparams options: %s" % err,
13646                                    errors.ECODE_INVAL)
13647     else:
13648       self.new_diskparams = {}
13649
13650     if self.op.ipolicy:
13651       cluster = self.cfg.GetClusterInfo()
13652       full_ipolicy = cluster.SimpleFillIPolicy(self.op.ipolicy)
13653       try:
13654         objects.InstancePolicy.CheckParameterSyntax(full_ipolicy)
13655       except errors.ConfigurationError, err:
13656         raise errors.OpPrereqError("Invalid instance policy: %s" % err,
13657                                    errors.ECODE_INVAL)
13658
13659   def BuildHooksEnv(self):
13660     """Build hooks env.
13661
13662     """
13663     return {
13664       "GROUP_NAME": self.op.group_name,
13665       }
13666
13667   def BuildHooksNodes(self):
13668     """Build hooks nodes.
13669
13670     """
13671     mn = self.cfg.GetMasterNode()
13672     return ([mn], [mn])
13673
13674   def Exec(self, feedback_fn):
13675     """Add the node group to the cluster.
13676
13677     """
13678     group_obj = objects.NodeGroup(name=self.op.group_name, members=[],
13679                                   uuid=self.group_uuid,
13680                                   alloc_policy=self.op.alloc_policy,
13681                                   ndparams=self.op.ndparams,
13682                                   diskparams=self.new_diskparams,
13683                                   ipolicy=self.op.ipolicy,
13684                                   hv_state_static=self.new_hv_state,
13685                                   disk_state_static=self.new_disk_state)
13686
13687     self.cfg.AddNodeGroup(group_obj, self.proc.GetECId(), check_uuid=False)
13688     del self.remove_locks[locking.LEVEL_NODEGROUP]
13689
13690
13691 class LUGroupAssignNodes(NoHooksLU):
13692   """Logical unit for assigning nodes to groups.
13693
13694   """
13695   REQ_BGL = False
13696
13697   def ExpandNames(self):
13698     # These raise errors.OpPrereqError on their own:
13699     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13700     self.op.nodes = _GetWantedNodes(self, self.op.nodes)
13701
13702     # We want to lock all the affected nodes and groups. We have readily
13703     # available the list of nodes, and the *destination* group. To gather the
13704     # list of "source" groups, we need to fetch node information later on.
13705     self.needed_locks = {
13706       locking.LEVEL_NODEGROUP: set([self.group_uuid]),
13707       locking.LEVEL_NODE: self.op.nodes,
13708       }
13709
13710   def DeclareLocks(self, level):
13711     if level == locking.LEVEL_NODEGROUP:
13712       assert len(self.needed_locks[locking.LEVEL_NODEGROUP]) == 1
13713
13714       # Try to get all affected nodes' groups without having the group or node
13715       # lock yet. Needs verification later in the code flow.
13716       groups = self.cfg.GetNodeGroupsFromNodes(self.op.nodes)
13717
13718       self.needed_locks[locking.LEVEL_NODEGROUP].update(groups)
13719
13720   def CheckPrereq(self):
13721     """Check prerequisites.
13722
13723     """
13724     assert self.needed_locks[locking.LEVEL_NODEGROUP]
13725     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
13726             frozenset(self.op.nodes))
13727
13728     expected_locks = (set([self.group_uuid]) |
13729                       self.cfg.GetNodeGroupsFromNodes(self.op.nodes))
13730     actual_locks = self.owned_locks(locking.LEVEL_NODEGROUP)
13731     if actual_locks != expected_locks:
13732       raise errors.OpExecError("Nodes changed groups since locks were acquired,"
13733                                " current groups are '%s', used to be '%s'" %
13734                                (utils.CommaJoin(expected_locks),
13735                                 utils.CommaJoin(actual_locks)))
13736
13737     self.node_data = self.cfg.GetAllNodesInfo()
13738     self.group = self.cfg.GetNodeGroup(self.group_uuid)
13739     instance_data = self.cfg.GetAllInstancesInfo()
13740
13741     if self.group is None:
13742       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
13743                                (self.op.group_name, self.group_uuid))
13744
13745     (new_splits, previous_splits) = \
13746       self.CheckAssignmentForSplitInstances([(node, self.group_uuid)
13747                                              for node in self.op.nodes],
13748                                             self.node_data, instance_data)
13749
13750     if new_splits:
13751       fmt_new_splits = utils.CommaJoin(utils.NiceSort(new_splits))
13752
13753       if not self.op.force:
13754         raise errors.OpExecError("The following instances get split by this"
13755                                  " change and --force was not given: %s" %
13756                                  fmt_new_splits)
13757       else:
13758         self.LogWarning("This operation will split the following instances: %s",
13759                         fmt_new_splits)
13760
13761         if previous_splits:
13762           self.LogWarning("In addition, these already-split instances continue"
13763                           " to be split across groups: %s",
13764                           utils.CommaJoin(utils.NiceSort(previous_splits)))
13765
13766   def Exec(self, feedback_fn):
13767     """Assign nodes to a new group.
13768
13769     """
13770     mods = [(node_name, self.group_uuid) for node_name in self.op.nodes]
13771
13772     self.cfg.AssignGroupNodes(mods)
13773
13774   @staticmethod
13775   def CheckAssignmentForSplitInstances(changes, node_data, instance_data):
13776     """Check for split instances after a node assignment.
13777
13778     This method considers a series of node assignments as an atomic operation,
13779     and returns information about split instances after applying the set of
13780     changes.
13781
13782     In particular, it returns information about newly split instances, and
13783     instances that were already split, and remain so after the change.
13784
13785     Only instances whose disk template is listed in constants.DTS_INT_MIRROR are
13786     considered.
13787
13788     @type changes: list of (node_name, new_group_uuid) pairs.
13789     @param changes: list of node assignments to consider.
13790     @param node_data: a dict with data for all nodes
13791     @param instance_data: a dict with all instances to consider
13792     @rtype: a two-tuple
13793     @return: a list of instances that were previously okay and result split as a
13794       consequence of this change, and a list of instances that were previously
13795       split and this change does not fix.
13796
13797     """
13798     changed_nodes = dict((node, group) for node, group in changes
13799                          if node_data[node].group != group)
13800
13801     all_split_instances = set()
13802     previously_split_instances = set()
13803
13804     def InstanceNodes(instance):
13805       return [instance.primary_node] + list(instance.secondary_nodes)
13806
13807     for inst in instance_data.values():
13808       if inst.disk_template not in constants.DTS_INT_MIRROR:
13809         continue
13810
13811       instance_nodes = InstanceNodes(inst)
13812
13813       if len(set(node_data[node].group for node in instance_nodes)) > 1:
13814         previously_split_instances.add(inst.name)
13815
13816       if len(set(changed_nodes.get(node, node_data[node].group)
13817                  for node in instance_nodes)) > 1:
13818         all_split_instances.add(inst.name)
13819
13820     return (list(all_split_instances - previously_split_instances),
13821             list(previously_split_instances & all_split_instances))
13822
13823
13824 class _GroupQuery(_QueryBase):
13825   FIELDS = query.GROUP_FIELDS
13826
13827   def ExpandNames(self, lu):
13828     lu.needed_locks = {}
13829
13830     self._all_groups = lu.cfg.GetAllNodeGroupsInfo()
13831     self._cluster = lu.cfg.GetClusterInfo()
13832     name_to_uuid = dict((g.name, g.uuid) for g in self._all_groups.values())
13833
13834     if not self.names:
13835       self.wanted = [name_to_uuid[name]
13836                      for name in utils.NiceSort(name_to_uuid.keys())]
13837     else:
13838       # Accept names to be either names or UUIDs.
13839       missing = []
13840       self.wanted = []
13841       all_uuid = frozenset(self._all_groups.keys())
13842
13843       for name in self.names:
13844         if name in all_uuid:
13845           self.wanted.append(name)
13846         elif name in name_to_uuid:
13847           self.wanted.append(name_to_uuid[name])
13848         else:
13849           missing.append(name)
13850
13851       if missing:
13852         raise errors.OpPrereqError("Some groups do not exist: %s" %
13853                                    utils.CommaJoin(missing),
13854                                    errors.ECODE_NOENT)
13855
13856   def DeclareLocks(self, lu, level):
13857     pass
13858
13859   def _GetQueryData(self, lu):
13860     """Computes the list of node groups and their attributes.
13861
13862     """
13863     do_nodes = query.GQ_NODE in self.requested_data
13864     do_instances = query.GQ_INST in self.requested_data
13865
13866     group_to_nodes = None
13867     group_to_instances = None
13868
13869     # For GQ_NODE, we need to map group->[nodes], and group->[instances] for
13870     # GQ_INST. The former is attainable with just GetAllNodesInfo(), but for the
13871     # latter GetAllInstancesInfo() is not enough, for we have to go through
13872     # instance->node. Hence, we will need to process nodes even if we only need
13873     # instance information.
13874     if do_nodes or do_instances:
13875       all_nodes = lu.cfg.GetAllNodesInfo()
13876       group_to_nodes = dict((uuid, []) for uuid in self.wanted)
13877       node_to_group = {}
13878
13879       for node in all_nodes.values():
13880         if node.group in group_to_nodes:
13881           group_to_nodes[node.group].append(node.name)
13882           node_to_group[node.name] = node.group
13883
13884       if do_instances:
13885         all_instances = lu.cfg.GetAllInstancesInfo()
13886         group_to_instances = dict((uuid, []) for uuid in self.wanted)
13887
13888         for instance in all_instances.values():
13889           node = instance.primary_node
13890           if node in node_to_group:
13891             group_to_instances[node_to_group[node]].append(instance.name)
13892
13893         if not do_nodes:
13894           # Do not pass on node information if it was not requested.
13895           group_to_nodes = None
13896
13897     return query.GroupQueryData(self._cluster,
13898                                 [self._all_groups[uuid]
13899                                  for uuid in self.wanted],
13900                                 group_to_nodes, group_to_instances,
13901                                 query.GQ_DISKPARAMS in self.requested_data)
13902
13903
13904 class LUGroupQuery(NoHooksLU):
13905   """Logical unit for querying node groups.
13906
13907   """
13908   REQ_BGL = False
13909
13910   def CheckArguments(self):
13911     self.gq = _GroupQuery(qlang.MakeSimpleFilter("name", self.op.names),
13912                           self.op.output_fields, False)
13913
13914   def ExpandNames(self):
13915     self.gq.ExpandNames(self)
13916
13917   def DeclareLocks(self, level):
13918     self.gq.DeclareLocks(self, level)
13919
13920   def Exec(self, feedback_fn):
13921     return self.gq.OldStyleQuery(self)
13922
13923
13924 class LUGroupSetParams(LogicalUnit):
13925   """Modifies the parameters of a node group.
13926
13927   """
13928   HPATH = "group-modify"
13929   HTYPE = constants.HTYPE_GROUP
13930   REQ_BGL = False
13931
13932   def CheckArguments(self):
13933     all_changes = [
13934       self.op.ndparams,
13935       self.op.diskparams,
13936       self.op.alloc_policy,
13937       self.op.hv_state,
13938       self.op.disk_state,
13939       self.op.ipolicy,
13940       ]
13941
13942     if all_changes.count(None) == len(all_changes):
13943       raise errors.OpPrereqError("Please pass at least one modification",
13944                                  errors.ECODE_INVAL)
13945
13946   def ExpandNames(self):
13947     # This raises errors.OpPrereqError on its own:
13948     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13949
13950     self.needed_locks = {
13951       locking.LEVEL_INSTANCE: [],
13952       locking.LEVEL_NODEGROUP: [self.group_uuid],
13953       }
13954
13955     self.share_locks[locking.LEVEL_INSTANCE] = 1
13956
13957   def DeclareLocks(self, level):
13958     if level == locking.LEVEL_INSTANCE:
13959       assert not self.needed_locks[locking.LEVEL_INSTANCE]
13960
13961       # Lock instances optimistically, needs verification once group lock has
13962       # been acquired
13963       self.needed_locks[locking.LEVEL_INSTANCE] = \
13964           self.cfg.GetNodeGroupInstances(self.group_uuid)
13965
13966   @staticmethod
13967   def _UpdateAndVerifyDiskParams(old, new):
13968     """Updates and verifies disk parameters.
13969
13970     """
13971     new_params = _GetUpdatedParams(old, new)
13972     utils.ForceDictType(new_params, constants.DISK_DT_TYPES)
13973     return new_params
13974
13975   def CheckPrereq(self):
13976     """Check prerequisites.
13977
13978     """
13979     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
13980
13981     # Check if locked instances are still correct
13982     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
13983
13984     self.group = self.cfg.GetNodeGroup(self.group_uuid)
13985     cluster = self.cfg.GetClusterInfo()
13986
13987     if self.group is None:
13988       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
13989                                (self.op.group_name, self.group_uuid))
13990
13991     if self.op.ndparams:
13992       new_ndparams = _GetUpdatedParams(self.group.ndparams, self.op.ndparams)
13993       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
13994       self.new_ndparams = new_ndparams
13995
13996     if self.op.diskparams:
13997       diskparams = self.group.diskparams
13998       uavdp = self._UpdateAndVerifyDiskParams
13999       # For each disktemplate subdict update and verify the values
14000       new_diskparams = dict((dt,
14001                              uavdp(diskparams.get(dt, {}),
14002                                    self.op.diskparams[dt]))
14003                             for dt in constants.DISK_TEMPLATES
14004                             if dt in self.op.diskparams)
14005       # As we've all subdicts of diskparams ready, lets merge the actual
14006       # dict with all updated subdicts
14007       self.new_diskparams = objects.FillDict(diskparams, new_diskparams)
14008       try:
14009         utils.VerifyDictOptions(self.new_diskparams, constants.DISK_DT_DEFAULTS)
14010       except errors.OpPrereqError, err:
14011         raise errors.OpPrereqError("While verify diskparams options: %s" % err,
14012                                    errors.ECODE_INVAL)
14013
14014     if self.op.hv_state:
14015       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
14016                                                  self.group.hv_state_static)
14017
14018     if self.op.disk_state:
14019       self.new_disk_state = \
14020         _MergeAndVerifyDiskState(self.op.disk_state,
14021                                  self.group.disk_state_static)
14022
14023     if self.op.ipolicy:
14024       self.new_ipolicy = _GetUpdatedIPolicy(self.group.ipolicy,
14025                                             self.op.ipolicy,
14026                                             group_policy=True)
14027
14028       new_ipolicy = cluster.SimpleFillIPolicy(self.new_ipolicy)
14029       inst_filter = lambda inst: inst.name in owned_instances
14030       instances = self.cfg.GetInstancesInfoByFilter(inst_filter).values()
14031       violations = \
14032           _ComputeNewInstanceViolations(_CalculateGroupIPolicy(cluster,
14033                                                                self.group),
14034                                         new_ipolicy, instances)
14035
14036       if violations:
14037         self.LogWarning("After the ipolicy change the following instances"
14038                         " violate them: %s",
14039                         utils.CommaJoin(violations))
14040
14041   def BuildHooksEnv(self):
14042     """Build hooks env.
14043
14044     """
14045     return {
14046       "GROUP_NAME": self.op.group_name,
14047       "NEW_ALLOC_POLICY": self.op.alloc_policy,
14048       }
14049
14050   def BuildHooksNodes(self):
14051     """Build hooks nodes.
14052
14053     """
14054     mn = self.cfg.GetMasterNode()
14055     return ([mn], [mn])
14056
14057   def Exec(self, feedback_fn):
14058     """Modifies the node group.
14059
14060     """
14061     result = []
14062
14063     if self.op.ndparams:
14064       self.group.ndparams = self.new_ndparams
14065       result.append(("ndparams", str(self.group.ndparams)))
14066
14067     if self.op.diskparams:
14068       self.group.diskparams = self.new_diskparams
14069       result.append(("diskparams", str(self.group.diskparams)))
14070
14071     if self.op.alloc_policy:
14072       self.group.alloc_policy = self.op.alloc_policy
14073
14074     if self.op.hv_state:
14075       self.group.hv_state_static = self.new_hv_state
14076
14077     if self.op.disk_state:
14078       self.group.disk_state_static = self.new_disk_state
14079
14080     if self.op.ipolicy:
14081       self.group.ipolicy = self.new_ipolicy
14082
14083     self.cfg.Update(self.group, feedback_fn)
14084     return result
14085
14086
14087 class LUGroupRemove(LogicalUnit):
14088   HPATH = "group-remove"
14089   HTYPE = constants.HTYPE_GROUP
14090   REQ_BGL = False
14091
14092   def ExpandNames(self):
14093     # This will raises errors.OpPrereqError on its own:
14094     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14095     self.needed_locks = {
14096       locking.LEVEL_NODEGROUP: [self.group_uuid],
14097       }
14098
14099   def CheckPrereq(self):
14100     """Check prerequisites.
14101
14102     This checks that the given group name exists as a node group, that is
14103     empty (i.e., contains no nodes), and that is not the last group of the
14104     cluster.
14105
14106     """
14107     # Verify that the group is empty.
14108     group_nodes = [node.name
14109                    for node in self.cfg.GetAllNodesInfo().values()
14110                    if node.group == self.group_uuid]
14111
14112     if group_nodes:
14113       raise errors.OpPrereqError("Group '%s' not empty, has the following"
14114                                  " nodes: %s" %
14115                                  (self.op.group_name,
14116                                   utils.CommaJoin(utils.NiceSort(group_nodes))),
14117                                  errors.ECODE_STATE)
14118
14119     # Verify the cluster would not be left group-less.
14120     if len(self.cfg.GetNodeGroupList()) == 1:
14121       raise errors.OpPrereqError("Group '%s' is the only group,"
14122                                  " cannot be removed" %
14123                                  self.op.group_name,
14124                                  errors.ECODE_STATE)
14125
14126   def BuildHooksEnv(self):
14127     """Build hooks env.
14128
14129     """
14130     return {
14131       "GROUP_NAME": self.op.group_name,
14132       }
14133
14134   def BuildHooksNodes(self):
14135     """Build hooks nodes.
14136
14137     """
14138     mn = self.cfg.GetMasterNode()
14139     return ([mn], [mn])
14140
14141   def Exec(self, feedback_fn):
14142     """Remove the node group.
14143
14144     """
14145     try:
14146       self.cfg.RemoveNodeGroup(self.group_uuid)
14147     except errors.ConfigurationError:
14148       raise errors.OpExecError("Group '%s' with UUID %s disappeared" %
14149                                (self.op.group_name, self.group_uuid))
14150
14151     self.remove_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
14152
14153
14154 class LUGroupRename(LogicalUnit):
14155   HPATH = "group-rename"
14156   HTYPE = constants.HTYPE_GROUP
14157   REQ_BGL = False
14158
14159   def ExpandNames(self):
14160     # This raises errors.OpPrereqError on its own:
14161     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14162
14163     self.needed_locks = {
14164       locking.LEVEL_NODEGROUP: [self.group_uuid],
14165       }
14166
14167   def CheckPrereq(self):
14168     """Check prerequisites.
14169
14170     Ensures requested new name is not yet used.
14171
14172     """
14173     try:
14174       new_name_uuid = self.cfg.LookupNodeGroup(self.op.new_name)
14175     except errors.OpPrereqError:
14176       pass
14177     else:
14178       raise errors.OpPrereqError("Desired new name '%s' clashes with existing"
14179                                  " node group (UUID: %s)" %
14180                                  (self.op.new_name, new_name_uuid),
14181                                  errors.ECODE_EXISTS)
14182
14183   def BuildHooksEnv(self):
14184     """Build hooks env.
14185
14186     """
14187     return {
14188       "OLD_NAME": self.op.group_name,
14189       "NEW_NAME": self.op.new_name,
14190       }
14191
14192   def BuildHooksNodes(self):
14193     """Build hooks nodes.
14194
14195     """
14196     mn = self.cfg.GetMasterNode()
14197
14198     all_nodes = self.cfg.GetAllNodesInfo()
14199     all_nodes.pop(mn, None)
14200
14201     run_nodes = [mn]
14202     run_nodes.extend(node.name for node in all_nodes.values()
14203                      if node.group == self.group_uuid)
14204
14205     return (run_nodes, run_nodes)
14206
14207   def Exec(self, feedback_fn):
14208     """Rename the node group.
14209
14210     """
14211     group = self.cfg.GetNodeGroup(self.group_uuid)
14212
14213     if group is None:
14214       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
14215                                (self.op.group_name, self.group_uuid))
14216
14217     group.name = self.op.new_name
14218     self.cfg.Update(group, feedback_fn)
14219
14220     return self.op.new_name
14221
14222
14223 class LUGroupEvacuate(LogicalUnit):
14224   HPATH = "group-evacuate"
14225   HTYPE = constants.HTYPE_GROUP
14226   REQ_BGL = False
14227
14228   def ExpandNames(self):
14229     # This raises errors.OpPrereqError on its own:
14230     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14231
14232     if self.op.target_groups:
14233       self.req_target_uuids = map(self.cfg.LookupNodeGroup,
14234                                   self.op.target_groups)
14235     else:
14236       self.req_target_uuids = []
14237
14238     if self.group_uuid in self.req_target_uuids:
14239       raise errors.OpPrereqError("Group to be evacuated (%s) can not be used"
14240                                  " as a target group (targets are %s)" %
14241                                  (self.group_uuid,
14242                                   utils.CommaJoin(self.req_target_uuids)),
14243                                  errors.ECODE_INVAL)
14244
14245     self.op.iallocator = _GetDefaultIAllocator(self.cfg, self.op.iallocator)
14246
14247     self.share_locks = _ShareAll()
14248     self.needed_locks = {
14249       locking.LEVEL_INSTANCE: [],
14250       locking.LEVEL_NODEGROUP: [],
14251       locking.LEVEL_NODE: [],
14252       }
14253
14254   def DeclareLocks(self, level):
14255     if level == locking.LEVEL_INSTANCE:
14256       assert not self.needed_locks[locking.LEVEL_INSTANCE]
14257
14258       # Lock instances optimistically, needs verification once node and group
14259       # locks have been acquired
14260       self.needed_locks[locking.LEVEL_INSTANCE] = \
14261         self.cfg.GetNodeGroupInstances(self.group_uuid)
14262
14263     elif level == locking.LEVEL_NODEGROUP:
14264       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
14265
14266       if self.req_target_uuids:
14267         lock_groups = set([self.group_uuid] + self.req_target_uuids)
14268
14269         # Lock all groups used by instances optimistically; this requires going
14270         # via the node before it's locked, requiring verification later on
14271         lock_groups.update(group_uuid
14272                            for instance_name in
14273                              self.owned_locks(locking.LEVEL_INSTANCE)
14274                            for group_uuid in
14275                              self.cfg.GetInstanceNodeGroups(instance_name))
14276       else:
14277         # No target groups, need to lock all of them
14278         lock_groups = locking.ALL_SET
14279
14280       self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
14281
14282     elif level == locking.LEVEL_NODE:
14283       # This will only lock the nodes in the group to be evacuated which
14284       # contain actual instances
14285       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
14286       self._LockInstancesNodes()
14287
14288       # Lock all nodes in group to be evacuated and target groups
14289       owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
14290       assert self.group_uuid in owned_groups
14291       member_nodes = [node_name
14292                       for group in owned_groups
14293                       for node_name in self.cfg.GetNodeGroup(group).members]
14294       self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
14295
14296   def CheckPrereq(self):
14297     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
14298     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
14299     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
14300
14301     assert owned_groups.issuperset(self.req_target_uuids)
14302     assert self.group_uuid in owned_groups
14303
14304     # Check if locked instances are still correct
14305     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
14306
14307     # Get instance information
14308     self.instances = dict(self.cfg.GetMultiInstanceInfo(owned_instances))
14309
14310     # Check if node groups for locked instances are still correct
14311     _CheckInstancesNodeGroups(self.cfg, self.instances,
14312                               owned_groups, owned_nodes, self.group_uuid)
14313
14314     if self.req_target_uuids:
14315       # User requested specific target groups
14316       self.target_uuids = self.req_target_uuids
14317     else:
14318       # All groups except the one to be evacuated are potential targets
14319       self.target_uuids = [group_uuid for group_uuid in owned_groups
14320                            if group_uuid != self.group_uuid]
14321
14322       if not self.target_uuids:
14323         raise errors.OpPrereqError("There are no possible target groups",
14324                                    errors.ECODE_INVAL)
14325
14326   def BuildHooksEnv(self):
14327     """Build hooks env.
14328
14329     """
14330     return {
14331       "GROUP_NAME": self.op.group_name,
14332       "TARGET_GROUPS": " ".join(self.target_uuids),
14333       }
14334
14335   def BuildHooksNodes(self):
14336     """Build hooks nodes.
14337
14338     """
14339     mn = self.cfg.GetMasterNode()
14340
14341     assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
14342
14343     run_nodes = [mn] + self.cfg.GetNodeGroup(self.group_uuid).members
14344
14345     return (run_nodes, run_nodes)
14346
14347   def Exec(self, feedback_fn):
14348     instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
14349
14350     assert self.group_uuid not in self.target_uuids
14351
14352     ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_CHG_GROUP,
14353                      instances=instances, target_groups=self.target_uuids)
14354
14355     ial.Run(self.op.iallocator)
14356
14357     if not ial.success:
14358       raise errors.OpPrereqError("Can't compute group evacuation using"
14359                                  " iallocator '%s': %s" %
14360                                  (self.op.iallocator, ial.info),
14361                                  errors.ECODE_NORES)
14362
14363     jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
14364
14365     self.LogInfo("Iallocator returned %s job(s) for evacuating node group %s",
14366                  len(jobs), self.op.group_name)
14367
14368     return ResultWithJobs(jobs)
14369
14370
14371 class TagsLU(NoHooksLU): # pylint: disable=W0223
14372   """Generic tags LU.
14373
14374   This is an abstract class which is the parent of all the other tags LUs.
14375
14376   """
14377   def ExpandNames(self):
14378     self.group_uuid = None
14379     self.needed_locks = {}
14380
14381     if self.op.kind == constants.TAG_NODE:
14382       self.op.name = _ExpandNodeName(self.cfg, self.op.name)
14383       lock_level = locking.LEVEL_NODE
14384       lock_name = self.op.name
14385     elif self.op.kind == constants.TAG_INSTANCE:
14386       self.op.name = _ExpandInstanceName(self.cfg, self.op.name)
14387       lock_level = locking.LEVEL_INSTANCE
14388       lock_name = self.op.name
14389     elif self.op.kind == constants.TAG_NODEGROUP:
14390       self.group_uuid = self.cfg.LookupNodeGroup(self.op.name)
14391       lock_level = locking.LEVEL_NODEGROUP
14392       lock_name = self.group_uuid
14393     else:
14394       lock_level = None
14395       lock_name = None
14396
14397     if lock_level and getattr(self.op, "use_locking", True):
14398       self.needed_locks[lock_level] = lock_name
14399
14400     # FIXME: Acquire BGL for cluster tag operations (as of this writing it's
14401     # not possible to acquire the BGL based on opcode parameters)
14402
14403   def CheckPrereq(self):
14404     """Check prerequisites.
14405
14406     """
14407     if self.op.kind == constants.TAG_CLUSTER:
14408       self.target = self.cfg.GetClusterInfo()
14409     elif self.op.kind == constants.TAG_NODE:
14410       self.target = self.cfg.GetNodeInfo(self.op.name)
14411     elif self.op.kind == constants.TAG_INSTANCE:
14412       self.target = self.cfg.GetInstanceInfo(self.op.name)
14413     elif self.op.kind == constants.TAG_NODEGROUP:
14414       self.target = self.cfg.GetNodeGroup(self.group_uuid)
14415     else:
14416       raise errors.OpPrereqError("Wrong tag type requested (%s)" %
14417                                  str(self.op.kind), errors.ECODE_INVAL)
14418
14419
14420 class LUTagsGet(TagsLU):
14421   """Returns the tags of a given object.
14422
14423   """
14424   REQ_BGL = False
14425
14426   def ExpandNames(self):
14427     TagsLU.ExpandNames(self)
14428
14429     # Share locks as this is only a read operation
14430     self.share_locks = _ShareAll()
14431
14432   def Exec(self, feedback_fn):
14433     """Returns the tag list.
14434
14435     """
14436     return list(self.target.GetTags())
14437
14438
14439 class LUTagsSearch(NoHooksLU):
14440   """Searches the tags for a given pattern.
14441
14442   """
14443   REQ_BGL = False
14444
14445   def ExpandNames(self):
14446     self.needed_locks = {}
14447
14448   def CheckPrereq(self):
14449     """Check prerequisites.
14450
14451     This checks the pattern passed for validity by compiling it.
14452
14453     """
14454     try:
14455       self.re = re.compile(self.op.pattern)
14456     except re.error, err:
14457       raise errors.OpPrereqError("Invalid search pattern '%s': %s" %
14458                                  (self.op.pattern, err), errors.ECODE_INVAL)
14459
14460   def Exec(self, feedback_fn):
14461     """Returns the tag list.
14462
14463     """
14464     cfg = self.cfg
14465     tgts = [("/cluster", cfg.GetClusterInfo())]
14466     ilist = cfg.GetAllInstancesInfo().values()
14467     tgts.extend([("/instances/%s" % i.name, i) for i in ilist])
14468     nlist = cfg.GetAllNodesInfo().values()
14469     tgts.extend([("/nodes/%s" % n.name, n) for n in nlist])
14470     tgts.extend(("/nodegroup/%s" % n.name, n)
14471                 for n in cfg.GetAllNodeGroupsInfo().values())
14472     results = []
14473     for path, target in tgts:
14474       for tag in target.GetTags():
14475         if self.re.search(tag):
14476           results.append((path, tag))
14477     return results
14478
14479
14480 class LUTagsSet(TagsLU):
14481   """Sets a tag on a given object.
14482
14483   """
14484   REQ_BGL = False
14485
14486   def CheckPrereq(self):
14487     """Check prerequisites.
14488
14489     This checks the type and length of the tag name and value.
14490
14491     """
14492     TagsLU.CheckPrereq(self)
14493     for tag in self.op.tags:
14494       objects.TaggableObject.ValidateTag(tag)
14495
14496   def Exec(self, feedback_fn):
14497     """Sets the tag.
14498
14499     """
14500     try:
14501       for tag in self.op.tags:
14502         self.target.AddTag(tag)
14503     except errors.TagError, err:
14504       raise errors.OpExecError("Error while setting tag: %s" % str(err))
14505     self.cfg.Update(self.target, feedback_fn)
14506
14507
14508 class LUTagsDel(TagsLU):
14509   """Delete a list of tags from a given object.
14510
14511   """
14512   REQ_BGL = False
14513
14514   def CheckPrereq(self):
14515     """Check prerequisites.
14516
14517     This checks that we have the given tag.
14518
14519     """
14520     TagsLU.CheckPrereq(self)
14521     for tag in self.op.tags:
14522       objects.TaggableObject.ValidateTag(tag)
14523     del_tags = frozenset(self.op.tags)
14524     cur_tags = self.target.GetTags()
14525
14526     diff_tags = del_tags - cur_tags
14527     if diff_tags:
14528       diff_names = ("'%s'" % i for i in sorted(diff_tags))
14529       raise errors.OpPrereqError("Tag(s) %s not found" %
14530                                  (utils.CommaJoin(diff_names), ),
14531                                  errors.ECODE_NOENT)
14532
14533   def Exec(self, feedback_fn):
14534     """Remove the tag from the object.
14535
14536     """
14537     for tag in self.op.tags:
14538       self.target.RemoveTag(tag)
14539     self.cfg.Update(self.target, feedback_fn)
14540
14541
14542 class LUTestDelay(NoHooksLU):
14543   """Sleep for a specified amount of time.
14544
14545   This LU sleeps on the master and/or nodes for a specified amount of
14546   time.
14547
14548   """
14549   REQ_BGL = False
14550
14551   def ExpandNames(self):
14552     """Expand names and set required locks.
14553
14554     This expands the node list, if any.
14555
14556     """
14557     self.needed_locks = {}
14558     if self.op.on_nodes:
14559       # _GetWantedNodes can be used here, but is not always appropriate to use
14560       # this way in ExpandNames. Check LogicalUnit.ExpandNames docstring for
14561       # more information.
14562       self.op.on_nodes = _GetWantedNodes(self, self.op.on_nodes)
14563       self.needed_locks[locking.LEVEL_NODE] = self.op.on_nodes
14564
14565   def _TestDelay(self):
14566     """Do the actual sleep.
14567
14568     """
14569     if self.op.on_master:
14570       if not utils.TestDelay(self.op.duration):
14571         raise errors.OpExecError("Error during master delay test")
14572     if self.op.on_nodes:
14573       result = self.rpc.call_test_delay(self.op.on_nodes, self.op.duration)
14574       for node, node_result in result.items():
14575         node_result.Raise("Failure during rpc call to node %s" % node)
14576
14577   def Exec(self, feedback_fn):
14578     """Execute the test delay opcode, with the wanted repetitions.
14579
14580     """
14581     if self.op.repeat == 0:
14582       self._TestDelay()
14583     else:
14584       top_value = self.op.repeat - 1
14585       for i in range(self.op.repeat):
14586         self.LogInfo("Test delay iteration %d/%d" % (i, top_value))
14587         self._TestDelay()
14588
14589
14590 class LUTestJqueue(NoHooksLU):
14591   """Utility LU to test some aspects of the job queue.
14592
14593   """
14594   REQ_BGL = False
14595
14596   # Must be lower than default timeout for WaitForJobChange to see whether it
14597   # notices changed jobs
14598   _CLIENT_CONNECT_TIMEOUT = 20.0
14599   _CLIENT_CONFIRM_TIMEOUT = 60.0
14600
14601   @classmethod
14602   def _NotifyUsingSocket(cls, cb, errcls):
14603     """Opens a Unix socket and waits for another program to connect.
14604
14605     @type cb: callable
14606     @param cb: Callback to send socket name to client
14607     @type errcls: class
14608     @param errcls: Exception class to use for errors
14609
14610     """
14611     # Using a temporary directory as there's no easy way to create temporary
14612     # sockets without writing a custom loop around tempfile.mktemp and
14613     # socket.bind
14614     tmpdir = tempfile.mkdtemp()
14615     try:
14616       tmpsock = utils.PathJoin(tmpdir, "sock")
14617
14618       logging.debug("Creating temporary socket at %s", tmpsock)
14619       sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
14620       try:
14621         sock.bind(tmpsock)
14622         sock.listen(1)
14623
14624         # Send details to client
14625         cb(tmpsock)
14626
14627         # Wait for client to connect before continuing
14628         sock.settimeout(cls._CLIENT_CONNECT_TIMEOUT)
14629         try:
14630           (conn, _) = sock.accept()
14631         except socket.error, err:
14632           raise errcls("Client didn't connect in time (%s)" % err)
14633       finally:
14634         sock.close()
14635     finally:
14636       # Remove as soon as client is connected
14637       shutil.rmtree(tmpdir)
14638
14639     # Wait for client to close
14640     try:
14641       try:
14642         # pylint: disable=E1101
14643         # Instance of '_socketobject' has no ... member
14644         conn.settimeout(cls._CLIENT_CONFIRM_TIMEOUT)
14645         conn.recv(1)
14646       except socket.error, err:
14647         raise errcls("Client failed to confirm notification (%s)" % err)
14648     finally:
14649       conn.close()
14650
14651   def _SendNotification(self, test, arg, sockname):
14652     """Sends a notification to the client.
14653
14654     @type test: string
14655     @param test: Test name
14656     @param arg: Test argument (depends on test)
14657     @type sockname: string
14658     @param sockname: Socket path
14659
14660     """
14661     self.Log(constants.ELOG_JQUEUE_TEST, (sockname, test, arg))
14662
14663   def _Notify(self, prereq, test, arg):
14664     """Notifies the client of a test.
14665
14666     @type prereq: bool
14667     @param prereq: Whether this is a prereq-phase test
14668     @type test: string
14669     @param test: Test name
14670     @param arg: Test argument (depends on test)
14671
14672     """
14673     if prereq:
14674       errcls = errors.OpPrereqError
14675     else:
14676       errcls = errors.OpExecError
14677
14678     return self._NotifyUsingSocket(compat.partial(self._SendNotification,
14679                                                   test, arg),
14680                                    errcls)
14681
14682   def CheckArguments(self):
14683     self.checkargs_calls = getattr(self, "checkargs_calls", 0) + 1
14684     self.expandnames_calls = 0
14685
14686   def ExpandNames(self):
14687     checkargs_calls = getattr(self, "checkargs_calls", 0)
14688     if checkargs_calls < 1:
14689       raise errors.ProgrammerError("CheckArguments was not called")
14690
14691     self.expandnames_calls += 1
14692
14693     if self.op.notify_waitlock:
14694       self._Notify(True, constants.JQT_EXPANDNAMES, None)
14695
14696     self.LogInfo("Expanding names")
14697
14698     # Get lock on master node (just to get a lock, not for a particular reason)
14699     self.needed_locks = {
14700       locking.LEVEL_NODE: self.cfg.GetMasterNode(),
14701       }
14702
14703   def Exec(self, feedback_fn):
14704     if self.expandnames_calls < 1:
14705       raise errors.ProgrammerError("ExpandNames was not called")
14706
14707     if self.op.notify_exec:
14708       self._Notify(False, constants.JQT_EXEC, None)
14709
14710     self.LogInfo("Executing")
14711
14712     if self.op.log_messages:
14713       self._Notify(False, constants.JQT_STARTMSG, len(self.op.log_messages))
14714       for idx, msg in enumerate(self.op.log_messages):
14715         self.LogInfo("Sending log message %s", idx + 1)
14716         feedback_fn(constants.JQT_MSGPREFIX + msg)
14717         # Report how many test messages have been sent
14718         self._Notify(False, constants.JQT_LOGMSG, idx + 1)
14719
14720     if self.op.fail:
14721       raise errors.OpExecError("Opcode failure was requested")
14722
14723     return True
14724
14725
14726 class IAllocator(object):
14727   """IAllocator framework.
14728
14729   An IAllocator instance has three sets of attributes:
14730     - cfg that is needed to query the cluster
14731     - input data (all members of the _KEYS class attribute are required)
14732     - four buffer attributes (in|out_data|text), that represent the
14733       input (to the external script) in text and data structure format,
14734       and the output from it, again in two formats
14735     - the result variables from the script (success, info, nodes) for
14736       easy usage
14737
14738   """
14739   # pylint: disable=R0902
14740   # lots of instance attributes
14741
14742   def __init__(self, cfg, rpc_runner, mode, **kwargs):
14743     self.cfg = cfg
14744     self.rpc = rpc_runner
14745     # init buffer variables
14746     self.in_text = self.out_text = self.in_data = self.out_data = None
14747     # init all input fields so that pylint is happy
14748     self.mode = mode
14749     self.memory = self.disks = self.disk_template = self.spindle_use = None
14750     self.os = self.tags = self.nics = self.vcpus = None
14751     self.hypervisor = None
14752     self.relocate_from = None
14753     self.name = None
14754     self.instances = None
14755     self.evac_mode = None
14756     self.target_groups = []
14757     # computed fields
14758     self.required_nodes = None
14759     # init result fields
14760     self.success = self.info = self.result = None
14761
14762     try:
14763       (fn, keydata, self._result_check) = self._MODE_DATA[self.mode]
14764     except KeyError:
14765       raise errors.ProgrammerError("Unknown mode '%s' passed to the"
14766                                    " IAllocator" % self.mode)
14767
14768     keyset = [n for (n, _) in keydata]
14769
14770     for key in kwargs:
14771       if key not in keyset:
14772         raise errors.ProgrammerError("Invalid input parameter '%s' to"
14773                                      " IAllocator" % key)
14774       setattr(self, key, kwargs[key])
14775
14776     for key in keyset:
14777       if key not in kwargs:
14778         raise errors.ProgrammerError("Missing input parameter '%s' to"
14779                                      " IAllocator" % key)
14780     self._BuildInputData(compat.partial(fn, self), keydata)
14781
14782   def _ComputeClusterData(self):
14783     """Compute the generic allocator input data.
14784
14785     This is the data that is independent of the actual operation.
14786
14787     """
14788     cfg = self.cfg
14789     cluster_info = cfg.GetClusterInfo()
14790     # cluster data
14791     data = {
14792       "version": constants.IALLOCATOR_VERSION,
14793       "cluster_name": cfg.GetClusterName(),
14794       "cluster_tags": list(cluster_info.GetTags()),
14795       "enabled_hypervisors": list(cluster_info.enabled_hypervisors),
14796       "ipolicy": cluster_info.ipolicy,
14797       }
14798     ninfo = cfg.GetAllNodesInfo()
14799     iinfo = cfg.GetAllInstancesInfo().values()
14800     i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo]
14801
14802     # node data
14803     node_list = [n.name for n in ninfo.values() if n.vm_capable]
14804
14805     if self.mode == constants.IALLOCATOR_MODE_ALLOC:
14806       hypervisor_name = self.hypervisor
14807     elif self.mode == constants.IALLOCATOR_MODE_RELOC:
14808       hypervisor_name = cfg.GetInstanceInfo(self.name).hypervisor
14809     else:
14810       hypervisor_name = cluster_info.primary_hypervisor
14811
14812     node_data = self.rpc.call_node_info(node_list, [cfg.GetVGName()],
14813                                         [hypervisor_name])
14814     node_iinfo = \
14815       self.rpc.call_all_instances_info(node_list,
14816                                        cluster_info.enabled_hypervisors)
14817
14818     data["nodegroups"] = self._ComputeNodeGroupData(cfg)
14819
14820     config_ndata = self._ComputeBasicNodeData(cfg, ninfo)
14821     data["nodes"] = self._ComputeDynamicNodeData(ninfo, node_data, node_iinfo,
14822                                                  i_list, config_ndata)
14823     assert len(data["nodes"]) == len(ninfo), \
14824         "Incomplete node data computed"
14825
14826     data["instances"] = self._ComputeInstanceData(cluster_info, i_list)
14827
14828     self.in_data = data
14829
14830   @staticmethod
14831   def _ComputeNodeGroupData(cfg):
14832     """Compute node groups data.
14833
14834     """
14835     cluster = cfg.GetClusterInfo()
14836     ng = dict((guuid, {
14837       "name": gdata.name,
14838       "alloc_policy": gdata.alloc_policy,
14839       "ipolicy": _CalculateGroupIPolicy(cluster, gdata),
14840       })
14841       for guuid, gdata in cfg.GetAllNodeGroupsInfo().items())
14842
14843     return ng
14844
14845   @staticmethod
14846   def _ComputeBasicNodeData(cfg, node_cfg):
14847     """Compute global node data.
14848
14849     @rtype: dict
14850     @returns: a dict of name: (node dict, node config)
14851
14852     """
14853     # fill in static (config-based) values
14854     node_results = dict((ninfo.name, {
14855       "tags": list(ninfo.GetTags()),
14856       "primary_ip": ninfo.primary_ip,
14857       "secondary_ip": ninfo.secondary_ip,
14858       "offline": ninfo.offline,
14859       "drained": ninfo.drained,
14860       "master_candidate": ninfo.master_candidate,
14861       "group": ninfo.group,
14862       "master_capable": ninfo.master_capable,
14863       "vm_capable": ninfo.vm_capable,
14864       "ndparams": cfg.GetNdParams(ninfo),
14865       })
14866       for ninfo in node_cfg.values())
14867
14868     return node_results
14869
14870   @staticmethod
14871   def _ComputeDynamicNodeData(node_cfg, node_data, node_iinfo, i_list,
14872                               node_results):
14873     """Compute global node data.
14874
14875     @param node_results: the basic node structures as filled from the config
14876
14877     """
14878     #TODO(dynmem): compute the right data on MAX and MIN memory
14879     # make a copy of the current dict
14880     node_results = dict(node_results)
14881     for nname, nresult in node_data.items():
14882       assert nname in node_results, "Missing basic data for node %s" % nname
14883       ninfo = node_cfg[nname]
14884
14885       if not (ninfo.offline or ninfo.drained):
14886         nresult.Raise("Can't get data for node %s" % nname)
14887         node_iinfo[nname].Raise("Can't get node instance info from node %s" %
14888                                 nname)
14889         remote_info = _MakeLegacyNodeInfo(nresult.payload)
14890
14891         for attr in ["memory_total", "memory_free", "memory_dom0",
14892                      "vg_size", "vg_free", "cpu_total"]:
14893           if attr not in remote_info:
14894             raise errors.OpExecError("Node '%s' didn't return attribute"
14895                                      " '%s'" % (nname, attr))
14896           if not isinstance(remote_info[attr], int):
14897             raise errors.OpExecError("Node '%s' returned invalid value"
14898                                      " for '%s': %s" %
14899                                      (nname, attr, remote_info[attr]))
14900         # compute memory used by primary instances
14901         i_p_mem = i_p_up_mem = 0
14902         for iinfo, beinfo in i_list:
14903           if iinfo.primary_node == nname:
14904             i_p_mem += beinfo[constants.BE_MAXMEM]
14905             if iinfo.name not in node_iinfo[nname].payload:
14906               i_used_mem = 0
14907             else:
14908               i_used_mem = int(node_iinfo[nname].payload[iinfo.name]["memory"])
14909             i_mem_diff = beinfo[constants.BE_MAXMEM] - i_used_mem
14910             remote_info["memory_free"] -= max(0, i_mem_diff)
14911
14912             if iinfo.admin_state == constants.ADMINST_UP:
14913               i_p_up_mem += beinfo[constants.BE_MAXMEM]
14914
14915         # compute memory used by instances
14916         pnr_dyn = {
14917           "total_memory": remote_info["memory_total"],
14918           "reserved_memory": remote_info["memory_dom0"],
14919           "free_memory": remote_info["memory_free"],
14920           "total_disk": remote_info["vg_size"],
14921           "free_disk": remote_info["vg_free"],
14922           "total_cpus": remote_info["cpu_total"],
14923           "i_pri_memory": i_p_mem,
14924           "i_pri_up_memory": i_p_up_mem,
14925           }
14926         pnr_dyn.update(node_results[nname])
14927         node_results[nname] = pnr_dyn
14928
14929     return node_results
14930
14931   @staticmethod
14932   def _ComputeInstanceData(cluster_info, i_list):
14933     """Compute global instance data.
14934
14935     """
14936     instance_data = {}
14937     for iinfo, beinfo in i_list:
14938       nic_data = []
14939       for nic in iinfo.nics:
14940         filled_params = cluster_info.SimpleFillNIC(nic.nicparams)
14941         nic_dict = {
14942           "mac": nic.mac,
14943           "ip": nic.ip,
14944           "mode": filled_params[constants.NIC_MODE],
14945           "link": filled_params[constants.NIC_LINK],
14946           }
14947         if filled_params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
14948           nic_dict["bridge"] = filled_params[constants.NIC_LINK]
14949         nic_data.append(nic_dict)
14950       pir = {
14951         "tags": list(iinfo.GetTags()),
14952         "admin_state": iinfo.admin_state,
14953         "vcpus": beinfo[constants.BE_VCPUS],
14954         "memory": beinfo[constants.BE_MAXMEM],
14955         "spindle_use": beinfo[constants.BE_SPINDLE_USE],
14956         "os": iinfo.os,
14957         "nodes": [iinfo.primary_node] + list(iinfo.secondary_nodes),
14958         "nics": nic_data,
14959         "disks": [{constants.IDISK_SIZE: dsk.size,
14960                    constants.IDISK_MODE: dsk.mode}
14961                   for dsk in iinfo.disks],
14962         "disk_template": iinfo.disk_template,
14963         "hypervisor": iinfo.hypervisor,
14964         }
14965       pir["disk_space_total"] = _ComputeDiskSize(iinfo.disk_template,
14966                                                  pir["disks"])
14967       instance_data[iinfo.name] = pir
14968
14969     return instance_data
14970
14971   def _AddNewInstance(self):
14972     """Add new instance data to allocator structure.
14973
14974     This in combination with _AllocatorGetClusterData will create the
14975     correct structure needed as input for the allocator.
14976
14977     The checks for the completeness of the opcode must have already been
14978     done.
14979
14980     """
14981     disk_space = _ComputeDiskSize(self.disk_template, self.disks)
14982
14983     if self.disk_template in constants.DTS_INT_MIRROR:
14984       self.required_nodes = 2
14985     else:
14986       self.required_nodes = 1
14987
14988     request = {
14989       "name": self.name,
14990       "disk_template": self.disk_template,
14991       "tags": self.tags,
14992       "os": self.os,
14993       "vcpus": self.vcpus,
14994       "memory": self.memory,
14995       "spindle_use": self.spindle_use,
14996       "disks": self.disks,
14997       "disk_space_total": disk_space,
14998       "nics": self.nics,
14999       "required_nodes": self.required_nodes,
15000       "hypervisor": self.hypervisor,
15001       }
15002
15003     return request
15004
15005   def _AddRelocateInstance(self):
15006     """Add relocate instance data to allocator structure.
15007
15008     This in combination with _IAllocatorGetClusterData will create the
15009     correct structure needed as input for the allocator.
15010
15011     The checks for the completeness of the opcode must have already been
15012     done.
15013
15014     """
15015     instance = self.cfg.GetInstanceInfo(self.name)
15016     if instance is None:
15017       raise errors.ProgrammerError("Unknown instance '%s' passed to"
15018                                    " IAllocator" % self.name)
15019
15020     if instance.disk_template not in constants.DTS_MIRRORED:
15021       raise errors.OpPrereqError("Can't relocate non-mirrored instances",
15022                                  errors.ECODE_INVAL)
15023
15024     if instance.disk_template in constants.DTS_INT_MIRROR and \
15025         len(instance.secondary_nodes) != 1:
15026       raise errors.OpPrereqError("Instance has not exactly one secondary node",
15027                                  errors.ECODE_STATE)
15028
15029     self.required_nodes = 1
15030     disk_sizes = [{constants.IDISK_SIZE: disk.size} for disk in instance.disks]
15031     disk_space = _ComputeDiskSize(instance.disk_template, disk_sizes)
15032
15033     request = {
15034       "name": self.name,
15035       "disk_space_total": disk_space,
15036       "required_nodes": self.required_nodes,
15037       "relocate_from": self.relocate_from,
15038       }
15039     return request
15040
15041   def _AddNodeEvacuate(self):
15042     """Get data for node-evacuate requests.
15043
15044     """
15045     return {
15046       "instances": self.instances,
15047       "evac_mode": self.evac_mode,
15048       }
15049
15050   def _AddChangeGroup(self):
15051     """Get data for node-evacuate requests.
15052
15053     """
15054     return {
15055       "instances": self.instances,
15056       "target_groups": self.target_groups,
15057       }
15058
15059   def _BuildInputData(self, fn, keydata):
15060     """Build input data structures.
15061
15062     """
15063     self._ComputeClusterData()
15064
15065     request = fn()
15066     request["type"] = self.mode
15067     for keyname, keytype in keydata:
15068       if keyname not in request:
15069         raise errors.ProgrammerError("Request parameter %s is missing" %
15070                                      keyname)
15071       val = request[keyname]
15072       if not keytype(val):
15073         raise errors.ProgrammerError("Request parameter %s doesn't pass"
15074                                      " validation, value %s, expected"
15075                                      " type %s" % (keyname, val, keytype))
15076     self.in_data["request"] = request
15077
15078     self.in_text = serializer.Dump(self.in_data)
15079
15080   _STRING_LIST = ht.TListOf(ht.TString)
15081   _JOB_LIST = ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, {
15082      # pylint: disable=E1101
15083      # Class '...' has no 'OP_ID' member
15084      "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID,
15085                           opcodes.OpInstanceMigrate.OP_ID,
15086                           opcodes.OpInstanceReplaceDisks.OP_ID])
15087      })))
15088
15089   _NEVAC_MOVED = \
15090     ht.TListOf(ht.TAnd(ht.TIsLength(3),
15091                        ht.TItems([ht.TNonEmptyString,
15092                                   ht.TNonEmptyString,
15093                                   ht.TListOf(ht.TNonEmptyString),
15094                                  ])))
15095   _NEVAC_FAILED = \
15096     ht.TListOf(ht.TAnd(ht.TIsLength(2),
15097                        ht.TItems([ht.TNonEmptyString,
15098                                   ht.TMaybeString,
15099                                  ])))
15100   _NEVAC_RESULT = ht.TAnd(ht.TIsLength(3),
15101                           ht.TItems([_NEVAC_MOVED, _NEVAC_FAILED, _JOB_LIST]))
15102
15103   _MODE_DATA = {
15104     constants.IALLOCATOR_MODE_ALLOC:
15105       (_AddNewInstance,
15106        [
15107         ("name", ht.TString),
15108         ("memory", ht.TInt),
15109         ("spindle_use", ht.TInt),
15110         ("disks", ht.TListOf(ht.TDict)),
15111         ("disk_template", ht.TString),
15112         ("os", ht.TString),
15113         ("tags", _STRING_LIST),
15114         ("nics", ht.TListOf(ht.TDict)),
15115         ("vcpus", ht.TInt),
15116         ("hypervisor", ht.TString),
15117         ], ht.TList),
15118     constants.IALLOCATOR_MODE_RELOC:
15119       (_AddRelocateInstance,
15120        [("name", ht.TString), ("relocate_from", _STRING_LIST)],
15121        ht.TList),
15122      constants.IALLOCATOR_MODE_NODE_EVAC:
15123       (_AddNodeEvacuate, [
15124         ("instances", _STRING_LIST),
15125         ("evac_mode", ht.TElemOf(constants.IALLOCATOR_NEVAC_MODES)),
15126         ], _NEVAC_RESULT),
15127      constants.IALLOCATOR_MODE_CHG_GROUP:
15128       (_AddChangeGroup, [
15129         ("instances", _STRING_LIST),
15130         ("target_groups", _STRING_LIST),
15131         ], _NEVAC_RESULT),
15132     }
15133
15134   def Run(self, name, validate=True, call_fn=None):
15135     """Run an instance allocator and return the results.
15136
15137     """
15138     if call_fn is None:
15139       call_fn = self.rpc.call_iallocator_runner
15140
15141     result = call_fn(self.cfg.GetMasterNode(), name, self.in_text)
15142     result.Raise("Failure while running the iallocator script")
15143
15144     self.out_text = result.payload
15145     if validate:
15146       self._ValidateResult()
15147
15148   def _ValidateResult(self):
15149     """Process the allocator results.
15150
15151     This will process and if successful save the result in
15152     self.out_data and the other parameters.
15153
15154     """
15155     try:
15156       rdict = serializer.Load(self.out_text)
15157     except Exception, err:
15158       raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
15159
15160     if not isinstance(rdict, dict):
15161       raise errors.OpExecError("Can't parse iallocator results: not a dict")
15162
15163     # TODO: remove backwards compatiblity in later versions
15164     if "nodes" in rdict and "result" not in rdict:
15165       rdict["result"] = rdict["nodes"]
15166       del rdict["nodes"]
15167
15168     for key in "success", "info", "result":
15169       if key not in rdict:
15170         raise errors.OpExecError("Can't parse iallocator results:"
15171                                  " missing key '%s'" % key)
15172       setattr(self, key, rdict[key])
15173
15174     if not self._result_check(self.result):
15175       raise errors.OpExecError("Iallocator returned invalid result,"
15176                                " expected %s, got %s" %
15177                                (self._result_check, self.result),
15178                                errors.ECODE_INVAL)
15179
15180     if self.mode == constants.IALLOCATOR_MODE_RELOC:
15181       assert self.relocate_from is not None
15182       assert self.required_nodes == 1
15183
15184       node2group = dict((name, ndata["group"])
15185                         for (name, ndata) in self.in_data["nodes"].items())
15186
15187       fn = compat.partial(self._NodesToGroups, node2group,
15188                           self.in_data["nodegroups"])
15189
15190       instance = self.cfg.GetInstanceInfo(self.name)
15191       request_groups = fn(self.relocate_from + [instance.primary_node])
15192       result_groups = fn(rdict["result"] + [instance.primary_node])
15193
15194       if self.success and not set(result_groups).issubset(request_groups):
15195         raise errors.OpExecError("Groups of nodes returned by iallocator (%s)"
15196                                  " differ from original groups (%s)" %
15197                                  (utils.CommaJoin(result_groups),
15198                                   utils.CommaJoin(request_groups)))
15199
15200     elif self.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
15201       assert self.evac_mode in constants.IALLOCATOR_NEVAC_MODES
15202
15203     self.out_data = rdict
15204
15205   @staticmethod
15206   def _NodesToGroups(node2group, groups, nodes):
15207     """Returns a list of unique group names for a list of nodes.
15208
15209     @type node2group: dict
15210     @param node2group: Map from node name to group UUID
15211     @type groups: dict
15212     @param groups: Group information
15213     @type nodes: list
15214     @param nodes: Node names
15215
15216     """
15217     result = set()
15218
15219     for node in nodes:
15220       try:
15221         group_uuid = node2group[node]
15222       except KeyError:
15223         # Ignore unknown node
15224         pass
15225       else:
15226         try:
15227           group = groups[group_uuid]
15228         except KeyError:
15229           # Can't find group, let's use UUID
15230           group_name = group_uuid
15231         else:
15232           group_name = group["name"]
15233
15234         result.add(group_name)
15235
15236     return sorted(result)
15237
15238
15239 class LUTestAllocator(NoHooksLU):
15240   """Run allocator tests.
15241
15242   This LU runs the allocator tests
15243
15244   """
15245   def CheckPrereq(self):
15246     """Check prerequisites.
15247
15248     This checks the opcode parameters depending on the director and mode test.
15249
15250     """
15251     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
15252       for attr in ["memory", "disks", "disk_template",
15253                    "os", "tags", "nics", "vcpus"]:
15254         if not hasattr(self.op, attr):
15255           raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
15256                                      attr, errors.ECODE_INVAL)
15257       iname = self.cfg.ExpandInstanceName(self.op.name)
15258       if iname is not None:
15259         raise errors.OpPrereqError("Instance '%s' already in the cluster" %
15260                                    iname, errors.ECODE_EXISTS)
15261       if not isinstance(self.op.nics, list):
15262         raise errors.OpPrereqError("Invalid parameter 'nics'",
15263                                    errors.ECODE_INVAL)
15264       if not isinstance(self.op.disks, list):
15265         raise errors.OpPrereqError("Invalid parameter 'disks'",
15266                                    errors.ECODE_INVAL)
15267       for row in self.op.disks:
15268         if (not isinstance(row, dict) or
15269             constants.IDISK_SIZE not in row or
15270             not isinstance(row[constants.IDISK_SIZE], int) or
15271             constants.IDISK_MODE not in row or
15272             row[constants.IDISK_MODE] not in constants.DISK_ACCESS_SET):
15273           raise errors.OpPrereqError("Invalid contents of the 'disks'"
15274                                      " parameter", errors.ECODE_INVAL)
15275       if self.op.hypervisor is None:
15276         self.op.hypervisor = self.cfg.GetHypervisorType()
15277     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
15278       fname = _ExpandInstanceName(self.cfg, self.op.name)
15279       self.op.name = fname
15280       self.relocate_from = \
15281           list(self.cfg.GetInstanceInfo(fname).secondary_nodes)
15282     elif self.op.mode in (constants.IALLOCATOR_MODE_CHG_GROUP,
15283                           constants.IALLOCATOR_MODE_NODE_EVAC):
15284       if not self.op.instances:
15285         raise errors.OpPrereqError("Missing instances", errors.ECODE_INVAL)
15286       self.op.instances = _GetWantedInstances(self, self.op.instances)
15287     else:
15288       raise errors.OpPrereqError("Invalid test allocator mode '%s'" %
15289                                  self.op.mode, errors.ECODE_INVAL)
15290
15291     if self.op.direction == constants.IALLOCATOR_DIR_OUT:
15292       if self.op.allocator is None:
15293         raise errors.OpPrereqError("Missing allocator name",
15294                                    errors.ECODE_INVAL)
15295     elif self.op.direction != constants.IALLOCATOR_DIR_IN:
15296       raise errors.OpPrereqError("Wrong allocator test '%s'" %
15297                                  self.op.direction, errors.ECODE_INVAL)
15298
15299   def Exec(self, feedback_fn):
15300     """Run the allocator test.
15301
15302     """
15303     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
15304       ial = IAllocator(self.cfg, self.rpc,
15305                        mode=self.op.mode,
15306                        name=self.op.name,
15307                        memory=self.op.memory,
15308                        disks=self.op.disks,
15309                        disk_template=self.op.disk_template,
15310                        os=self.op.os,
15311                        tags=self.op.tags,
15312                        nics=self.op.nics,
15313                        vcpus=self.op.vcpus,
15314                        hypervisor=self.op.hypervisor,
15315                        )
15316     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
15317       ial = IAllocator(self.cfg, self.rpc,
15318                        mode=self.op.mode,
15319                        name=self.op.name,
15320                        relocate_from=list(self.relocate_from),
15321                        )
15322     elif self.op.mode == constants.IALLOCATOR_MODE_CHG_GROUP:
15323       ial = IAllocator(self.cfg, self.rpc,
15324                        mode=self.op.mode,
15325                        instances=self.op.instances,
15326                        target_groups=self.op.target_groups)
15327     elif self.op.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
15328       ial = IAllocator(self.cfg, self.rpc,
15329                        mode=self.op.mode,
15330                        instances=self.op.instances,
15331                        evac_mode=self.op.evac_mode)
15332     else:
15333       raise errors.ProgrammerError("Uncatched mode %s in"
15334                                    " LUTestAllocator.Exec", self.op.mode)
15335
15336     if self.op.direction == constants.IALLOCATOR_DIR_IN:
15337       result = ial.in_text
15338     else:
15339       ial.Run(self.op.allocator, validate=False)
15340       result = ial.out_text
15341     return result
15342
15343
15344 #: Query type implementations
15345 _QUERY_IMPL = {
15346   constants.QR_CLUSTER: _ClusterQuery,
15347   constants.QR_INSTANCE: _InstanceQuery,
15348   constants.QR_NODE: _NodeQuery,
15349   constants.QR_GROUP: _GroupQuery,
15350   constants.QR_OS: _OsQuery,
15351   constants.QR_EXPORT: _ExportQuery,
15352   }
15353
15354 assert set(_QUERY_IMPL.keys()) == constants.QR_VIA_OP
15355
15356
15357 def _GetQueryImplementation(name):
15358   """Returns the implemtnation for a query type.
15359
15360   @param name: Query type, must be one of L{constants.QR_VIA_OP}
15361
15362   """
15363   try:
15364     return _QUERY_IMPL[name]
15365   except KeyError:
15366     raise errors.OpPrereqError("Unknown query resource '%s'" % name,
15367                                errors.ECODE_INVAL)