Fix cluster verify error on master-ip-setup script
[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   vm_nodes = lu.cfg.GetVmCapableNodeList()
4370
4371   if additional_nodes is not None:
4372     online_nodes.extend(additional_nodes)
4373     if additional_vm:
4374       vm_nodes.extend(additional_nodes)
4375
4376   # Never distribute to master node
4377   for nodelist in [online_nodes, vm_nodes]:
4378     if master_info.name in nodelist:
4379       nodelist.remove(master_info.name)
4380
4381   # Gather file lists
4382   (files_all, _, files_mc, files_vm) = \
4383     _ComputeAncillaryFiles(cluster, True)
4384
4385   # Never re-distribute configuration file from here
4386   assert not (constants.CLUSTER_CONF_FILE in files_all or
4387               constants.CLUSTER_CONF_FILE in files_vm)
4388   assert not files_mc, "Master candidates not handled in this function"
4389
4390   filemap = [
4391     (online_nodes, files_all),
4392     (vm_nodes, files_vm),
4393     ]
4394
4395   # Upload the files
4396   for (node_list, files) in filemap:
4397     for fname in files:
4398       _UploadHelper(lu, node_list, fname)
4399
4400
4401 class LUClusterRedistConf(NoHooksLU):
4402   """Force the redistribution of cluster configuration.
4403
4404   This is a very simple LU.
4405
4406   """
4407   REQ_BGL = False
4408
4409   def ExpandNames(self):
4410     self.needed_locks = {
4411       locking.LEVEL_NODE: locking.ALL_SET,
4412     }
4413     self.share_locks[locking.LEVEL_NODE] = 1
4414
4415   def Exec(self, feedback_fn):
4416     """Redistribute the configuration.
4417
4418     """
4419     self.cfg.Update(self.cfg.GetClusterInfo(), feedback_fn)
4420     _RedistributeAncillaryFiles(self)
4421
4422
4423 class LUClusterActivateMasterIp(NoHooksLU):
4424   """Activate the master IP on the master node.
4425
4426   """
4427   def Exec(self, feedback_fn):
4428     """Activate the master IP.
4429
4430     """
4431     master_params = self.cfg.GetMasterNetworkParameters()
4432     ems = self.cfg.GetUseExternalMipScript()
4433     result = self.rpc.call_node_activate_master_ip(master_params.name,
4434                                                    master_params, ems)
4435     result.Raise("Could not activate the master IP")
4436
4437
4438 class LUClusterDeactivateMasterIp(NoHooksLU):
4439   """Deactivate the master IP on the master node.
4440
4441   """
4442   def Exec(self, feedback_fn):
4443     """Deactivate the master IP.
4444
4445     """
4446     master_params = self.cfg.GetMasterNetworkParameters()
4447     ems = self.cfg.GetUseExternalMipScript()
4448     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
4449                                                      master_params, ems)
4450     result.Raise("Could not deactivate the master IP")
4451
4452
4453 def _WaitForSync(lu, instance, disks=None, oneshot=False):
4454   """Sleep and poll for an instance's disk to sync.
4455
4456   """
4457   if not instance.disks or disks is not None and not disks:
4458     return True
4459
4460   disks = _ExpandCheckDisks(instance, disks)
4461
4462   if not oneshot:
4463     lu.proc.LogInfo("Waiting for instance %s to sync disks." % instance.name)
4464
4465   node = instance.primary_node
4466
4467   for dev in disks:
4468     lu.cfg.SetDiskID(dev, node)
4469
4470   # TODO: Convert to utils.Retry
4471
4472   retries = 0
4473   degr_retries = 10 # in seconds, as we sleep 1 second each time
4474   while True:
4475     max_time = 0
4476     done = True
4477     cumul_degraded = False
4478     rstats = lu.rpc.call_blockdev_getmirrorstatus(node, (disks, instance))
4479     msg = rstats.fail_msg
4480     if msg:
4481       lu.LogWarning("Can't get any data from node %s: %s", node, msg)
4482       retries += 1
4483       if retries >= 10:
4484         raise errors.RemoteError("Can't contact node %s for mirror data,"
4485                                  " aborting." % node)
4486       time.sleep(6)
4487       continue
4488     rstats = rstats.payload
4489     retries = 0
4490     for i, mstat in enumerate(rstats):
4491       if mstat is None:
4492         lu.LogWarning("Can't compute data for node %s/%s",
4493                            node, disks[i].iv_name)
4494         continue
4495
4496       cumul_degraded = (cumul_degraded or
4497                         (mstat.is_degraded and mstat.sync_percent is None))
4498       if mstat.sync_percent is not None:
4499         done = False
4500         if mstat.estimated_time is not None:
4501           rem_time = ("%s remaining (estimated)" %
4502                       utils.FormatSeconds(mstat.estimated_time))
4503           max_time = mstat.estimated_time
4504         else:
4505           rem_time = "no time estimate"
4506         lu.proc.LogInfo("- device %s: %5.2f%% done, %s" %
4507                         (disks[i].iv_name, mstat.sync_percent, rem_time))
4508
4509     # if we're done but degraded, let's do a few small retries, to
4510     # make sure we see a stable and not transient situation; therefore
4511     # we force restart of the loop
4512     if (done or oneshot) and cumul_degraded and degr_retries > 0:
4513       logging.info("Degraded disks found, %d retries left", degr_retries)
4514       degr_retries -= 1
4515       time.sleep(1)
4516       continue
4517
4518     if done or oneshot:
4519       break
4520
4521     time.sleep(min(60, max_time))
4522
4523   if done:
4524     lu.proc.LogInfo("Instance %s's disks are in sync." % instance.name)
4525   return not cumul_degraded
4526
4527
4528 def _BlockdevFind(lu, node, dev, instance):
4529   """Wrapper around call_blockdev_find to annotate diskparams.
4530
4531   @param lu: A reference to the lu object
4532   @param node: The node to call out
4533   @param dev: The device to find
4534   @param instance: The instance object the device belongs to
4535   @returns The result of the rpc call
4536
4537   """
4538   (disk,) = _AnnotateDiskParams(instance, [dev], lu.cfg)
4539   return lu.rpc.call_blockdev_find(node, disk)
4540
4541
4542 def _CheckDiskConsistency(lu, instance, dev, node, on_primary, ldisk=False):
4543   """Wrapper around L{_CheckDiskConsistencyInner}.
4544
4545   """
4546   (disk,) = _AnnotateDiskParams(instance, [dev], lu.cfg)
4547   return _CheckDiskConsistencyInner(lu, instance, disk, node, on_primary,
4548                                     ldisk=ldisk)
4549
4550
4551 def _CheckDiskConsistencyInner(lu, instance, dev, node, on_primary,
4552                                ldisk=False):
4553   """Check that mirrors are not degraded.
4554
4555   @attention: The device has to be annotated already.
4556
4557   The ldisk parameter, if True, will change the test from the
4558   is_degraded attribute (which represents overall non-ok status for
4559   the device(s)) to the ldisk (representing the local storage status).
4560
4561   """
4562   lu.cfg.SetDiskID(dev, node)
4563
4564   result = True
4565
4566   if on_primary or dev.AssembleOnSecondary():
4567     rstats = lu.rpc.call_blockdev_find(node, dev)
4568     msg = rstats.fail_msg
4569     if msg:
4570       lu.LogWarning("Can't find disk on node %s: %s", node, msg)
4571       result = False
4572     elif not rstats.payload:
4573       lu.LogWarning("Can't find disk on node %s", node)
4574       result = False
4575     else:
4576       if ldisk:
4577         result = result and rstats.payload.ldisk_status == constants.LDS_OKAY
4578       else:
4579         result = result and not rstats.payload.is_degraded
4580
4581   if dev.children:
4582     for child in dev.children:
4583       result = result and _CheckDiskConsistencyInner(lu, instance, child, node,
4584                                                      on_primary)
4585
4586   return result
4587
4588
4589 class LUOobCommand(NoHooksLU):
4590   """Logical unit for OOB handling.
4591
4592   """
4593   REQ_BGL = False
4594   _SKIP_MASTER = (constants.OOB_POWER_OFF, constants.OOB_POWER_CYCLE)
4595
4596   def ExpandNames(self):
4597     """Gather locks we need.
4598
4599     """
4600     if self.op.node_names:
4601       self.op.node_names = _GetWantedNodes(self, self.op.node_names)
4602       lock_names = self.op.node_names
4603     else:
4604       lock_names = locking.ALL_SET
4605
4606     self.needed_locks = {
4607       locking.LEVEL_NODE: lock_names,
4608       }
4609
4610   def CheckPrereq(self):
4611     """Check prerequisites.
4612
4613     This checks:
4614      - the node exists in the configuration
4615      - OOB is supported
4616
4617     Any errors are signaled by raising errors.OpPrereqError.
4618
4619     """
4620     self.nodes = []
4621     self.master_node = self.cfg.GetMasterNode()
4622
4623     assert self.op.power_delay >= 0.0
4624
4625     if self.op.node_names:
4626       if (self.op.command in self._SKIP_MASTER and
4627           self.master_node in self.op.node_names):
4628         master_node_obj = self.cfg.GetNodeInfo(self.master_node)
4629         master_oob_handler = _SupportsOob(self.cfg, master_node_obj)
4630
4631         if master_oob_handler:
4632           additional_text = ("run '%s %s %s' if you want to operate on the"
4633                              " master regardless") % (master_oob_handler,
4634                                                       self.op.command,
4635                                                       self.master_node)
4636         else:
4637           additional_text = "it does not support out-of-band operations"
4638
4639         raise errors.OpPrereqError(("Operating on the master node %s is not"
4640                                     " allowed for %s; %s") %
4641                                    (self.master_node, self.op.command,
4642                                     additional_text), errors.ECODE_INVAL)
4643     else:
4644       self.op.node_names = self.cfg.GetNodeList()
4645       if self.op.command in self._SKIP_MASTER:
4646         self.op.node_names.remove(self.master_node)
4647
4648     if self.op.command in self._SKIP_MASTER:
4649       assert self.master_node not in self.op.node_names
4650
4651     for (node_name, node) in self.cfg.GetMultiNodeInfo(self.op.node_names):
4652       if node is None:
4653         raise errors.OpPrereqError("Node %s not found" % node_name,
4654                                    errors.ECODE_NOENT)
4655       else:
4656         self.nodes.append(node)
4657
4658       if (not self.op.ignore_status and
4659           (self.op.command == constants.OOB_POWER_OFF and not node.offline)):
4660         raise errors.OpPrereqError(("Cannot power off node %s because it is"
4661                                     " not marked offline") % node_name,
4662                                    errors.ECODE_STATE)
4663
4664   def Exec(self, feedback_fn):
4665     """Execute OOB and return result if we expect any.
4666
4667     """
4668     master_node = self.master_node
4669     ret = []
4670
4671     for idx, node in enumerate(utils.NiceSort(self.nodes,
4672                                               key=lambda node: node.name)):
4673       node_entry = [(constants.RS_NORMAL, node.name)]
4674       ret.append(node_entry)
4675
4676       oob_program = _SupportsOob(self.cfg, node)
4677
4678       if not oob_program:
4679         node_entry.append((constants.RS_UNAVAIL, None))
4680         continue
4681
4682       logging.info("Executing out-of-band command '%s' using '%s' on %s",
4683                    self.op.command, oob_program, node.name)
4684       result = self.rpc.call_run_oob(master_node, oob_program,
4685                                      self.op.command, node.name,
4686                                      self.op.timeout)
4687
4688       if result.fail_msg:
4689         self.LogWarning("Out-of-band RPC failed on node '%s': %s",
4690                         node.name, result.fail_msg)
4691         node_entry.append((constants.RS_NODATA, None))
4692       else:
4693         try:
4694           self._CheckPayload(result)
4695         except errors.OpExecError, err:
4696           self.LogWarning("Payload returned by node '%s' is not valid: %s",
4697                           node.name, err)
4698           node_entry.append((constants.RS_NODATA, None))
4699         else:
4700           if self.op.command == constants.OOB_HEALTH:
4701             # For health we should log important events
4702             for item, status in result.payload:
4703               if status in [constants.OOB_STATUS_WARNING,
4704                             constants.OOB_STATUS_CRITICAL]:
4705                 self.LogWarning("Item '%s' on node '%s' has status '%s'",
4706                                 item, node.name, status)
4707
4708           if self.op.command == constants.OOB_POWER_ON:
4709             node.powered = True
4710           elif self.op.command == constants.OOB_POWER_OFF:
4711             node.powered = False
4712           elif self.op.command == constants.OOB_POWER_STATUS:
4713             powered = result.payload[constants.OOB_POWER_STATUS_POWERED]
4714             if powered != node.powered:
4715               logging.warning(("Recorded power state (%s) of node '%s' does not"
4716                                " match actual power state (%s)"), node.powered,
4717                               node.name, powered)
4718
4719           # For configuration changing commands we should update the node
4720           if self.op.command in (constants.OOB_POWER_ON,
4721                                  constants.OOB_POWER_OFF):
4722             self.cfg.Update(node, feedback_fn)
4723
4724           node_entry.append((constants.RS_NORMAL, result.payload))
4725
4726           if (self.op.command == constants.OOB_POWER_ON and
4727               idx < len(self.nodes) - 1):
4728             time.sleep(self.op.power_delay)
4729
4730     return ret
4731
4732   def _CheckPayload(self, result):
4733     """Checks if the payload is valid.
4734
4735     @param result: RPC result
4736     @raises errors.OpExecError: If payload is not valid
4737
4738     """
4739     errs = []
4740     if self.op.command == constants.OOB_HEALTH:
4741       if not isinstance(result.payload, list):
4742         errs.append("command 'health' is expected to return a list but got %s" %
4743                     type(result.payload))
4744       else:
4745         for item, status in result.payload:
4746           if status not in constants.OOB_STATUSES:
4747             errs.append("health item '%s' has invalid status '%s'" %
4748                         (item, status))
4749
4750     if self.op.command == constants.OOB_POWER_STATUS:
4751       if not isinstance(result.payload, dict):
4752         errs.append("power-status is expected to return a dict but got %s" %
4753                     type(result.payload))
4754
4755     if self.op.command in [
4756         constants.OOB_POWER_ON,
4757         constants.OOB_POWER_OFF,
4758         constants.OOB_POWER_CYCLE,
4759         ]:
4760       if result.payload is not None:
4761         errs.append("%s is expected to not return payload but got '%s'" %
4762                     (self.op.command, result.payload))
4763
4764     if errs:
4765       raise errors.OpExecError("Check of out-of-band payload failed due to %s" %
4766                                utils.CommaJoin(errs))
4767
4768
4769 class _OsQuery(_QueryBase):
4770   FIELDS = query.OS_FIELDS
4771
4772   def ExpandNames(self, lu):
4773     # Lock all nodes in shared mode
4774     # Temporary removal of locks, should be reverted later
4775     # TODO: reintroduce locks when they are lighter-weight
4776     lu.needed_locks = {}
4777     #self.share_locks[locking.LEVEL_NODE] = 1
4778     #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
4779
4780     # The following variables interact with _QueryBase._GetNames
4781     if self.names:
4782       self.wanted = self.names
4783     else:
4784       self.wanted = locking.ALL_SET
4785
4786     self.do_locking = self.use_locking
4787
4788   def DeclareLocks(self, lu, level):
4789     pass
4790
4791   @staticmethod
4792   def _DiagnoseByOS(rlist):
4793     """Remaps a per-node return list into an a per-os per-node dictionary
4794
4795     @param rlist: a map with node names as keys and OS objects as values
4796
4797     @rtype: dict
4798     @return: a dictionary with osnames as keys and as value another
4799         map, with nodes as keys and tuples of (path, status, diagnose,
4800         variants, parameters, api_versions) as values, eg::
4801
4802           {"debian-etch": {"node1": [(/usr/lib/..., True, "", [], []),
4803                                      (/srv/..., False, "invalid api")],
4804                            "node2": [(/srv/..., True, "", [], [])]}
4805           }
4806
4807     """
4808     all_os = {}
4809     # we build here the list of nodes that didn't fail the RPC (at RPC
4810     # level), so that nodes with a non-responding node daemon don't
4811     # make all OSes invalid
4812     good_nodes = [node_name for node_name in rlist
4813                   if not rlist[node_name].fail_msg]
4814     for node_name, nr in rlist.items():
4815       if nr.fail_msg or not nr.payload:
4816         continue
4817       for (name, path, status, diagnose, variants,
4818            params, api_versions) in nr.payload:
4819         if name not in all_os:
4820           # build a list of nodes for this os containing empty lists
4821           # for each node in node_list
4822           all_os[name] = {}
4823           for nname in good_nodes:
4824             all_os[name][nname] = []
4825         # convert params from [name, help] to (name, help)
4826         params = [tuple(v) for v in params]
4827         all_os[name][node_name].append((path, status, diagnose,
4828                                         variants, params, api_versions))
4829     return all_os
4830
4831   def _GetQueryData(self, lu):
4832     """Computes the list of nodes and their attributes.
4833
4834     """
4835     # Locking is not used
4836     assert not (compat.any(lu.glm.is_owned(level)
4837                            for level in locking.LEVELS
4838                            if level != locking.LEVEL_CLUSTER) or
4839                 self.do_locking or self.use_locking)
4840
4841     valid_nodes = [node.name
4842                    for node in lu.cfg.GetAllNodesInfo().values()
4843                    if not node.offline and node.vm_capable]
4844     pol = self._DiagnoseByOS(lu.rpc.call_os_diagnose(valid_nodes))
4845     cluster = lu.cfg.GetClusterInfo()
4846
4847     data = {}
4848
4849     for (os_name, os_data) in pol.items():
4850       info = query.OsInfo(name=os_name, valid=True, node_status=os_data,
4851                           hidden=(os_name in cluster.hidden_os),
4852                           blacklisted=(os_name in cluster.blacklisted_os))
4853
4854       variants = set()
4855       parameters = set()
4856       api_versions = set()
4857
4858       for idx, osl in enumerate(os_data.values()):
4859         info.valid = bool(info.valid and osl and osl[0][1])
4860         if not info.valid:
4861           break
4862
4863         (node_variants, node_params, node_api) = osl[0][3:6]
4864         if idx == 0:
4865           # First entry
4866           variants.update(node_variants)
4867           parameters.update(node_params)
4868           api_versions.update(node_api)
4869         else:
4870           # Filter out inconsistent values
4871           variants.intersection_update(node_variants)
4872           parameters.intersection_update(node_params)
4873           api_versions.intersection_update(node_api)
4874
4875       info.variants = list(variants)
4876       info.parameters = list(parameters)
4877       info.api_versions = list(api_versions)
4878
4879       data[os_name] = info
4880
4881     # Prepare data in requested order
4882     return [data[name] for name in self._GetNames(lu, pol.keys(), None)
4883             if name in data]
4884
4885
4886 class LUOsDiagnose(NoHooksLU):
4887   """Logical unit for OS diagnose/query.
4888
4889   """
4890   REQ_BGL = False
4891
4892   @staticmethod
4893   def _BuildFilter(fields, names):
4894     """Builds a filter for querying OSes.
4895
4896     """
4897     name_filter = qlang.MakeSimpleFilter("name", names)
4898
4899     # Legacy behaviour: Hide hidden, blacklisted or invalid OSes if the
4900     # respective field is not requested
4901     status_filter = [[qlang.OP_NOT, [qlang.OP_TRUE, fname]]
4902                      for fname in ["hidden", "blacklisted"]
4903                      if fname not in fields]
4904     if "valid" not in fields:
4905       status_filter.append([qlang.OP_TRUE, "valid"])
4906
4907     if status_filter:
4908       status_filter.insert(0, qlang.OP_AND)
4909     else:
4910       status_filter = None
4911
4912     if name_filter and status_filter:
4913       return [qlang.OP_AND, name_filter, status_filter]
4914     elif name_filter:
4915       return name_filter
4916     else:
4917       return status_filter
4918
4919   def CheckArguments(self):
4920     self.oq = _OsQuery(self._BuildFilter(self.op.output_fields, self.op.names),
4921                        self.op.output_fields, False)
4922
4923   def ExpandNames(self):
4924     self.oq.ExpandNames(self)
4925
4926   def Exec(self, feedback_fn):
4927     return self.oq.OldStyleQuery(self)
4928
4929
4930 class LUNodeRemove(LogicalUnit):
4931   """Logical unit for removing a node.
4932
4933   """
4934   HPATH = "node-remove"
4935   HTYPE = constants.HTYPE_NODE
4936
4937   def BuildHooksEnv(self):
4938     """Build hooks env.
4939
4940     """
4941     return {
4942       "OP_TARGET": self.op.node_name,
4943       "NODE_NAME": self.op.node_name,
4944       }
4945
4946   def BuildHooksNodes(self):
4947     """Build hooks nodes.
4948
4949     This doesn't run on the target node in the pre phase as a failed
4950     node would then be impossible to remove.
4951
4952     """
4953     all_nodes = self.cfg.GetNodeList()
4954     try:
4955       all_nodes.remove(self.op.node_name)
4956     except ValueError:
4957       pass
4958     return (all_nodes, all_nodes)
4959
4960   def CheckPrereq(self):
4961     """Check prerequisites.
4962
4963     This checks:
4964      - the node exists in the configuration
4965      - it does not have primary or secondary instances
4966      - it's not the master
4967
4968     Any errors are signaled by raising errors.OpPrereqError.
4969
4970     """
4971     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
4972     node = self.cfg.GetNodeInfo(self.op.node_name)
4973     assert node is not None
4974
4975     masternode = self.cfg.GetMasterNode()
4976     if node.name == masternode:
4977       raise errors.OpPrereqError("Node is the master node, failover to another"
4978                                  " node is required", errors.ECODE_INVAL)
4979
4980     for instance_name, instance in self.cfg.GetAllInstancesInfo().items():
4981       if node.name in instance.all_nodes:
4982         raise errors.OpPrereqError("Instance %s is still running on the node,"
4983                                    " please remove first" % instance_name,
4984                                    errors.ECODE_INVAL)
4985     self.op.node_name = node.name
4986     self.node = node
4987
4988   def Exec(self, feedback_fn):
4989     """Removes the node from the cluster.
4990
4991     """
4992     node = self.node
4993     logging.info("Stopping the node daemon and removing configs from node %s",
4994                  node.name)
4995
4996     modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
4997
4998     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER), \
4999       "Not owning BGL"
5000
5001     # Promote nodes to master candidate as needed
5002     _AdjustCandidatePool(self, exceptions=[node.name])
5003     self.context.RemoveNode(node.name)
5004
5005     # Run post hooks on the node before it's removed
5006     _RunPostHook(self, node.name)
5007
5008     result = self.rpc.call_node_leave_cluster(node.name, modify_ssh_setup)
5009     msg = result.fail_msg
5010     if msg:
5011       self.LogWarning("Errors encountered on the remote node while leaving"
5012                       " the cluster: %s", msg)
5013
5014     # Remove node from our /etc/hosts
5015     if self.cfg.GetClusterInfo().modify_etc_hosts:
5016       master_node = self.cfg.GetMasterNode()
5017       result = self.rpc.call_etc_hosts_modify(master_node,
5018                                               constants.ETC_HOSTS_REMOVE,
5019                                               node.name, None)
5020       result.Raise("Can't update hosts file with new host data")
5021       _RedistributeAncillaryFiles(self)
5022
5023
5024 class _NodeQuery(_QueryBase):
5025   FIELDS = query.NODE_FIELDS
5026
5027   def ExpandNames(self, lu):
5028     lu.needed_locks = {}
5029     lu.share_locks = _ShareAll()
5030
5031     if self.names:
5032       self.wanted = _GetWantedNodes(lu, self.names)
5033     else:
5034       self.wanted = locking.ALL_SET
5035
5036     self.do_locking = (self.use_locking and
5037                        query.NQ_LIVE in self.requested_data)
5038
5039     if self.do_locking:
5040       # If any non-static field is requested we need to lock the nodes
5041       lu.needed_locks[locking.LEVEL_NODE] = self.wanted
5042
5043   def DeclareLocks(self, lu, level):
5044     pass
5045
5046   def _GetQueryData(self, lu):
5047     """Computes the list of nodes and their attributes.
5048
5049     """
5050     all_info = lu.cfg.GetAllNodesInfo()
5051
5052     nodenames = self._GetNames(lu, all_info.keys(), locking.LEVEL_NODE)
5053
5054     # Gather data as requested
5055     if query.NQ_LIVE in self.requested_data:
5056       # filter out non-vm_capable nodes
5057       toquery_nodes = [name for name in nodenames if all_info[name].vm_capable]
5058
5059       node_data = lu.rpc.call_node_info(toquery_nodes, [lu.cfg.GetVGName()],
5060                                         [lu.cfg.GetHypervisorType()])
5061       live_data = dict((name, _MakeLegacyNodeInfo(nresult.payload))
5062                        for (name, nresult) in node_data.items()
5063                        if not nresult.fail_msg and nresult.payload)
5064     else:
5065       live_data = None
5066
5067     if query.NQ_INST in self.requested_data:
5068       node_to_primary = dict([(name, set()) for name in nodenames])
5069       node_to_secondary = dict([(name, set()) for name in nodenames])
5070
5071       inst_data = lu.cfg.GetAllInstancesInfo()
5072
5073       for inst in inst_data.values():
5074         if inst.primary_node in node_to_primary:
5075           node_to_primary[inst.primary_node].add(inst.name)
5076         for secnode in inst.secondary_nodes:
5077           if secnode in node_to_secondary:
5078             node_to_secondary[secnode].add(inst.name)
5079     else:
5080       node_to_primary = None
5081       node_to_secondary = None
5082
5083     if query.NQ_OOB in self.requested_data:
5084       oob_support = dict((name, bool(_SupportsOob(lu.cfg, node)))
5085                          for name, node in all_info.iteritems())
5086     else:
5087       oob_support = None
5088
5089     if query.NQ_GROUP in self.requested_data:
5090       groups = lu.cfg.GetAllNodeGroupsInfo()
5091     else:
5092       groups = {}
5093
5094     return query.NodeQueryData([all_info[name] for name in nodenames],
5095                                live_data, lu.cfg.GetMasterNode(),
5096                                node_to_primary, node_to_secondary, groups,
5097                                oob_support, lu.cfg.GetClusterInfo())
5098
5099
5100 class LUNodeQuery(NoHooksLU):
5101   """Logical unit for querying nodes.
5102
5103   """
5104   # pylint: disable=W0142
5105   REQ_BGL = False
5106
5107   def CheckArguments(self):
5108     self.nq = _NodeQuery(qlang.MakeSimpleFilter("name", self.op.names),
5109                          self.op.output_fields, self.op.use_locking)
5110
5111   def ExpandNames(self):
5112     self.nq.ExpandNames(self)
5113
5114   def DeclareLocks(self, level):
5115     self.nq.DeclareLocks(self, level)
5116
5117   def Exec(self, feedback_fn):
5118     return self.nq.OldStyleQuery(self)
5119
5120
5121 class LUNodeQueryvols(NoHooksLU):
5122   """Logical unit for getting volumes on node(s).
5123
5124   """
5125   REQ_BGL = False
5126   _FIELDS_DYNAMIC = utils.FieldSet("phys", "vg", "name", "size", "instance")
5127   _FIELDS_STATIC = utils.FieldSet("node")
5128
5129   def CheckArguments(self):
5130     _CheckOutputFields(static=self._FIELDS_STATIC,
5131                        dynamic=self._FIELDS_DYNAMIC,
5132                        selected=self.op.output_fields)
5133
5134   def ExpandNames(self):
5135     self.share_locks = _ShareAll()
5136     self.needed_locks = {}
5137
5138     if not self.op.nodes:
5139       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5140     else:
5141       self.needed_locks[locking.LEVEL_NODE] = \
5142         _GetWantedNodes(self, self.op.nodes)
5143
5144   def Exec(self, feedback_fn):
5145     """Computes the list of nodes and their attributes.
5146
5147     """
5148     nodenames = self.owned_locks(locking.LEVEL_NODE)
5149     volumes = self.rpc.call_node_volumes(nodenames)
5150
5151     ilist = self.cfg.GetAllInstancesInfo()
5152     vol2inst = _MapInstanceDisksToNodes(ilist.values())
5153
5154     output = []
5155     for node in nodenames:
5156       nresult = volumes[node]
5157       if nresult.offline:
5158         continue
5159       msg = nresult.fail_msg
5160       if msg:
5161         self.LogWarning("Can't compute volume data on node %s: %s", node, msg)
5162         continue
5163
5164       node_vols = sorted(nresult.payload,
5165                          key=operator.itemgetter("dev"))
5166
5167       for vol in node_vols:
5168         node_output = []
5169         for field in self.op.output_fields:
5170           if field == "node":
5171             val = node
5172           elif field == "phys":
5173             val = vol["dev"]
5174           elif field == "vg":
5175             val = vol["vg"]
5176           elif field == "name":
5177             val = vol["name"]
5178           elif field == "size":
5179             val = int(float(vol["size"]))
5180           elif field == "instance":
5181             val = vol2inst.get((node, vol["vg"] + "/" + vol["name"]), "-")
5182           else:
5183             raise errors.ParameterError(field)
5184           node_output.append(str(val))
5185
5186         output.append(node_output)
5187
5188     return output
5189
5190
5191 class LUNodeQueryStorage(NoHooksLU):
5192   """Logical unit for getting information on storage units on node(s).
5193
5194   """
5195   _FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
5196   REQ_BGL = False
5197
5198   def CheckArguments(self):
5199     _CheckOutputFields(static=self._FIELDS_STATIC,
5200                        dynamic=utils.FieldSet(*constants.VALID_STORAGE_FIELDS),
5201                        selected=self.op.output_fields)
5202
5203   def ExpandNames(self):
5204     self.share_locks = _ShareAll()
5205     self.needed_locks = {}
5206
5207     if self.op.nodes:
5208       self.needed_locks[locking.LEVEL_NODE] = \
5209         _GetWantedNodes(self, self.op.nodes)
5210     else:
5211       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5212
5213   def Exec(self, feedback_fn):
5214     """Computes the list of nodes and their attributes.
5215
5216     """
5217     self.nodes = self.owned_locks(locking.LEVEL_NODE)
5218
5219     # Always get name to sort by
5220     if constants.SF_NAME in self.op.output_fields:
5221       fields = self.op.output_fields[:]
5222     else:
5223       fields = [constants.SF_NAME] + self.op.output_fields
5224
5225     # Never ask for node or type as it's only known to the LU
5226     for extra in [constants.SF_NODE, constants.SF_TYPE]:
5227       while extra in fields:
5228         fields.remove(extra)
5229
5230     field_idx = dict([(name, idx) for (idx, name) in enumerate(fields)])
5231     name_idx = field_idx[constants.SF_NAME]
5232
5233     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
5234     data = self.rpc.call_storage_list(self.nodes,
5235                                       self.op.storage_type, st_args,
5236                                       self.op.name, fields)
5237
5238     result = []
5239
5240     for node in utils.NiceSort(self.nodes):
5241       nresult = data[node]
5242       if nresult.offline:
5243         continue
5244
5245       msg = nresult.fail_msg
5246       if msg:
5247         self.LogWarning("Can't get storage data from node %s: %s", node, msg)
5248         continue
5249
5250       rows = dict([(row[name_idx], row) for row in nresult.payload])
5251
5252       for name in utils.NiceSort(rows.keys()):
5253         row = rows[name]
5254
5255         out = []
5256
5257         for field in self.op.output_fields:
5258           if field == constants.SF_NODE:
5259             val = node
5260           elif field == constants.SF_TYPE:
5261             val = self.op.storage_type
5262           elif field in field_idx:
5263             val = row[field_idx[field]]
5264           else:
5265             raise errors.ParameterError(field)
5266
5267           out.append(val)
5268
5269         result.append(out)
5270
5271     return result
5272
5273
5274 class _InstanceQuery(_QueryBase):
5275   FIELDS = query.INSTANCE_FIELDS
5276
5277   def ExpandNames(self, lu):
5278     lu.needed_locks = {}
5279     lu.share_locks = _ShareAll()
5280
5281     if self.names:
5282       self.wanted = _GetWantedInstances(lu, self.names)
5283     else:
5284       self.wanted = locking.ALL_SET
5285
5286     self.do_locking = (self.use_locking and
5287                        query.IQ_LIVE in self.requested_data)
5288     if self.do_locking:
5289       lu.needed_locks[locking.LEVEL_INSTANCE] = self.wanted
5290       lu.needed_locks[locking.LEVEL_NODEGROUP] = []
5291       lu.needed_locks[locking.LEVEL_NODE] = []
5292       lu.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
5293
5294     self.do_grouplocks = (self.do_locking and
5295                           query.IQ_NODES in self.requested_data)
5296
5297   def DeclareLocks(self, lu, level):
5298     if self.do_locking:
5299       if level == locking.LEVEL_NODEGROUP and self.do_grouplocks:
5300         assert not lu.needed_locks[locking.LEVEL_NODEGROUP]
5301
5302         # Lock all groups used by instances optimistically; this requires going
5303         # via the node before it's locked, requiring verification later on
5304         lu.needed_locks[locking.LEVEL_NODEGROUP] = \
5305           set(group_uuid
5306               for instance_name in lu.owned_locks(locking.LEVEL_INSTANCE)
5307               for group_uuid in lu.cfg.GetInstanceNodeGroups(instance_name))
5308       elif level == locking.LEVEL_NODE:
5309         lu._LockInstancesNodes() # pylint: disable=W0212
5310
5311   @staticmethod
5312   def _CheckGroupLocks(lu):
5313     owned_instances = frozenset(lu.owned_locks(locking.LEVEL_INSTANCE))
5314     owned_groups = frozenset(lu.owned_locks(locking.LEVEL_NODEGROUP))
5315
5316     # Check if node groups for locked instances are still correct
5317     for instance_name in owned_instances:
5318       _CheckInstanceNodeGroups(lu.cfg, instance_name, owned_groups)
5319
5320   def _GetQueryData(self, lu):
5321     """Computes the list of instances and their attributes.
5322
5323     """
5324     if self.do_grouplocks:
5325       self._CheckGroupLocks(lu)
5326
5327     cluster = lu.cfg.GetClusterInfo()
5328     all_info = lu.cfg.GetAllInstancesInfo()
5329
5330     instance_names = self._GetNames(lu, all_info.keys(), locking.LEVEL_INSTANCE)
5331
5332     instance_list = [all_info[name] for name in instance_names]
5333     nodes = frozenset(itertools.chain(*(inst.all_nodes
5334                                         for inst in instance_list)))
5335     hv_list = list(set([inst.hypervisor for inst in instance_list]))
5336     bad_nodes = []
5337     offline_nodes = []
5338     wrongnode_inst = set()
5339
5340     # Gather data as requested
5341     if self.requested_data & set([query.IQ_LIVE, query.IQ_CONSOLE]):
5342       live_data = {}
5343       node_data = lu.rpc.call_all_instances_info(nodes, hv_list)
5344       for name in nodes:
5345         result = node_data[name]
5346         if result.offline:
5347           # offline nodes will be in both lists
5348           assert result.fail_msg
5349           offline_nodes.append(name)
5350         if result.fail_msg:
5351           bad_nodes.append(name)
5352         elif result.payload:
5353           for inst in result.payload:
5354             if inst in all_info:
5355               if all_info[inst].primary_node == name:
5356                 live_data.update(result.payload)
5357               else:
5358                 wrongnode_inst.add(inst)
5359             else:
5360               # orphan instance; we don't list it here as we don't
5361               # handle this case yet in the output of instance listing
5362               logging.warning("Orphan instance '%s' found on node %s",
5363                               inst, name)
5364         # else no instance is alive
5365     else:
5366       live_data = {}
5367
5368     if query.IQ_DISKUSAGE in self.requested_data:
5369       disk_usage = dict((inst.name,
5370                          _ComputeDiskSize(inst.disk_template,
5371                                           [{constants.IDISK_SIZE: disk.size}
5372                                            for disk in inst.disks]))
5373                         for inst in instance_list)
5374     else:
5375       disk_usage = None
5376
5377     if query.IQ_CONSOLE in self.requested_data:
5378       consinfo = {}
5379       for inst in instance_list:
5380         if inst.name in live_data:
5381           # Instance is running
5382           consinfo[inst.name] = _GetInstanceConsole(cluster, inst)
5383         else:
5384           consinfo[inst.name] = None
5385       assert set(consinfo.keys()) == set(instance_names)
5386     else:
5387       consinfo = None
5388
5389     if query.IQ_NODES in self.requested_data:
5390       node_names = set(itertools.chain(*map(operator.attrgetter("all_nodes"),
5391                                             instance_list)))
5392       nodes = dict(lu.cfg.GetMultiNodeInfo(node_names))
5393       groups = dict((uuid, lu.cfg.GetNodeGroup(uuid))
5394                     for uuid in set(map(operator.attrgetter("group"),
5395                                         nodes.values())))
5396     else:
5397       nodes = None
5398       groups = None
5399
5400     return query.InstanceQueryData(instance_list, lu.cfg.GetClusterInfo(),
5401                                    disk_usage, offline_nodes, bad_nodes,
5402                                    live_data, wrongnode_inst, consinfo,
5403                                    nodes, groups)
5404
5405
5406 class LUQuery(NoHooksLU):
5407   """Query for resources/items of a certain kind.
5408
5409   """
5410   # pylint: disable=W0142
5411   REQ_BGL = False
5412
5413   def CheckArguments(self):
5414     qcls = _GetQueryImplementation(self.op.what)
5415
5416     self.impl = qcls(self.op.qfilter, self.op.fields, self.op.use_locking)
5417
5418   def ExpandNames(self):
5419     self.impl.ExpandNames(self)
5420
5421   def DeclareLocks(self, level):
5422     self.impl.DeclareLocks(self, level)
5423
5424   def Exec(self, feedback_fn):
5425     return self.impl.NewStyleQuery(self)
5426
5427
5428 class LUQueryFields(NoHooksLU):
5429   """Query for resources/items of a certain kind.
5430
5431   """
5432   # pylint: disable=W0142
5433   REQ_BGL = False
5434
5435   def CheckArguments(self):
5436     self.qcls = _GetQueryImplementation(self.op.what)
5437
5438   def ExpandNames(self):
5439     self.needed_locks = {}
5440
5441   def Exec(self, feedback_fn):
5442     return query.QueryFields(self.qcls.FIELDS, self.op.fields)
5443
5444
5445 class LUNodeModifyStorage(NoHooksLU):
5446   """Logical unit for modifying a storage volume on a node.
5447
5448   """
5449   REQ_BGL = False
5450
5451   def CheckArguments(self):
5452     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5453
5454     storage_type = self.op.storage_type
5455
5456     try:
5457       modifiable = constants.MODIFIABLE_STORAGE_FIELDS[storage_type]
5458     except KeyError:
5459       raise errors.OpPrereqError("Storage units of type '%s' can not be"
5460                                  " modified" % storage_type,
5461                                  errors.ECODE_INVAL)
5462
5463     diff = set(self.op.changes.keys()) - modifiable
5464     if diff:
5465       raise errors.OpPrereqError("The following fields can not be modified for"
5466                                  " storage units of type '%s': %r" %
5467                                  (storage_type, list(diff)),
5468                                  errors.ECODE_INVAL)
5469
5470   def ExpandNames(self):
5471     self.needed_locks = {
5472       locking.LEVEL_NODE: self.op.node_name,
5473       }
5474
5475   def Exec(self, feedback_fn):
5476     """Computes the list of nodes and their attributes.
5477
5478     """
5479     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
5480     result = self.rpc.call_storage_modify(self.op.node_name,
5481                                           self.op.storage_type, st_args,
5482                                           self.op.name, self.op.changes)
5483     result.Raise("Failed to modify storage unit '%s' on %s" %
5484                  (self.op.name, self.op.node_name))
5485
5486
5487 class LUNodeAdd(LogicalUnit):
5488   """Logical unit for adding node to the cluster.
5489
5490   """
5491   HPATH = "node-add"
5492   HTYPE = constants.HTYPE_NODE
5493   _NFLAGS = ["master_capable", "vm_capable"]
5494
5495   def CheckArguments(self):
5496     self.primary_ip_family = self.cfg.GetPrimaryIPFamily()
5497     # validate/normalize the node name
5498     self.hostname = netutils.GetHostname(name=self.op.node_name,
5499                                          family=self.primary_ip_family)
5500     self.op.node_name = self.hostname.name
5501
5502     if self.op.readd and self.op.node_name == self.cfg.GetMasterNode():
5503       raise errors.OpPrereqError("Cannot readd the master node",
5504                                  errors.ECODE_STATE)
5505
5506     if self.op.readd and self.op.group:
5507       raise errors.OpPrereqError("Cannot pass a node group when a node is"
5508                                  " being readded", errors.ECODE_INVAL)
5509
5510   def BuildHooksEnv(self):
5511     """Build hooks env.
5512
5513     This will run on all nodes before, and on all nodes + the new node after.
5514
5515     """
5516     return {
5517       "OP_TARGET": self.op.node_name,
5518       "NODE_NAME": self.op.node_name,
5519       "NODE_PIP": self.op.primary_ip,
5520       "NODE_SIP": self.op.secondary_ip,
5521       "MASTER_CAPABLE": str(self.op.master_capable),
5522       "VM_CAPABLE": str(self.op.vm_capable),
5523       }
5524
5525   def BuildHooksNodes(self):
5526     """Build hooks nodes.
5527
5528     """
5529     # Exclude added node
5530     pre_nodes = list(set(self.cfg.GetNodeList()) - set([self.op.node_name]))
5531     post_nodes = pre_nodes + [self.op.node_name, ]
5532
5533     return (pre_nodes, post_nodes)
5534
5535   def CheckPrereq(self):
5536     """Check prerequisites.
5537
5538     This checks:
5539      - the new node is not already in the config
5540      - it is resolvable
5541      - its parameters (single/dual homed) matches the cluster
5542
5543     Any errors are signaled by raising errors.OpPrereqError.
5544
5545     """
5546     cfg = self.cfg
5547     hostname = self.hostname
5548     node = hostname.name
5549     primary_ip = self.op.primary_ip = hostname.ip
5550     if self.op.secondary_ip is None:
5551       if self.primary_ip_family == netutils.IP6Address.family:
5552         raise errors.OpPrereqError("When using a IPv6 primary address, a valid"
5553                                    " IPv4 address must be given as secondary",
5554                                    errors.ECODE_INVAL)
5555       self.op.secondary_ip = primary_ip
5556
5557     secondary_ip = self.op.secondary_ip
5558     if not netutils.IP4Address.IsValid(secondary_ip):
5559       raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
5560                                  " address" % secondary_ip, errors.ECODE_INVAL)
5561
5562     node_list = cfg.GetNodeList()
5563     if not self.op.readd and node in node_list:
5564       raise errors.OpPrereqError("Node %s is already in the configuration" %
5565                                  node, errors.ECODE_EXISTS)
5566     elif self.op.readd and node not in node_list:
5567       raise errors.OpPrereqError("Node %s is not in the configuration" % node,
5568                                  errors.ECODE_NOENT)
5569
5570     self.changed_primary_ip = False
5571
5572     for existing_node_name, existing_node in cfg.GetMultiNodeInfo(node_list):
5573       if self.op.readd and node == existing_node_name:
5574         if existing_node.secondary_ip != secondary_ip:
5575           raise errors.OpPrereqError("Readded node doesn't have the same IP"
5576                                      " address configuration as before",
5577                                      errors.ECODE_INVAL)
5578         if existing_node.primary_ip != primary_ip:
5579           self.changed_primary_ip = True
5580
5581         continue
5582
5583       if (existing_node.primary_ip == primary_ip or
5584           existing_node.secondary_ip == primary_ip or
5585           existing_node.primary_ip == secondary_ip or
5586           existing_node.secondary_ip == secondary_ip):
5587         raise errors.OpPrereqError("New node ip address(es) conflict with"
5588                                    " existing node %s" % existing_node.name,
5589                                    errors.ECODE_NOTUNIQUE)
5590
5591     # After this 'if' block, None is no longer a valid value for the
5592     # _capable op attributes
5593     if self.op.readd:
5594       old_node = self.cfg.GetNodeInfo(node)
5595       assert old_node is not None, "Can't retrieve locked node %s" % node
5596       for attr in self._NFLAGS:
5597         if getattr(self.op, attr) is None:
5598           setattr(self.op, attr, getattr(old_node, attr))
5599     else:
5600       for attr in self._NFLAGS:
5601         if getattr(self.op, attr) is None:
5602           setattr(self.op, attr, True)
5603
5604     if self.op.readd and not self.op.vm_capable:
5605       pri, sec = cfg.GetNodeInstances(node)
5606       if pri or sec:
5607         raise errors.OpPrereqError("Node %s being re-added with vm_capable"
5608                                    " flag set to false, but it already holds"
5609                                    " instances" % node,
5610                                    errors.ECODE_STATE)
5611
5612     # check that the type of the node (single versus dual homed) is the
5613     # same as for the master
5614     myself = cfg.GetNodeInfo(self.cfg.GetMasterNode())
5615     master_singlehomed = myself.secondary_ip == myself.primary_ip
5616     newbie_singlehomed = secondary_ip == primary_ip
5617     if master_singlehomed != newbie_singlehomed:
5618       if master_singlehomed:
5619         raise errors.OpPrereqError("The master has no secondary ip but the"
5620                                    " new node has one",
5621                                    errors.ECODE_INVAL)
5622       else:
5623         raise errors.OpPrereqError("The master has a secondary ip but the"
5624                                    " new node doesn't have one",
5625                                    errors.ECODE_INVAL)
5626
5627     # checks reachability
5628     if not netutils.TcpPing(primary_ip, constants.DEFAULT_NODED_PORT):
5629       raise errors.OpPrereqError("Node not reachable by ping",
5630                                  errors.ECODE_ENVIRON)
5631
5632     if not newbie_singlehomed:
5633       # check reachability from my secondary ip to newbie's secondary ip
5634       if not netutils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
5635                            source=myself.secondary_ip):
5636         raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
5637                                    " based ping to node daemon port",
5638                                    errors.ECODE_ENVIRON)
5639
5640     if self.op.readd:
5641       exceptions = [node]
5642     else:
5643       exceptions = []
5644
5645     if self.op.master_capable:
5646       self.master_candidate = _DecideSelfPromotion(self, exceptions=exceptions)
5647     else:
5648       self.master_candidate = False
5649
5650     if self.op.readd:
5651       self.new_node = old_node
5652     else:
5653       node_group = cfg.LookupNodeGroup(self.op.group)
5654       self.new_node = objects.Node(name=node,
5655                                    primary_ip=primary_ip,
5656                                    secondary_ip=secondary_ip,
5657                                    master_candidate=self.master_candidate,
5658                                    offline=False, drained=False,
5659                                    group=node_group)
5660
5661     if self.op.ndparams:
5662       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
5663
5664     if self.op.hv_state:
5665       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None)
5666
5667     if self.op.disk_state:
5668       self.new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state, None)
5669
5670     # TODO: If we need to have multiple DnsOnlyRunner we probably should make
5671     #       it a property on the base class.
5672     result = rpc.DnsOnlyRunner().call_version([node])[node]
5673     result.Raise("Can't get version information from node %s" % node)
5674     if constants.PROTOCOL_VERSION == result.payload:
5675       logging.info("Communication to node %s fine, sw version %s match",
5676                    node, result.payload)
5677     else:
5678       raise errors.OpPrereqError("Version mismatch master version %s,"
5679                                  " node version %s" %
5680                                  (constants.PROTOCOL_VERSION, result.payload),
5681                                  errors.ECODE_ENVIRON)
5682
5683   def Exec(self, feedback_fn):
5684     """Adds the new node to the cluster.
5685
5686     """
5687     new_node = self.new_node
5688     node = new_node.name
5689
5690     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER), \
5691       "Not owning BGL"
5692
5693     # We adding a new node so we assume it's powered
5694     new_node.powered = True
5695
5696     # for re-adds, reset the offline/drained/master-candidate flags;
5697     # we need to reset here, otherwise offline would prevent RPC calls
5698     # later in the procedure; this also means that if the re-add
5699     # fails, we are left with a non-offlined, broken node
5700     if self.op.readd:
5701       new_node.drained = new_node.offline = False # pylint: disable=W0201
5702       self.LogInfo("Readding a node, the offline/drained flags were reset")
5703       # if we demote the node, we do cleanup later in the procedure
5704       new_node.master_candidate = self.master_candidate
5705       if self.changed_primary_ip:
5706         new_node.primary_ip = self.op.primary_ip
5707
5708     # copy the master/vm_capable flags
5709     for attr in self._NFLAGS:
5710       setattr(new_node, attr, getattr(self.op, attr))
5711
5712     # notify the user about any possible mc promotion
5713     if new_node.master_candidate:
5714       self.LogInfo("Node will be a master candidate")
5715
5716     if self.op.ndparams:
5717       new_node.ndparams = self.op.ndparams
5718     else:
5719       new_node.ndparams = {}
5720
5721     if self.op.hv_state:
5722       new_node.hv_state_static = self.new_hv_state
5723
5724     if self.op.disk_state:
5725       new_node.disk_state_static = self.new_disk_state
5726
5727     # Add node to our /etc/hosts, and add key to known_hosts
5728     if self.cfg.GetClusterInfo().modify_etc_hosts:
5729       master_node = self.cfg.GetMasterNode()
5730       result = self.rpc.call_etc_hosts_modify(master_node,
5731                                               constants.ETC_HOSTS_ADD,
5732                                               self.hostname.name,
5733                                               self.hostname.ip)
5734       result.Raise("Can't update hosts file with new host data")
5735
5736     if new_node.secondary_ip != new_node.primary_ip:
5737       _CheckNodeHasSecondaryIP(self, new_node.name, new_node.secondary_ip,
5738                                False)
5739
5740     node_verify_list = [self.cfg.GetMasterNode()]
5741     node_verify_param = {
5742       constants.NV_NODELIST: ([node], {}),
5743       # TODO: do a node-net-test as well?
5744     }
5745
5746     result = self.rpc.call_node_verify(node_verify_list, node_verify_param,
5747                                        self.cfg.GetClusterName())
5748     for verifier in node_verify_list:
5749       result[verifier].Raise("Cannot communicate with node %s" % verifier)
5750       nl_payload = result[verifier].payload[constants.NV_NODELIST]
5751       if nl_payload:
5752         for failed in nl_payload:
5753           feedback_fn("ssh/hostname verification failed"
5754                       " (checking from %s): %s" %
5755                       (verifier, nl_payload[failed]))
5756         raise errors.OpExecError("ssh/hostname verification failed")
5757
5758     if self.op.readd:
5759       _RedistributeAncillaryFiles(self)
5760       self.context.ReaddNode(new_node)
5761       # make sure we redistribute the config
5762       self.cfg.Update(new_node, feedback_fn)
5763       # and make sure the new node will not have old files around
5764       if not new_node.master_candidate:
5765         result = self.rpc.call_node_demote_from_mc(new_node.name)
5766         msg = result.fail_msg
5767         if msg:
5768           self.LogWarning("Node failed to demote itself from master"
5769                           " candidate status: %s" % msg)
5770     else:
5771       _RedistributeAncillaryFiles(self, additional_nodes=[node],
5772                                   additional_vm=self.op.vm_capable)
5773       self.context.AddNode(new_node, self.proc.GetECId())
5774
5775
5776 class LUNodeSetParams(LogicalUnit):
5777   """Modifies the parameters of a node.
5778
5779   @cvar _F2R: a dictionary from tuples of flags (mc, drained, offline)
5780       to the node role (as _ROLE_*)
5781   @cvar _R2F: a dictionary from node role to tuples of flags
5782   @cvar _FLAGS: a list of attribute names corresponding to the flags
5783
5784   """
5785   HPATH = "node-modify"
5786   HTYPE = constants.HTYPE_NODE
5787   REQ_BGL = False
5788   (_ROLE_CANDIDATE, _ROLE_DRAINED, _ROLE_OFFLINE, _ROLE_REGULAR) = range(4)
5789   _F2R = {
5790     (True, False, False): _ROLE_CANDIDATE,
5791     (False, True, False): _ROLE_DRAINED,
5792     (False, False, True): _ROLE_OFFLINE,
5793     (False, False, False): _ROLE_REGULAR,
5794     }
5795   _R2F = dict((v, k) for k, v in _F2R.items())
5796   _FLAGS = ["master_candidate", "drained", "offline"]
5797
5798   def CheckArguments(self):
5799     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5800     all_mods = [self.op.offline, self.op.master_candidate, self.op.drained,
5801                 self.op.master_capable, self.op.vm_capable,
5802                 self.op.secondary_ip, self.op.ndparams, self.op.hv_state,
5803                 self.op.disk_state]
5804     if all_mods.count(None) == len(all_mods):
5805       raise errors.OpPrereqError("Please pass at least one modification",
5806                                  errors.ECODE_INVAL)
5807     if all_mods.count(True) > 1:
5808       raise errors.OpPrereqError("Can't set the node into more than one"
5809                                  " state at the same time",
5810                                  errors.ECODE_INVAL)
5811
5812     # Boolean value that tells us whether we might be demoting from MC
5813     self.might_demote = (self.op.master_candidate == False or
5814                          self.op.offline == True or
5815                          self.op.drained == True or
5816                          self.op.master_capable == False)
5817
5818     if self.op.secondary_ip:
5819       if not netutils.IP4Address.IsValid(self.op.secondary_ip):
5820         raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
5821                                    " address" % self.op.secondary_ip,
5822                                    errors.ECODE_INVAL)
5823
5824     self.lock_all = self.op.auto_promote and self.might_demote
5825     self.lock_instances = self.op.secondary_ip is not None
5826
5827   def _InstanceFilter(self, instance):
5828     """Filter for getting affected instances.
5829
5830     """
5831     return (instance.disk_template in constants.DTS_INT_MIRROR and
5832             self.op.node_name in instance.all_nodes)
5833
5834   def ExpandNames(self):
5835     if self.lock_all:
5836       self.needed_locks = {locking.LEVEL_NODE: locking.ALL_SET}
5837     else:
5838       self.needed_locks = {locking.LEVEL_NODE: self.op.node_name}
5839
5840     # Since modifying a node can have severe effects on currently running
5841     # operations the resource lock is at least acquired in shared mode
5842     self.needed_locks[locking.LEVEL_NODE_RES] = \
5843       self.needed_locks[locking.LEVEL_NODE]
5844
5845     # Get node resource and instance locks in shared mode; they are not used
5846     # for anything but read-only access
5847     self.share_locks[locking.LEVEL_NODE_RES] = 1
5848     self.share_locks[locking.LEVEL_INSTANCE] = 1
5849
5850     if self.lock_instances:
5851       self.needed_locks[locking.LEVEL_INSTANCE] = \
5852         frozenset(self.cfg.GetInstancesInfoByFilter(self._InstanceFilter))
5853
5854   def BuildHooksEnv(self):
5855     """Build hooks env.
5856
5857     This runs on the master node.
5858
5859     """
5860     return {
5861       "OP_TARGET": self.op.node_name,
5862       "MASTER_CANDIDATE": str(self.op.master_candidate),
5863       "OFFLINE": str(self.op.offline),
5864       "DRAINED": str(self.op.drained),
5865       "MASTER_CAPABLE": str(self.op.master_capable),
5866       "VM_CAPABLE": str(self.op.vm_capable),
5867       }
5868
5869   def BuildHooksNodes(self):
5870     """Build hooks nodes.
5871
5872     """
5873     nl = [self.cfg.GetMasterNode(), self.op.node_name]
5874     return (nl, nl)
5875
5876   def CheckPrereq(self):
5877     """Check prerequisites.
5878
5879     This only checks the instance list against the existing names.
5880
5881     """
5882     node = self.node = self.cfg.GetNodeInfo(self.op.node_name)
5883
5884     if self.lock_instances:
5885       affected_instances = \
5886         self.cfg.GetInstancesInfoByFilter(self._InstanceFilter)
5887
5888       # Verify instance locks
5889       owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
5890       wanted_instances = frozenset(affected_instances.keys())
5891       if wanted_instances - owned_instances:
5892         raise errors.OpPrereqError("Instances affected by changing node %s's"
5893                                    " secondary IP address have changed since"
5894                                    " locks were acquired, wanted '%s', have"
5895                                    " '%s'; retry the operation" %
5896                                    (self.op.node_name,
5897                                     utils.CommaJoin(wanted_instances),
5898                                     utils.CommaJoin(owned_instances)),
5899                                    errors.ECODE_STATE)
5900     else:
5901       affected_instances = None
5902
5903     if (self.op.master_candidate is not None or
5904         self.op.drained is not None or
5905         self.op.offline is not None):
5906       # we can't change the master's node flags
5907       if self.op.node_name == self.cfg.GetMasterNode():
5908         raise errors.OpPrereqError("The master role can be changed"
5909                                    " only via master-failover",
5910                                    errors.ECODE_INVAL)
5911
5912     if self.op.master_candidate and not node.master_capable:
5913       raise errors.OpPrereqError("Node %s is not master capable, cannot make"
5914                                  " it a master candidate" % node.name,
5915                                  errors.ECODE_STATE)
5916
5917     if self.op.vm_capable == False:
5918       (ipri, isec) = self.cfg.GetNodeInstances(self.op.node_name)
5919       if ipri or isec:
5920         raise errors.OpPrereqError("Node %s hosts instances, cannot unset"
5921                                    " the vm_capable flag" % node.name,
5922                                    errors.ECODE_STATE)
5923
5924     if node.master_candidate and self.might_demote and not self.lock_all:
5925       assert not self.op.auto_promote, "auto_promote set but lock_all not"
5926       # check if after removing the current node, we're missing master
5927       # candidates
5928       (mc_remaining, mc_should, _) = \
5929           self.cfg.GetMasterCandidateStats(exceptions=[node.name])
5930       if mc_remaining < mc_should:
5931         raise errors.OpPrereqError("Not enough master candidates, please"
5932                                    " pass auto promote option to allow"
5933                                    " promotion (--auto-promote or RAPI"
5934                                    " auto_promote=True)", errors.ECODE_STATE)
5935
5936     self.old_flags = old_flags = (node.master_candidate,
5937                                   node.drained, node.offline)
5938     assert old_flags in self._F2R, "Un-handled old flags %s" % str(old_flags)
5939     self.old_role = old_role = self._F2R[old_flags]
5940
5941     # Check for ineffective changes
5942     for attr in self._FLAGS:
5943       if (getattr(self.op, attr) == False and getattr(node, attr) == False):
5944         self.LogInfo("Ignoring request to unset flag %s, already unset", attr)
5945         setattr(self.op, attr, None)
5946
5947     # Past this point, any flag change to False means a transition
5948     # away from the respective state, as only real changes are kept
5949
5950     # TODO: We might query the real power state if it supports OOB
5951     if _SupportsOob(self.cfg, node):
5952       if self.op.offline is False and not (node.powered or
5953                                            self.op.powered == True):
5954         raise errors.OpPrereqError(("Node %s needs to be turned on before its"
5955                                     " offline status can be reset") %
5956                                    self.op.node_name)
5957     elif self.op.powered is not None:
5958       raise errors.OpPrereqError(("Unable to change powered state for node %s"
5959                                   " as it does not support out-of-band"
5960                                   " handling") % self.op.node_name)
5961
5962     # If we're being deofflined/drained, we'll MC ourself if needed
5963     if (self.op.drained == False or self.op.offline == False or
5964         (self.op.master_capable and not node.master_capable)):
5965       if _DecideSelfPromotion(self):
5966         self.op.master_candidate = True
5967         self.LogInfo("Auto-promoting node to master candidate")
5968
5969     # If we're no longer master capable, we'll demote ourselves from MC
5970     if self.op.master_capable == False and node.master_candidate:
5971       self.LogInfo("Demoting from master candidate")
5972       self.op.master_candidate = False
5973
5974     # Compute new role
5975     assert [getattr(self.op, attr) for attr in self._FLAGS].count(True) <= 1
5976     if self.op.master_candidate:
5977       new_role = self._ROLE_CANDIDATE
5978     elif self.op.drained:
5979       new_role = self._ROLE_DRAINED
5980     elif self.op.offline:
5981       new_role = self._ROLE_OFFLINE
5982     elif False in [self.op.master_candidate, self.op.drained, self.op.offline]:
5983       # False is still in new flags, which means we're un-setting (the
5984       # only) True flag
5985       new_role = self._ROLE_REGULAR
5986     else: # no new flags, nothing, keep old role
5987       new_role = old_role
5988
5989     self.new_role = new_role
5990
5991     if old_role == self._ROLE_OFFLINE and new_role != old_role:
5992       # Trying to transition out of offline status
5993       result = self.rpc.call_version([node.name])[node.name]
5994       if result.fail_msg:
5995         raise errors.OpPrereqError("Node %s is being de-offlined but fails"
5996                                    " to report its version: %s" %
5997                                    (node.name, result.fail_msg),
5998                                    errors.ECODE_STATE)
5999       else:
6000         self.LogWarning("Transitioning node from offline to online state"
6001                         " without using re-add. Please make sure the node"
6002                         " is healthy!")
6003
6004     if self.op.secondary_ip:
6005       # Ok even without locking, because this can't be changed by any LU
6006       master = self.cfg.GetNodeInfo(self.cfg.GetMasterNode())
6007       master_singlehomed = master.secondary_ip == master.primary_ip
6008       if master_singlehomed and self.op.secondary_ip:
6009         raise errors.OpPrereqError("Cannot change the secondary ip on a single"
6010                                    " homed cluster", errors.ECODE_INVAL)
6011
6012       assert not (frozenset(affected_instances) -
6013                   self.owned_locks(locking.LEVEL_INSTANCE))
6014
6015       if node.offline:
6016         if affected_instances:
6017           raise errors.OpPrereqError("Cannot change secondary IP address:"
6018                                      " offline node has instances (%s)"
6019                                      " configured to use it" %
6020                                      utils.CommaJoin(affected_instances.keys()))
6021       else:
6022         # On online nodes, check that no instances are running, and that
6023         # the node has the new ip and we can reach it.
6024         for instance in affected_instances.values():
6025           _CheckInstanceState(self, instance, INSTANCE_DOWN,
6026                               msg="cannot change secondary ip")
6027
6028         _CheckNodeHasSecondaryIP(self, node.name, self.op.secondary_ip, True)
6029         if master.name != node.name:
6030           # check reachability from master secondary ip to new secondary ip
6031           if not netutils.TcpPing(self.op.secondary_ip,
6032                                   constants.DEFAULT_NODED_PORT,
6033                                   source=master.secondary_ip):
6034             raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
6035                                        " based ping to node daemon port",
6036                                        errors.ECODE_ENVIRON)
6037
6038     if self.op.ndparams:
6039       new_ndparams = _GetUpdatedParams(self.node.ndparams, self.op.ndparams)
6040       utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES)
6041       self.new_ndparams = new_ndparams
6042
6043     if self.op.hv_state:
6044       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
6045                                                  self.node.hv_state_static)
6046
6047     if self.op.disk_state:
6048       self.new_disk_state = \
6049         _MergeAndVerifyDiskState(self.op.disk_state,
6050                                  self.node.disk_state_static)
6051
6052   def Exec(self, feedback_fn):
6053     """Modifies a node.
6054
6055     """
6056     node = self.node
6057     old_role = self.old_role
6058     new_role = self.new_role
6059
6060     result = []
6061
6062     if self.op.ndparams:
6063       node.ndparams = self.new_ndparams
6064
6065     if self.op.powered is not None:
6066       node.powered = self.op.powered
6067
6068     if self.op.hv_state:
6069       node.hv_state_static = self.new_hv_state
6070
6071     if self.op.disk_state:
6072       node.disk_state_static = self.new_disk_state
6073
6074     for attr in ["master_capable", "vm_capable"]:
6075       val = getattr(self.op, attr)
6076       if val is not None:
6077         setattr(node, attr, val)
6078         result.append((attr, str(val)))
6079
6080     if new_role != old_role:
6081       # Tell the node to demote itself, if no longer MC and not offline
6082       if old_role == self._ROLE_CANDIDATE and new_role != self._ROLE_OFFLINE:
6083         msg = self.rpc.call_node_demote_from_mc(node.name).fail_msg
6084         if msg:
6085           self.LogWarning("Node failed to demote itself: %s", msg)
6086
6087       new_flags = self._R2F[new_role]
6088       for of, nf, desc in zip(self.old_flags, new_flags, self._FLAGS):
6089         if of != nf:
6090           result.append((desc, str(nf)))
6091       (node.master_candidate, node.drained, node.offline) = new_flags
6092
6093       # we locked all nodes, we adjust the CP before updating this node
6094       if self.lock_all:
6095         _AdjustCandidatePool(self, [node.name])
6096
6097     if self.op.secondary_ip:
6098       node.secondary_ip = self.op.secondary_ip
6099       result.append(("secondary_ip", self.op.secondary_ip))
6100
6101     # this will trigger configuration file update, if needed
6102     self.cfg.Update(node, feedback_fn)
6103
6104     # this will trigger job queue propagation or cleanup if the mc
6105     # flag changed
6106     if [old_role, new_role].count(self._ROLE_CANDIDATE) == 1:
6107       self.context.ReaddNode(node)
6108
6109     return result
6110
6111
6112 class LUNodePowercycle(NoHooksLU):
6113   """Powercycles a node.
6114
6115   """
6116   REQ_BGL = False
6117
6118   def CheckArguments(self):
6119     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
6120     if self.op.node_name == self.cfg.GetMasterNode() and not self.op.force:
6121       raise errors.OpPrereqError("The node is the master and the force"
6122                                  " parameter was not set",
6123                                  errors.ECODE_INVAL)
6124
6125   def ExpandNames(self):
6126     """Locking for PowercycleNode.
6127
6128     This is a last-resort option and shouldn't block on other
6129     jobs. Therefore, we grab no locks.
6130
6131     """
6132     self.needed_locks = {}
6133
6134   def Exec(self, feedback_fn):
6135     """Reboots a node.
6136
6137     """
6138     result = self.rpc.call_node_powercycle(self.op.node_name,
6139                                            self.cfg.GetHypervisorType())
6140     result.Raise("Failed to schedule the reboot")
6141     return result.payload
6142
6143
6144 class LUClusterQuery(NoHooksLU):
6145   """Query cluster configuration.
6146
6147   """
6148   REQ_BGL = False
6149
6150   def ExpandNames(self):
6151     self.needed_locks = {}
6152
6153   def Exec(self, feedback_fn):
6154     """Return cluster config.
6155
6156     """
6157     cluster = self.cfg.GetClusterInfo()
6158     os_hvp = {}
6159
6160     # Filter just for enabled hypervisors
6161     for os_name, hv_dict in cluster.os_hvp.items():
6162       os_hvp[os_name] = {}
6163       for hv_name, hv_params in hv_dict.items():
6164         if hv_name in cluster.enabled_hypervisors:
6165           os_hvp[os_name][hv_name] = hv_params
6166
6167     # Convert ip_family to ip_version
6168     primary_ip_version = constants.IP4_VERSION
6169     if cluster.primary_ip_family == netutils.IP6Address.family:
6170       primary_ip_version = constants.IP6_VERSION
6171
6172     result = {
6173       "software_version": constants.RELEASE_VERSION,
6174       "protocol_version": constants.PROTOCOL_VERSION,
6175       "config_version": constants.CONFIG_VERSION,
6176       "os_api_version": max(constants.OS_API_VERSIONS),
6177       "export_version": constants.EXPORT_VERSION,
6178       "architecture": runtime.GetArchInfo(),
6179       "name": cluster.cluster_name,
6180       "master": cluster.master_node,
6181       "default_hypervisor": cluster.primary_hypervisor,
6182       "enabled_hypervisors": cluster.enabled_hypervisors,
6183       "hvparams": dict([(hypervisor_name, cluster.hvparams[hypervisor_name])
6184                         for hypervisor_name in cluster.enabled_hypervisors]),
6185       "os_hvp": os_hvp,
6186       "beparams": cluster.beparams,
6187       "osparams": cluster.osparams,
6188       "ipolicy": cluster.ipolicy,
6189       "nicparams": cluster.nicparams,
6190       "ndparams": cluster.ndparams,
6191       "diskparams": cluster.diskparams,
6192       "candidate_pool_size": cluster.candidate_pool_size,
6193       "master_netdev": cluster.master_netdev,
6194       "master_netmask": cluster.master_netmask,
6195       "use_external_mip_script": cluster.use_external_mip_script,
6196       "volume_group_name": cluster.volume_group_name,
6197       "drbd_usermode_helper": cluster.drbd_usermode_helper,
6198       "file_storage_dir": cluster.file_storage_dir,
6199       "shared_file_storage_dir": cluster.shared_file_storage_dir,
6200       "maintain_node_health": cluster.maintain_node_health,
6201       "ctime": cluster.ctime,
6202       "mtime": cluster.mtime,
6203       "uuid": cluster.uuid,
6204       "tags": list(cluster.GetTags()),
6205       "uid_pool": cluster.uid_pool,
6206       "default_iallocator": cluster.default_iallocator,
6207       "reserved_lvs": cluster.reserved_lvs,
6208       "primary_ip_version": primary_ip_version,
6209       "prealloc_wipe_disks": cluster.prealloc_wipe_disks,
6210       "hidden_os": cluster.hidden_os,
6211       "blacklisted_os": cluster.blacklisted_os,
6212       }
6213
6214     return result
6215
6216
6217 class LUClusterConfigQuery(NoHooksLU):
6218   """Return configuration values.
6219
6220   """
6221   REQ_BGL = False
6222
6223   def CheckArguments(self):
6224     self.cq = _ClusterQuery(None, self.op.output_fields, False)
6225
6226   def ExpandNames(self):
6227     self.cq.ExpandNames(self)
6228
6229   def DeclareLocks(self, level):
6230     self.cq.DeclareLocks(self, level)
6231
6232   def Exec(self, feedback_fn):
6233     result = self.cq.OldStyleQuery(self)
6234
6235     assert len(result) == 1
6236
6237     return result[0]
6238
6239
6240 class _ClusterQuery(_QueryBase):
6241   FIELDS = query.CLUSTER_FIELDS
6242
6243   #: Do not sort (there is only one item)
6244   SORT_FIELD = None
6245
6246   def ExpandNames(self, lu):
6247     lu.needed_locks = {}
6248
6249     # The following variables interact with _QueryBase._GetNames
6250     self.wanted = locking.ALL_SET
6251     self.do_locking = self.use_locking
6252
6253     if self.do_locking:
6254       raise errors.OpPrereqError("Can not use locking for cluster queries",
6255                                  errors.ECODE_INVAL)
6256
6257   def DeclareLocks(self, lu, level):
6258     pass
6259
6260   def _GetQueryData(self, lu):
6261     """Computes the list of nodes and their attributes.
6262
6263     """
6264     # Locking is not used
6265     assert not (compat.any(lu.glm.is_owned(level)
6266                            for level in locking.LEVELS
6267                            if level != locking.LEVEL_CLUSTER) or
6268                 self.do_locking or self.use_locking)
6269
6270     if query.CQ_CONFIG in self.requested_data:
6271       cluster = lu.cfg.GetClusterInfo()
6272     else:
6273       cluster = NotImplemented
6274
6275     if query.CQ_QUEUE_DRAINED in self.requested_data:
6276       drain_flag = os.path.exists(constants.JOB_QUEUE_DRAIN_FILE)
6277     else:
6278       drain_flag = NotImplemented
6279
6280     if query.CQ_WATCHER_PAUSE in self.requested_data:
6281       watcher_pause = utils.ReadWatcherPauseFile(constants.WATCHER_PAUSEFILE)
6282     else:
6283       watcher_pause = NotImplemented
6284
6285     return query.ClusterQueryData(cluster, drain_flag, watcher_pause)
6286
6287
6288 class LUInstanceActivateDisks(NoHooksLU):
6289   """Bring up an instance's disks.
6290
6291   """
6292   REQ_BGL = False
6293
6294   def ExpandNames(self):
6295     self._ExpandAndLockInstance()
6296     self.needed_locks[locking.LEVEL_NODE] = []
6297     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6298
6299   def DeclareLocks(self, level):
6300     if level == locking.LEVEL_NODE:
6301       self._LockInstancesNodes()
6302
6303   def CheckPrereq(self):
6304     """Check prerequisites.
6305
6306     This checks that the instance is in the cluster.
6307
6308     """
6309     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6310     assert self.instance is not None, \
6311       "Cannot retrieve locked instance %s" % self.op.instance_name
6312     _CheckNodeOnline(self, self.instance.primary_node)
6313
6314   def Exec(self, feedback_fn):
6315     """Activate the disks.
6316
6317     """
6318     disks_ok, disks_info = \
6319               _AssembleInstanceDisks(self, self.instance,
6320                                      ignore_size=self.op.ignore_size)
6321     if not disks_ok:
6322       raise errors.OpExecError("Cannot activate block devices")
6323
6324     return disks_info
6325
6326
6327 def _AssembleInstanceDisks(lu, instance, disks=None, ignore_secondaries=False,
6328                            ignore_size=False):
6329   """Prepare the block devices for an instance.
6330
6331   This sets up the block devices on all nodes.
6332
6333   @type lu: L{LogicalUnit}
6334   @param lu: the logical unit on whose behalf we execute
6335   @type instance: L{objects.Instance}
6336   @param instance: the instance for whose disks we assemble
6337   @type disks: list of L{objects.Disk} or None
6338   @param disks: which disks to assemble (or all, if None)
6339   @type ignore_secondaries: boolean
6340   @param ignore_secondaries: if true, errors on secondary nodes
6341       won't result in an error return from the function
6342   @type ignore_size: boolean
6343   @param ignore_size: if true, the current known size of the disk
6344       will not be used during the disk activation, useful for cases
6345       when the size is wrong
6346   @return: False if the operation failed, otherwise a list of
6347       (host, instance_visible_name, node_visible_name)
6348       with the mapping from node devices to instance devices
6349
6350   """
6351   device_info = []
6352   disks_ok = True
6353   iname = instance.name
6354   disks = _ExpandCheckDisks(instance, disks)
6355
6356   # With the two passes mechanism we try to reduce the window of
6357   # opportunity for the race condition of switching DRBD to primary
6358   # before handshaking occured, but we do not eliminate it
6359
6360   # The proper fix would be to wait (with some limits) until the
6361   # connection has been made and drbd transitions from WFConnection
6362   # into any other network-connected state (Connected, SyncTarget,
6363   # SyncSource, etc.)
6364
6365   # 1st pass, assemble on all nodes in secondary mode
6366   for idx, inst_disk in enumerate(disks):
6367     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
6368       if ignore_size:
6369         node_disk = node_disk.Copy()
6370         node_disk.UnsetSize()
6371       lu.cfg.SetDiskID(node_disk, node)
6372       result = lu.rpc.call_blockdev_assemble(node, (node_disk, instance), iname,
6373                                              False, idx)
6374       msg = result.fail_msg
6375       if msg:
6376         lu.proc.LogWarning("Could not prepare block device %s on node %s"
6377                            " (is_primary=False, pass=1): %s",
6378                            inst_disk.iv_name, node, msg)
6379         if not ignore_secondaries:
6380           disks_ok = False
6381
6382   # FIXME: race condition on drbd migration to primary
6383
6384   # 2nd pass, do only the primary node
6385   for idx, inst_disk in enumerate(disks):
6386     dev_path = None
6387
6388     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
6389       if node != instance.primary_node:
6390         continue
6391       if ignore_size:
6392         node_disk = node_disk.Copy()
6393         node_disk.UnsetSize()
6394       lu.cfg.SetDiskID(node_disk, node)
6395       result = lu.rpc.call_blockdev_assemble(node, (node_disk, instance), iname,
6396                                              True, idx)
6397       msg = result.fail_msg
6398       if msg:
6399         lu.proc.LogWarning("Could not prepare block device %s on node %s"
6400                            " (is_primary=True, pass=2): %s",
6401                            inst_disk.iv_name, node, msg)
6402         disks_ok = False
6403       else:
6404         dev_path = result.payload
6405
6406     device_info.append((instance.primary_node, inst_disk.iv_name, dev_path))
6407
6408   # leave the disks configured for the primary node
6409   # this is a workaround that would be fixed better by
6410   # improving the logical/physical id handling
6411   for disk in disks:
6412     lu.cfg.SetDiskID(disk, instance.primary_node)
6413
6414   return disks_ok, device_info
6415
6416
6417 def _StartInstanceDisks(lu, instance, force):
6418   """Start the disks of an instance.
6419
6420   """
6421   disks_ok, _ = _AssembleInstanceDisks(lu, instance,
6422                                            ignore_secondaries=force)
6423   if not disks_ok:
6424     _ShutdownInstanceDisks(lu, instance)
6425     if force is not None and not force:
6426       lu.proc.LogWarning("", hint="If the message above refers to a"
6427                          " secondary node,"
6428                          " you can retry the operation using '--force'.")
6429     raise errors.OpExecError("Disk consistency error")
6430
6431
6432 class LUInstanceDeactivateDisks(NoHooksLU):
6433   """Shutdown an instance's disks.
6434
6435   """
6436   REQ_BGL = False
6437
6438   def ExpandNames(self):
6439     self._ExpandAndLockInstance()
6440     self.needed_locks[locking.LEVEL_NODE] = []
6441     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6442
6443   def DeclareLocks(self, level):
6444     if level == locking.LEVEL_NODE:
6445       self._LockInstancesNodes()
6446
6447   def CheckPrereq(self):
6448     """Check prerequisites.
6449
6450     This checks that the instance is in the cluster.
6451
6452     """
6453     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6454     assert self.instance is not None, \
6455       "Cannot retrieve locked instance %s" % self.op.instance_name
6456
6457   def Exec(self, feedback_fn):
6458     """Deactivate the disks
6459
6460     """
6461     instance = self.instance
6462     if self.op.force:
6463       _ShutdownInstanceDisks(self, instance)
6464     else:
6465       _SafeShutdownInstanceDisks(self, instance)
6466
6467
6468 def _SafeShutdownInstanceDisks(lu, instance, disks=None):
6469   """Shutdown block devices of an instance.
6470
6471   This function checks if an instance is running, before calling
6472   _ShutdownInstanceDisks.
6473
6474   """
6475   _CheckInstanceState(lu, instance, INSTANCE_DOWN, msg="cannot shutdown disks")
6476   _ShutdownInstanceDisks(lu, instance, disks=disks)
6477
6478
6479 def _ExpandCheckDisks(instance, disks):
6480   """Return the instance disks selected by the disks list
6481
6482   @type disks: list of L{objects.Disk} or None
6483   @param disks: selected disks
6484   @rtype: list of L{objects.Disk}
6485   @return: selected instance disks to act on
6486
6487   """
6488   if disks is None:
6489     return instance.disks
6490   else:
6491     if not set(disks).issubset(instance.disks):
6492       raise errors.ProgrammerError("Can only act on disks belonging to the"
6493                                    " target instance")
6494     return disks
6495
6496
6497 def _ShutdownInstanceDisks(lu, instance, disks=None, ignore_primary=False):
6498   """Shutdown block devices of an instance.
6499
6500   This does the shutdown on all nodes of the instance.
6501
6502   If the ignore_primary is false, errors on the primary node are
6503   ignored.
6504
6505   """
6506   all_result = True
6507   disks = _ExpandCheckDisks(instance, disks)
6508
6509   for disk in disks:
6510     for node, top_disk in disk.ComputeNodeTree(instance.primary_node):
6511       lu.cfg.SetDiskID(top_disk, node)
6512       result = lu.rpc.call_blockdev_shutdown(node, (top_disk, instance))
6513       msg = result.fail_msg
6514       if msg:
6515         lu.LogWarning("Could not shutdown block device %s on node %s: %s",
6516                       disk.iv_name, node, msg)
6517         if ((node == instance.primary_node and not ignore_primary) or
6518             (node != instance.primary_node and not result.offline)):
6519           all_result = False
6520   return all_result
6521
6522
6523 def _CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name):
6524   """Checks if a node has enough free memory.
6525
6526   This function check if a given node has the needed amount of free
6527   memory. In case the node has less memory or we cannot get the
6528   information from the node, this function raise an OpPrereqError
6529   exception.
6530
6531   @type lu: C{LogicalUnit}
6532   @param lu: a logical unit from which we get configuration data
6533   @type node: C{str}
6534   @param node: the node to check
6535   @type reason: C{str}
6536   @param reason: string to use in the error message
6537   @type requested: C{int}
6538   @param requested: the amount of memory in MiB to check for
6539   @type hypervisor_name: C{str}
6540   @param hypervisor_name: the hypervisor to ask for memory stats
6541   @rtype: integer
6542   @return: node current free memory
6543   @raise errors.OpPrereqError: if the node doesn't have enough memory, or
6544       we cannot check the node
6545
6546   """
6547   nodeinfo = lu.rpc.call_node_info([node], None, [hypervisor_name])
6548   nodeinfo[node].Raise("Can't get data from node %s" % node,
6549                        prereq=True, ecode=errors.ECODE_ENVIRON)
6550   (_, _, (hv_info, )) = nodeinfo[node].payload
6551
6552   free_mem = hv_info.get("memory_free", None)
6553   if not isinstance(free_mem, int):
6554     raise errors.OpPrereqError("Can't compute free memory on node %s, result"
6555                                " was '%s'" % (node, free_mem),
6556                                errors.ECODE_ENVIRON)
6557   if requested > free_mem:
6558     raise errors.OpPrereqError("Not enough memory on node %s for %s:"
6559                                " needed %s MiB, available %s MiB" %
6560                                (node, reason, requested, free_mem),
6561                                errors.ECODE_NORES)
6562   return free_mem
6563
6564
6565 def _CheckNodesFreeDiskPerVG(lu, nodenames, req_sizes):
6566   """Checks if nodes have enough free disk space in the all VGs.
6567
6568   This function check if all given nodes have the needed amount of
6569   free disk. In case any node has less disk or we cannot get the
6570   information from the node, this function raise an OpPrereqError
6571   exception.
6572
6573   @type lu: C{LogicalUnit}
6574   @param lu: a logical unit from which we get configuration data
6575   @type nodenames: C{list}
6576   @param nodenames: the list of node names to check
6577   @type req_sizes: C{dict}
6578   @param req_sizes: the hash of vg and corresponding amount of disk in
6579       MiB to check for
6580   @raise errors.OpPrereqError: if the node doesn't have enough disk,
6581       or we cannot check the node
6582
6583   """
6584   for vg, req_size in req_sizes.items():
6585     _CheckNodesFreeDiskOnVG(lu, nodenames, vg, req_size)
6586
6587
6588 def _CheckNodesFreeDiskOnVG(lu, nodenames, vg, requested):
6589   """Checks if nodes have enough free disk space in the specified VG.
6590
6591   This function check if all given nodes have the needed amount of
6592   free disk. In case any node has less disk or we cannot get the
6593   information from the node, this function raise an OpPrereqError
6594   exception.
6595
6596   @type lu: C{LogicalUnit}
6597   @param lu: a logical unit from which we get configuration data
6598   @type nodenames: C{list}
6599   @param nodenames: the list of node names to check
6600   @type vg: C{str}
6601   @param vg: the volume group to check
6602   @type requested: C{int}
6603   @param requested: the amount of disk in MiB to check for
6604   @raise errors.OpPrereqError: if the node doesn't have enough disk,
6605       or we cannot check the node
6606
6607   """
6608   nodeinfo = lu.rpc.call_node_info(nodenames, [vg], None)
6609   for node in nodenames:
6610     info = nodeinfo[node]
6611     info.Raise("Cannot get current information from node %s" % node,
6612                prereq=True, ecode=errors.ECODE_ENVIRON)
6613     (_, (vg_info, ), _) = info.payload
6614     vg_free = vg_info.get("vg_free", None)
6615     if not isinstance(vg_free, int):
6616       raise errors.OpPrereqError("Can't compute free disk space on node"
6617                                  " %s for vg %s, result was '%s'" %
6618                                  (node, vg, vg_free), errors.ECODE_ENVIRON)
6619     if requested > vg_free:
6620       raise errors.OpPrereqError("Not enough disk space on target node %s"
6621                                  " vg %s: required %d MiB, available %d MiB" %
6622                                  (node, vg, requested, vg_free),
6623                                  errors.ECODE_NORES)
6624
6625
6626 def _CheckNodesPhysicalCPUs(lu, nodenames, requested, hypervisor_name):
6627   """Checks if nodes have enough physical CPUs
6628
6629   This function checks if all given nodes have the needed number of
6630   physical CPUs. In case any node has less CPUs or we cannot get the
6631   information from the node, this function raises an OpPrereqError
6632   exception.
6633
6634   @type lu: C{LogicalUnit}
6635   @param lu: a logical unit from which we get configuration data
6636   @type nodenames: C{list}
6637   @param nodenames: the list of node names to check
6638   @type requested: C{int}
6639   @param requested: the minimum acceptable number of physical CPUs
6640   @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
6641       or we cannot check the node
6642
6643   """
6644   nodeinfo = lu.rpc.call_node_info(nodenames, None, [hypervisor_name])
6645   for node in nodenames:
6646     info = nodeinfo[node]
6647     info.Raise("Cannot get current information from node %s" % node,
6648                prereq=True, ecode=errors.ECODE_ENVIRON)
6649     (_, _, (hv_info, )) = info.payload
6650     num_cpus = hv_info.get("cpu_total", None)
6651     if not isinstance(num_cpus, int):
6652       raise errors.OpPrereqError("Can't compute the number of physical CPUs"
6653                                  " on node %s, result was '%s'" %
6654                                  (node, num_cpus), errors.ECODE_ENVIRON)
6655     if requested > num_cpus:
6656       raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
6657                                  "required" % (node, num_cpus, requested),
6658                                  errors.ECODE_NORES)
6659
6660
6661 class LUInstanceStartup(LogicalUnit):
6662   """Starts an instance.
6663
6664   """
6665   HPATH = "instance-start"
6666   HTYPE = constants.HTYPE_INSTANCE
6667   REQ_BGL = False
6668
6669   def CheckArguments(self):
6670     # extra beparams
6671     if self.op.beparams:
6672       # fill the beparams dict
6673       objects.UpgradeBeParams(self.op.beparams)
6674       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
6675
6676   def ExpandNames(self):
6677     self._ExpandAndLockInstance()
6678     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
6679
6680   def DeclareLocks(self, level):
6681     if level == locking.LEVEL_NODE_RES:
6682       self._LockInstancesNodes(primary_only=True, level=locking.LEVEL_NODE_RES)
6683
6684   def BuildHooksEnv(self):
6685     """Build hooks env.
6686
6687     This runs on master, primary and secondary nodes of the instance.
6688
6689     """
6690     env = {
6691       "FORCE": self.op.force,
6692       }
6693
6694     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
6695
6696     return env
6697
6698   def BuildHooksNodes(self):
6699     """Build hooks nodes.
6700
6701     """
6702     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6703     return (nl, nl)
6704
6705   def CheckPrereq(self):
6706     """Check prerequisites.
6707
6708     This checks that the instance is in the cluster.
6709
6710     """
6711     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6712     assert self.instance is not None, \
6713       "Cannot retrieve locked instance %s" % self.op.instance_name
6714
6715     # extra hvparams
6716     if self.op.hvparams:
6717       # check hypervisor parameter syntax (locally)
6718       cluster = self.cfg.GetClusterInfo()
6719       utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
6720       filled_hvp = cluster.FillHV(instance)
6721       filled_hvp.update(self.op.hvparams)
6722       hv_type = hypervisor.GetHypervisor(instance.hypervisor)
6723       hv_type.CheckParameterSyntax(filled_hvp)
6724       _CheckHVParams(self, instance.all_nodes, instance.hypervisor, filled_hvp)
6725
6726     _CheckInstanceState(self, instance, INSTANCE_ONLINE)
6727
6728     self.primary_offline = self.cfg.GetNodeInfo(instance.primary_node).offline
6729
6730     if self.primary_offline and self.op.ignore_offline_nodes:
6731       self.proc.LogWarning("Ignoring offline primary node")
6732
6733       if self.op.hvparams or self.op.beparams:
6734         self.proc.LogWarning("Overridden parameters are ignored")
6735     else:
6736       _CheckNodeOnline(self, instance.primary_node)
6737
6738       bep = self.cfg.GetClusterInfo().FillBE(instance)
6739       bep.update(self.op.beparams)
6740
6741       # check bridges existence
6742       _CheckInstanceBridgesExist(self, instance)
6743
6744       remote_info = self.rpc.call_instance_info(instance.primary_node,
6745                                                 instance.name,
6746                                                 instance.hypervisor)
6747       remote_info.Raise("Error checking node %s" % instance.primary_node,
6748                         prereq=True, ecode=errors.ECODE_ENVIRON)
6749       if not remote_info.payload: # not running already
6750         _CheckNodeFreeMemory(self, instance.primary_node,
6751                              "starting instance %s" % instance.name,
6752                              bep[constants.BE_MINMEM], instance.hypervisor)
6753
6754   def Exec(self, feedback_fn):
6755     """Start the instance.
6756
6757     """
6758     instance = self.instance
6759     force = self.op.force
6760
6761     if not self.op.no_remember:
6762       self.cfg.MarkInstanceUp(instance.name)
6763
6764     if self.primary_offline:
6765       assert self.op.ignore_offline_nodes
6766       self.proc.LogInfo("Primary node offline, marked instance as started")
6767     else:
6768       node_current = instance.primary_node
6769
6770       _StartInstanceDisks(self, instance, force)
6771
6772       result = \
6773         self.rpc.call_instance_start(node_current,
6774                                      (instance, self.op.hvparams,
6775                                       self.op.beparams),
6776                                      self.op.startup_paused)
6777       msg = result.fail_msg
6778       if msg:
6779         _ShutdownInstanceDisks(self, instance)
6780         raise errors.OpExecError("Could not start instance: %s" % msg)
6781
6782
6783 class LUInstanceReboot(LogicalUnit):
6784   """Reboot an instance.
6785
6786   """
6787   HPATH = "instance-reboot"
6788   HTYPE = constants.HTYPE_INSTANCE
6789   REQ_BGL = False
6790
6791   def ExpandNames(self):
6792     self._ExpandAndLockInstance()
6793
6794   def BuildHooksEnv(self):
6795     """Build hooks env.
6796
6797     This runs on master, primary and secondary nodes of the instance.
6798
6799     """
6800     env = {
6801       "IGNORE_SECONDARIES": self.op.ignore_secondaries,
6802       "REBOOT_TYPE": self.op.reboot_type,
6803       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
6804       }
6805
6806     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
6807
6808     return env
6809
6810   def BuildHooksNodes(self):
6811     """Build hooks nodes.
6812
6813     """
6814     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6815     return (nl, nl)
6816
6817   def CheckPrereq(self):
6818     """Check prerequisites.
6819
6820     This checks that the instance is in the cluster.
6821
6822     """
6823     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6824     assert self.instance is not None, \
6825       "Cannot retrieve locked instance %s" % self.op.instance_name
6826     _CheckInstanceState(self, instance, INSTANCE_ONLINE)
6827     _CheckNodeOnline(self, instance.primary_node)
6828
6829     # check bridges existence
6830     _CheckInstanceBridgesExist(self, instance)
6831
6832   def Exec(self, feedback_fn):
6833     """Reboot the instance.
6834
6835     """
6836     instance = self.instance
6837     ignore_secondaries = self.op.ignore_secondaries
6838     reboot_type = self.op.reboot_type
6839
6840     remote_info = self.rpc.call_instance_info(instance.primary_node,
6841                                               instance.name,
6842                                               instance.hypervisor)
6843     remote_info.Raise("Error checking node %s" % instance.primary_node)
6844     instance_running = bool(remote_info.payload)
6845
6846     node_current = instance.primary_node
6847
6848     if instance_running and reboot_type in [constants.INSTANCE_REBOOT_SOFT,
6849                                             constants.INSTANCE_REBOOT_HARD]:
6850       for disk in instance.disks:
6851         self.cfg.SetDiskID(disk, node_current)
6852       result = self.rpc.call_instance_reboot(node_current, instance,
6853                                              reboot_type,
6854                                              self.op.shutdown_timeout)
6855       result.Raise("Could not reboot instance")
6856     else:
6857       if instance_running:
6858         result = self.rpc.call_instance_shutdown(node_current, instance,
6859                                                  self.op.shutdown_timeout)
6860         result.Raise("Could not shutdown instance for full reboot")
6861         _ShutdownInstanceDisks(self, instance)
6862       else:
6863         self.LogInfo("Instance %s was already stopped, starting now",
6864                      instance.name)
6865       _StartInstanceDisks(self, instance, ignore_secondaries)
6866       result = self.rpc.call_instance_start(node_current,
6867                                             (instance, None, None), False)
6868       msg = result.fail_msg
6869       if msg:
6870         _ShutdownInstanceDisks(self, instance)
6871         raise errors.OpExecError("Could not start instance for"
6872                                  " full reboot: %s" % msg)
6873
6874     self.cfg.MarkInstanceUp(instance.name)
6875
6876
6877 class LUInstanceShutdown(LogicalUnit):
6878   """Shutdown an instance.
6879
6880   """
6881   HPATH = "instance-stop"
6882   HTYPE = constants.HTYPE_INSTANCE
6883   REQ_BGL = False
6884
6885   def ExpandNames(self):
6886     self._ExpandAndLockInstance()
6887
6888   def BuildHooksEnv(self):
6889     """Build hooks env.
6890
6891     This runs on master, primary and secondary nodes of the instance.
6892
6893     """
6894     env = _BuildInstanceHookEnvByObject(self, self.instance)
6895     env["TIMEOUT"] = self.op.timeout
6896     return env
6897
6898   def BuildHooksNodes(self):
6899     """Build hooks nodes.
6900
6901     """
6902     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6903     return (nl, nl)
6904
6905   def CheckPrereq(self):
6906     """Check prerequisites.
6907
6908     This checks that the instance is in the cluster.
6909
6910     """
6911     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6912     assert self.instance is not None, \
6913       "Cannot retrieve locked instance %s" % self.op.instance_name
6914
6915     _CheckInstanceState(self, self.instance, INSTANCE_ONLINE)
6916
6917     self.primary_offline = \
6918       self.cfg.GetNodeInfo(self.instance.primary_node).offline
6919
6920     if self.primary_offline and self.op.ignore_offline_nodes:
6921       self.proc.LogWarning("Ignoring offline primary node")
6922     else:
6923       _CheckNodeOnline(self, self.instance.primary_node)
6924
6925   def Exec(self, feedback_fn):
6926     """Shutdown the instance.
6927
6928     """
6929     instance = self.instance
6930     node_current = instance.primary_node
6931     timeout = self.op.timeout
6932
6933     if not self.op.no_remember:
6934       self.cfg.MarkInstanceDown(instance.name)
6935
6936     if self.primary_offline:
6937       assert self.op.ignore_offline_nodes
6938       self.proc.LogInfo("Primary node offline, marked instance as stopped")
6939     else:
6940       result = self.rpc.call_instance_shutdown(node_current, instance, timeout)
6941       msg = result.fail_msg
6942       if msg:
6943         self.proc.LogWarning("Could not shutdown instance: %s" % msg)
6944
6945       _ShutdownInstanceDisks(self, instance)
6946
6947
6948 class LUInstanceReinstall(LogicalUnit):
6949   """Reinstall an instance.
6950
6951   """
6952   HPATH = "instance-reinstall"
6953   HTYPE = constants.HTYPE_INSTANCE
6954   REQ_BGL = False
6955
6956   def ExpandNames(self):
6957     self._ExpandAndLockInstance()
6958
6959   def BuildHooksEnv(self):
6960     """Build hooks env.
6961
6962     This runs on master, primary and secondary nodes of the instance.
6963
6964     """
6965     return _BuildInstanceHookEnvByObject(self, self.instance)
6966
6967   def BuildHooksNodes(self):
6968     """Build hooks nodes.
6969
6970     """
6971     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6972     return (nl, nl)
6973
6974   def CheckPrereq(self):
6975     """Check prerequisites.
6976
6977     This checks that the instance is in the cluster and is not running.
6978
6979     """
6980     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6981     assert instance is not None, \
6982       "Cannot retrieve locked instance %s" % self.op.instance_name
6983     _CheckNodeOnline(self, instance.primary_node, "Instance primary node"
6984                      " offline, cannot reinstall")
6985     for node in instance.secondary_nodes:
6986       _CheckNodeOnline(self, node, "Instance secondary node offline,"
6987                        " cannot reinstall")
6988
6989     if instance.disk_template == constants.DT_DISKLESS:
6990       raise errors.OpPrereqError("Instance '%s' has no disks" %
6991                                  self.op.instance_name,
6992                                  errors.ECODE_INVAL)
6993     _CheckInstanceState(self, instance, INSTANCE_DOWN, msg="cannot reinstall")
6994
6995     if self.op.os_type is not None:
6996       # OS verification
6997       pnode = _ExpandNodeName(self.cfg, instance.primary_node)
6998       _CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant)
6999       instance_os = self.op.os_type
7000     else:
7001       instance_os = instance.os
7002
7003     nodelist = list(instance.all_nodes)
7004
7005     if self.op.osparams:
7006       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
7007       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
7008       self.os_inst = i_osdict # the new dict (without defaults)
7009     else:
7010       self.os_inst = None
7011
7012     self.instance = instance
7013
7014   def Exec(self, feedback_fn):
7015     """Reinstall the instance.
7016
7017     """
7018     inst = self.instance
7019
7020     if self.op.os_type is not None:
7021       feedback_fn("Changing OS to '%s'..." % self.op.os_type)
7022       inst.os = self.op.os_type
7023       # Write to configuration
7024       self.cfg.Update(inst, feedback_fn)
7025
7026     _StartInstanceDisks(self, inst, None)
7027     try:
7028       feedback_fn("Running the instance OS create scripts...")
7029       # FIXME: pass debug option from opcode to backend
7030       result = self.rpc.call_instance_os_add(inst.primary_node,
7031                                              (inst, self.os_inst), True,
7032                                              self.op.debug_level)
7033       result.Raise("Could not install OS for instance %s on node %s" %
7034                    (inst.name, inst.primary_node))
7035     finally:
7036       _ShutdownInstanceDisks(self, inst)
7037
7038
7039 class LUInstanceRecreateDisks(LogicalUnit):
7040   """Recreate an instance's missing disks.
7041
7042   """
7043   HPATH = "instance-recreate-disks"
7044   HTYPE = constants.HTYPE_INSTANCE
7045   REQ_BGL = False
7046
7047   _MODIFYABLE = frozenset([
7048     constants.IDISK_SIZE,
7049     constants.IDISK_MODE,
7050     ])
7051
7052   # New or changed disk parameters may have different semantics
7053   assert constants.IDISK_PARAMS == (_MODIFYABLE | frozenset([
7054     constants.IDISK_ADOPT,
7055
7056     # TODO: Implement support changing VG while recreating
7057     constants.IDISK_VG,
7058     constants.IDISK_METAVG,
7059     ]))
7060
7061   def CheckArguments(self):
7062     if self.op.disks and ht.TPositiveInt(self.op.disks[0]):
7063       # Normalize and convert deprecated list of disk indices
7064       self.op.disks = [(idx, {}) for idx in sorted(frozenset(self.op.disks))]
7065
7066     duplicates = utils.FindDuplicates(map(compat.fst, self.op.disks))
7067     if duplicates:
7068       raise errors.OpPrereqError("Some disks have been specified more than"
7069                                  " once: %s" % utils.CommaJoin(duplicates),
7070                                  errors.ECODE_INVAL)
7071
7072     for (idx, params) in self.op.disks:
7073       utils.ForceDictType(params, constants.IDISK_PARAMS_TYPES)
7074       unsupported = frozenset(params.keys()) - self._MODIFYABLE
7075       if unsupported:
7076         raise errors.OpPrereqError("Parameters for disk %s try to change"
7077                                    " unmodifyable parameter(s): %s" %
7078                                    (idx, utils.CommaJoin(unsupported)),
7079                                    errors.ECODE_INVAL)
7080
7081   def ExpandNames(self):
7082     self._ExpandAndLockInstance()
7083     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
7084     if self.op.nodes:
7085       self.op.nodes = [_ExpandNodeName(self.cfg, n) for n in self.op.nodes]
7086       self.needed_locks[locking.LEVEL_NODE] = list(self.op.nodes)
7087     else:
7088       self.needed_locks[locking.LEVEL_NODE] = []
7089     self.needed_locks[locking.LEVEL_NODE_RES] = []
7090
7091   def DeclareLocks(self, level):
7092     if level == locking.LEVEL_NODE:
7093       # if we replace the nodes, we only need to lock the old primary,
7094       # otherwise we need to lock all nodes for disk re-creation
7095       primary_only = bool(self.op.nodes)
7096       self._LockInstancesNodes(primary_only=primary_only)
7097     elif level == locking.LEVEL_NODE_RES:
7098       # Copy node locks
7099       self.needed_locks[locking.LEVEL_NODE_RES] = \
7100         self.needed_locks[locking.LEVEL_NODE][:]
7101
7102   def BuildHooksEnv(self):
7103     """Build hooks env.
7104
7105     This runs on master, primary and secondary nodes of the instance.
7106
7107     """
7108     return _BuildInstanceHookEnvByObject(self, self.instance)
7109
7110   def BuildHooksNodes(self):
7111     """Build hooks nodes.
7112
7113     """
7114     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7115     return (nl, nl)
7116
7117   def CheckPrereq(self):
7118     """Check prerequisites.
7119
7120     This checks that the instance is in the cluster and is not running.
7121
7122     """
7123     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7124     assert instance is not None, \
7125       "Cannot retrieve locked instance %s" % self.op.instance_name
7126     if self.op.nodes:
7127       if len(self.op.nodes) != len(instance.all_nodes):
7128         raise errors.OpPrereqError("Instance %s currently has %d nodes, but"
7129                                    " %d replacement nodes were specified" %
7130                                    (instance.name, len(instance.all_nodes),
7131                                     len(self.op.nodes)),
7132                                    errors.ECODE_INVAL)
7133       assert instance.disk_template != constants.DT_DRBD8 or \
7134           len(self.op.nodes) == 2
7135       assert instance.disk_template != constants.DT_PLAIN or \
7136           len(self.op.nodes) == 1
7137       primary_node = self.op.nodes[0]
7138     else:
7139       primary_node = instance.primary_node
7140     _CheckNodeOnline(self, primary_node)
7141
7142     if instance.disk_template == constants.DT_DISKLESS:
7143       raise errors.OpPrereqError("Instance '%s' has no disks" %
7144                                  self.op.instance_name, errors.ECODE_INVAL)
7145
7146     # if we replace nodes *and* the old primary is offline, we don't
7147     # check
7148     assert instance.primary_node in self.owned_locks(locking.LEVEL_NODE)
7149     assert instance.primary_node in self.owned_locks(locking.LEVEL_NODE_RES)
7150     old_pnode = self.cfg.GetNodeInfo(instance.primary_node)
7151     if not (self.op.nodes and old_pnode.offline):
7152       _CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
7153                           msg="cannot recreate disks")
7154
7155     if self.op.disks:
7156       self.disks = dict(self.op.disks)
7157     else:
7158       self.disks = dict((idx, {}) for idx in range(len(instance.disks)))
7159
7160     maxidx = max(self.disks.keys())
7161     if maxidx >= len(instance.disks):
7162       raise errors.OpPrereqError("Invalid disk index '%s'" % maxidx,
7163                                  errors.ECODE_INVAL)
7164
7165     if (self.op.nodes and
7166         sorted(self.disks.keys()) != range(len(instance.disks))):
7167       raise errors.OpPrereqError("Can't recreate disks partially and"
7168                                  " change the nodes at the same time",
7169                                  errors.ECODE_INVAL)
7170
7171     self.instance = instance
7172
7173   def Exec(self, feedback_fn):
7174     """Recreate the disks.
7175
7176     """
7177     instance = self.instance
7178
7179     assert (self.owned_locks(locking.LEVEL_NODE) ==
7180             self.owned_locks(locking.LEVEL_NODE_RES))
7181
7182     to_skip = []
7183     mods = [] # keeps track of needed changes
7184
7185     for idx, disk in enumerate(instance.disks):
7186       try:
7187         changes = self.disks[idx]
7188       except KeyError:
7189         # Disk should not be recreated
7190         to_skip.append(idx)
7191         continue
7192
7193       # update secondaries for disks, if needed
7194       if self.op.nodes and disk.dev_type == constants.LD_DRBD8:
7195         # need to update the nodes and minors
7196         assert len(self.op.nodes) == 2
7197         assert len(disk.logical_id) == 6 # otherwise disk internals
7198                                          # have changed
7199         (_, _, old_port, _, _, old_secret) = disk.logical_id
7200         new_minors = self.cfg.AllocateDRBDMinor(self.op.nodes, instance.name)
7201         new_id = (self.op.nodes[0], self.op.nodes[1], old_port,
7202                   new_minors[0], new_minors[1], old_secret)
7203         assert len(disk.logical_id) == len(new_id)
7204       else:
7205         new_id = None
7206
7207       mods.append((idx, new_id, changes))
7208
7209     # now that we have passed all asserts above, we can apply the mods
7210     # in a single run (to avoid partial changes)
7211     for idx, new_id, changes in mods:
7212       disk = instance.disks[idx]
7213       if new_id is not None:
7214         assert disk.dev_type == constants.LD_DRBD8
7215         disk.logical_id = new_id
7216       if changes:
7217         disk.Update(size=changes.get(constants.IDISK_SIZE, None),
7218                     mode=changes.get(constants.IDISK_MODE, None))
7219
7220     # change primary node, if needed
7221     if self.op.nodes:
7222       instance.primary_node = self.op.nodes[0]
7223       self.LogWarning("Changing the instance's nodes, you will have to"
7224                       " remove any disks left on the older nodes manually")
7225
7226     if self.op.nodes:
7227       self.cfg.Update(instance, feedback_fn)
7228
7229     _CreateDisks(self, instance, to_skip=to_skip)
7230
7231
7232 class LUInstanceRename(LogicalUnit):
7233   """Rename an instance.
7234
7235   """
7236   HPATH = "instance-rename"
7237   HTYPE = constants.HTYPE_INSTANCE
7238
7239   def CheckArguments(self):
7240     """Check arguments.
7241
7242     """
7243     if self.op.ip_check and not self.op.name_check:
7244       # TODO: make the ip check more flexible and not depend on the name check
7245       raise errors.OpPrereqError("IP address check requires a name check",
7246                                  errors.ECODE_INVAL)
7247
7248   def BuildHooksEnv(self):
7249     """Build hooks env.
7250
7251     This runs on master, primary and secondary nodes of the instance.
7252
7253     """
7254     env = _BuildInstanceHookEnvByObject(self, self.instance)
7255     env["INSTANCE_NEW_NAME"] = self.op.new_name
7256     return env
7257
7258   def BuildHooksNodes(self):
7259     """Build hooks nodes.
7260
7261     """
7262     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7263     return (nl, nl)
7264
7265   def CheckPrereq(self):
7266     """Check prerequisites.
7267
7268     This checks that the instance is in the cluster and is not running.
7269
7270     """
7271     self.op.instance_name = _ExpandInstanceName(self.cfg,
7272                                                 self.op.instance_name)
7273     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7274     assert instance is not None
7275     _CheckNodeOnline(self, instance.primary_node)
7276     _CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
7277                         msg="cannot rename")
7278     self.instance = instance
7279
7280     new_name = self.op.new_name
7281     if self.op.name_check:
7282       hostname = netutils.GetHostname(name=new_name)
7283       if hostname.name != new_name:
7284         self.LogInfo("Resolved given name '%s' to '%s'", new_name,
7285                      hostname.name)
7286       if not utils.MatchNameComponent(self.op.new_name, [hostname.name]):
7287         raise errors.OpPrereqError(("Resolved hostname '%s' does not look the"
7288                                     " same as given hostname '%s'") %
7289                                     (hostname.name, self.op.new_name),
7290                                     errors.ECODE_INVAL)
7291       new_name = self.op.new_name = hostname.name
7292       if (self.op.ip_check and
7293           netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
7294         raise errors.OpPrereqError("IP %s of instance %s already in use" %
7295                                    (hostname.ip, new_name),
7296                                    errors.ECODE_NOTUNIQUE)
7297
7298     instance_list = self.cfg.GetInstanceList()
7299     if new_name in instance_list and new_name != instance.name:
7300       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
7301                                  new_name, errors.ECODE_EXISTS)
7302
7303   def Exec(self, feedback_fn):
7304     """Rename the instance.
7305
7306     """
7307     inst = self.instance
7308     old_name = inst.name
7309
7310     rename_file_storage = False
7311     if (inst.disk_template in constants.DTS_FILEBASED and
7312         self.op.new_name != inst.name):
7313       old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
7314       rename_file_storage = True
7315
7316     self.cfg.RenameInstance(inst.name, self.op.new_name)
7317     # Change the instance lock. This is definitely safe while we hold the BGL.
7318     # Otherwise the new lock would have to be added in acquired mode.
7319     assert self.REQ_BGL
7320     self.glm.remove(locking.LEVEL_INSTANCE, old_name)
7321     self.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
7322
7323     # re-read the instance from the configuration after rename
7324     inst = self.cfg.GetInstanceInfo(self.op.new_name)
7325
7326     if rename_file_storage:
7327       new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
7328       result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
7329                                                      old_file_storage_dir,
7330                                                      new_file_storage_dir)
7331       result.Raise("Could not rename on node %s directory '%s' to '%s'"
7332                    " (but the instance has been renamed in Ganeti)" %
7333                    (inst.primary_node, old_file_storage_dir,
7334                     new_file_storage_dir))
7335
7336     _StartInstanceDisks(self, inst, None)
7337     try:
7338       result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
7339                                                  old_name, self.op.debug_level)
7340       msg = result.fail_msg
7341       if msg:
7342         msg = ("Could not run OS rename script for instance %s on node %s"
7343                " (but the instance has been renamed in Ganeti): %s" %
7344                (inst.name, inst.primary_node, msg))
7345         self.proc.LogWarning(msg)
7346     finally:
7347       _ShutdownInstanceDisks(self, inst)
7348
7349     return inst.name
7350
7351
7352 class LUInstanceRemove(LogicalUnit):
7353   """Remove an instance.
7354
7355   """
7356   HPATH = "instance-remove"
7357   HTYPE = constants.HTYPE_INSTANCE
7358   REQ_BGL = False
7359
7360   def ExpandNames(self):
7361     self._ExpandAndLockInstance()
7362     self.needed_locks[locking.LEVEL_NODE] = []
7363     self.needed_locks[locking.LEVEL_NODE_RES] = []
7364     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7365
7366   def DeclareLocks(self, level):
7367     if level == locking.LEVEL_NODE:
7368       self._LockInstancesNodes()
7369     elif level == locking.LEVEL_NODE_RES:
7370       # Copy node locks
7371       self.needed_locks[locking.LEVEL_NODE_RES] = \
7372         self.needed_locks[locking.LEVEL_NODE][:]
7373
7374   def BuildHooksEnv(self):
7375     """Build hooks env.
7376
7377     This runs on master, primary and secondary nodes of the instance.
7378
7379     """
7380     env = _BuildInstanceHookEnvByObject(self, self.instance)
7381     env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
7382     return env
7383
7384   def BuildHooksNodes(self):
7385     """Build hooks nodes.
7386
7387     """
7388     nl = [self.cfg.GetMasterNode()]
7389     nl_post = list(self.instance.all_nodes) + nl
7390     return (nl, nl_post)
7391
7392   def CheckPrereq(self):
7393     """Check prerequisites.
7394
7395     This checks that the instance is in the cluster.
7396
7397     """
7398     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7399     assert self.instance is not None, \
7400       "Cannot retrieve locked instance %s" % self.op.instance_name
7401
7402   def Exec(self, feedback_fn):
7403     """Remove the instance.
7404
7405     """
7406     instance = self.instance
7407     logging.info("Shutting down instance %s on node %s",
7408                  instance.name, instance.primary_node)
7409
7410     result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
7411                                              self.op.shutdown_timeout)
7412     msg = result.fail_msg
7413     if msg:
7414       if self.op.ignore_failures:
7415         feedback_fn("Warning: can't shutdown instance: %s" % msg)
7416       else:
7417         raise errors.OpExecError("Could not shutdown instance %s on"
7418                                  " node %s: %s" %
7419                                  (instance.name, instance.primary_node, msg))
7420
7421     assert (self.owned_locks(locking.LEVEL_NODE) ==
7422             self.owned_locks(locking.LEVEL_NODE_RES))
7423     assert not (set(instance.all_nodes) -
7424                 self.owned_locks(locking.LEVEL_NODE)), \
7425       "Not owning correct locks"
7426
7427     _RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
7428
7429
7430 def _RemoveInstance(lu, feedback_fn, instance, ignore_failures):
7431   """Utility function to remove an instance.
7432
7433   """
7434   logging.info("Removing block devices for instance %s", instance.name)
7435
7436   if not _RemoveDisks(lu, instance, ignore_failures=ignore_failures):
7437     if not ignore_failures:
7438       raise errors.OpExecError("Can't remove instance's disks")
7439     feedback_fn("Warning: can't remove instance's disks")
7440
7441   logging.info("Removing instance %s out of cluster config", instance.name)
7442
7443   lu.cfg.RemoveInstance(instance.name)
7444
7445   assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \
7446     "Instance lock removal conflict"
7447
7448   # Remove lock for the instance
7449   lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
7450
7451
7452 class LUInstanceQuery(NoHooksLU):
7453   """Logical unit for querying instances.
7454
7455   """
7456   # pylint: disable=W0142
7457   REQ_BGL = False
7458
7459   def CheckArguments(self):
7460     self.iq = _InstanceQuery(qlang.MakeSimpleFilter("name", self.op.names),
7461                              self.op.output_fields, self.op.use_locking)
7462
7463   def ExpandNames(self):
7464     self.iq.ExpandNames(self)
7465
7466   def DeclareLocks(self, level):
7467     self.iq.DeclareLocks(self, level)
7468
7469   def Exec(self, feedback_fn):
7470     return self.iq.OldStyleQuery(self)
7471
7472
7473 class LUInstanceFailover(LogicalUnit):
7474   """Failover an instance.
7475
7476   """
7477   HPATH = "instance-failover"
7478   HTYPE = constants.HTYPE_INSTANCE
7479   REQ_BGL = False
7480
7481   def CheckArguments(self):
7482     """Check the arguments.
7483
7484     """
7485     self.iallocator = getattr(self.op, "iallocator", None)
7486     self.target_node = getattr(self.op, "target_node", None)
7487
7488   def ExpandNames(self):
7489     self._ExpandAndLockInstance()
7490
7491     if self.op.target_node is not None:
7492       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7493
7494     self.needed_locks[locking.LEVEL_NODE] = []
7495     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7496
7497     self.needed_locks[locking.LEVEL_NODE_RES] = []
7498     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
7499
7500     ignore_consistency = self.op.ignore_consistency
7501     shutdown_timeout = self.op.shutdown_timeout
7502     self._migrater = TLMigrateInstance(self, self.op.instance_name,
7503                                        cleanup=False,
7504                                        failover=True,
7505                                        ignore_consistency=ignore_consistency,
7506                                        shutdown_timeout=shutdown_timeout,
7507                                        ignore_ipolicy=self.op.ignore_ipolicy)
7508     self.tasklets = [self._migrater]
7509
7510   def DeclareLocks(self, level):
7511     if level == locking.LEVEL_NODE:
7512       instance = self.context.cfg.GetInstanceInfo(self.op.instance_name)
7513       if instance.disk_template in constants.DTS_EXT_MIRROR:
7514         if self.op.target_node is None:
7515           self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7516         else:
7517           self.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
7518                                                    self.op.target_node]
7519         del self.recalculate_locks[locking.LEVEL_NODE]
7520       else:
7521         self._LockInstancesNodes()
7522     elif level == locking.LEVEL_NODE_RES:
7523       # Copy node locks
7524       self.needed_locks[locking.LEVEL_NODE_RES] = \
7525         self.needed_locks[locking.LEVEL_NODE][:]
7526
7527   def BuildHooksEnv(self):
7528     """Build hooks env.
7529
7530     This runs on master, primary and secondary nodes of the instance.
7531
7532     """
7533     instance = self._migrater.instance
7534     source_node = instance.primary_node
7535     target_node = self.op.target_node
7536     env = {
7537       "IGNORE_CONSISTENCY": self.op.ignore_consistency,
7538       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
7539       "OLD_PRIMARY": source_node,
7540       "NEW_PRIMARY": target_node,
7541       }
7542
7543     if instance.disk_template in constants.DTS_INT_MIRROR:
7544       env["OLD_SECONDARY"] = instance.secondary_nodes[0]
7545       env["NEW_SECONDARY"] = source_node
7546     else:
7547       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = ""
7548
7549     env.update(_BuildInstanceHookEnvByObject(self, instance))
7550
7551     return env
7552
7553   def BuildHooksNodes(self):
7554     """Build hooks nodes.
7555
7556     """
7557     instance = self._migrater.instance
7558     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
7559     return (nl, nl + [instance.primary_node])
7560
7561
7562 class LUInstanceMigrate(LogicalUnit):
7563   """Migrate an instance.
7564
7565   This is migration without shutting down, compared to the failover,
7566   which is done with shutdown.
7567
7568   """
7569   HPATH = "instance-migrate"
7570   HTYPE = constants.HTYPE_INSTANCE
7571   REQ_BGL = False
7572
7573   def ExpandNames(self):
7574     self._ExpandAndLockInstance()
7575
7576     if self.op.target_node is not None:
7577       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7578
7579     self.needed_locks[locking.LEVEL_NODE] = []
7580     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7581
7582     self.needed_locks[locking.LEVEL_NODE] = []
7583     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7584
7585     self._migrater = \
7586       TLMigrateInstance(self, self.op.instance_name,
7587                         cleanup=self.op.cleanup,
7588                         failover=False,
7589                         fallback=self.op.allow_failover,
7590                         allow_runtime_changes=self.op.allow_runtime_changes,
7591                         ignore_ipolicy=self.op.ignore_ipolicy)
7592     self.tasklets = [self._migrater]
7593
7594   def DeclareLocks(self, level):
7595     if level == locking.LEVEL_NODE:
7596       instance = self.context.cfg.GetInstanceInfo(self.op.instance_name)
7597       if instance.disk_template in constants.DTS_EXT_MIRROR:
7598         if self.op.target_node is None:
7599           self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7600         else:
7601           self.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
7602                                                    self.op.target_node]
7603         del self.recalculate_locks[locking.LEVEL_NODE]
7604       else:
7605         self._LockInstancesNodes()
7606     elif level == locking.LEVEL_NODE_RES:
7607       # Copy node locks
7608       self.needed_locks[locking.LEVEL_NODE_RES] = \
7609         self.needed_locks[locking.LEVEL_NODE][:]
7610
7611   def BuildHooksEnv(self):
7612     """Build hooks env.
7613
7614     This runs on master, primary and secondary nodes of the instance.
7615
7616     """
7617     instance = self._migrater.instance
7618     source_node = instance.primary_node
7619     target_node = self.op.target_node
7620     env = _BuildInstanceHookEnvByObject(self, instance)
7621     env.update({
7622       "MIGRATE_LIVE": self._migrater.live,
7623       "MIGRATE_CLEANUP": self.op.cleanup,
7624       "OLD_PRIMARY": source_node,
7625       "NEW_PRIMARY": target_node,
7626       "ALLOW_RUNTIME_CHANGES": self.op.allow_runtime_changes,
7627       })
7628
7629     if instance.disk_template in constants.DTS_INT_MIRROR:
7630       env["OLD_SECONDARY"] = target_node
7631       env["NEW_SECONDARY"] = source_node
7632     else:
7633       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = None
7634
7635     return env
7636
7637   def BuildHooksNodes(self):
7638     """Build hooks nodes.
7639
7640     """
7641     instance = self._migrater.instance
7642     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
7643     return (nl, nl + [instance.primary_node])
7644
7645
7646 class LUInstanceMove(LogicalUnit):
7647   """Move an instance by data-copying.
7648
7649   """
7650   HPATH = "instance-move"
7651   HTYPE = constants.HTYPE_INSTANCE
7652   REQ_BGL = False
7653
7654   def ExpandNames(self):
7655     self._ExpandAndLockInstance()
7656     target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7657     self.op.target_node = target_node
7658     self.needed_locks[locking.LEVEL_NODE] = [target_node]
7659     self.needed_locks[locking.LEVEL_NODE_RES] = []
7660     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
7661
7662   def DeclareLocks(self, level):
7663     if level == locking.LEVEL_NODE:
7664       self._LockInstancesNodes(primary_only=True)
7665     elif level == locking.LEVEL_NODE_RES:
7666       # Copy node locks
7667       self.needed_locks[locking.LEVEL_NODE_RES] = \
7668         self.needed_locks[locking.LEVEL_NODE][:]
7669
7670   def BuildHooksEnv(self):
7671     """Build hooks env.
7672
7673     This runs on master, primary and secondary nodes of the instance.
7674
7675     """
7676     env = {
7677       "TARGET_NODE": self.op.target_node,
7678       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
7679       }
7680     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
7681     return env
7682
7683   def BuildHooksNodes(self):
7684     """Build hooks nodes.
7685
7686     """
7687     nl = [
7688       self.cfg.GetMasterNode(),
7689       self.instance.primary_node,
7690       self.op.target_node,
7691       ]
7692     return (nl, nl)
7693
7694   def CheckPrereq(self):
7695     """Check prerequisites.
7696
7697     This checks that the instance is in the cluster.
7698
7699     """
7700     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7701     assert self.instance is not None, \
7702       "Cannot retrieve locked instance %s" % self.op.instance_name
7703
7704     node = self.cfg.GetNodeInfo(self.op.target_node)
7705     assert node is not None, \
7706       "Cannot retrieve locked node %s" % self.op.target_node
7707
7708     self.target_node = target_node = node.name
7709
7710     if target_node == instance.primary_node:
7711       raise errors.OpPrereqError("Instance %s is already on the node %s" %
7712                                  (instance.name, target_node),
7713                                  errors.ECODE_STATE)
7714
7715     bep = self.cfg.GetClusterInfo().FillBE(instance)
7716
7717     for idx, dsk in enumerate(instance.disks):
7718       if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
7719         raise errors.OpPrereqError("Instance disk %d has a complex layout,"
7720                                    " cannot copy" % idx, errors.ECODE_STATE)
7721
7722     _CheckNodeOnline(self, target_node)
7723     _CheckNodeNotDrained(self, target_node)
7724     _CheckNodeVmCapable(self, target_node)
7725     ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(),
7726                                      self.cfg.GetNodeGroup(node.group))
7727     _CheckTargetNodeIPolicy(self, ipolicy, instance, node,
7728                             ignore=self.op.ignore_ipolicy)
7729
7730     if instance.admin_state == constants.ADMINST_UP:
7731       # check memory requirements on the secondary node
7732       _CheckNodeFreeMemory(self, target_node, "failing over instance %s" %
7733                            instance.name, bep[constants.BE_MAXMEM],
7734                            instance.hypervisor)
7735     else:
7736       self.LogInfo("Not checking memory on the secondary node as"
7737                    " instance will not be started")
7738
7739     # check bridge existance
7740     _CheckInstanceBridgesExist(self, instance, node=target_node)
7741
7742   def Exec(self, feedback_fn):
7743     """Move an instance.
7744
7745     The move is done by shutting it down on its present node, copying
7746     the data over (slow) and starting it on the new node.
7747
7748     """
7749     instance = self.instance
7750
7751     source_node = instance.primary_node
7752     target_node = self.target_node
7753
7754     self.LogInfo("Shutting down instance %s on source node %s",
7755                  instance.name, source_node)
7756
7757     assert (self.owned_locks(locking.LEVEL_NODE) ==
7758             self.owned_locks(locking.LEVEL_NODE_RES))
7759
7760     result = self.rpc.call_instance_shutdown(source_node, instance,
7761                                              self.op.shutdown_timeout)
7762     msg = result.fail_msg
7763     if msg:
7764       if self.op.ignore_consistency:
7765         self.proc.LogWarning("Could not shutdown instance %s on node %s."
7766                              " Proceeding anyway. Please make sure node"
7767                              " %s is down. Error details: %s",
7768                              instance.name, source_node, source_node, msg)
7769       else:
7770         raise errors.OpExecError("Could not shutdown instance %s on"
7771                                  " node %s: %s" %
7772                                  (instance.name, source_node, msg))
7773
7774     # create the target disks
7775     try:
7776       _CreateDisks(self, instance, target_node=target_node)
7777     except errors.OpExecError:
7778       self.LogWarning("Device creation failed, reverting...")
7779       try:
7780         _RemoveDisks(self, instance, target_node=target_node)
7781       finally:
7782         self.cfg.ReleaseDRBDMinors(instance.name)
7783         raise
7784
7785     cluster_name = self.cfg.GetClusterInfo().cluster_name
7786
7787     errs = []
7788     # activate, get path, copy the data over
7789     for idx, disk in enumerate(instance.disks):
7790       self.LogInfo("Copying data for disk %d", idx)
7791       result = self.rpc.call_blockdev_assemble(target_node, (disk, instance),
7792                                                instance.name, True, idx)
7793       if result.fail_msg:
7794         self.LogWarning("Can't assemble newly created disk %d: %s",
7795                         idx, result.fail_msg)
7796         errs.append(result.fail_msg)
7797         break
7798       dev_path = result.payload
7799       result = self.rpc.call_blockdev_export(source_node, (disk, instance),
7800                                              target_node, dev_path,
7801                                              cluster_name)
7802       if result.fail_msg:
7803         self.LogWarning("Can't copy data over for disk %d: %s",
7804                         idx, result.fail_msg)
7805         errs.append(result.fail_msg)
7806         break
7807
7808     if errs:
7809       self.LogWarning("Some disks failed to copy, aborting")
7810       try:
7811         _RemoveDisks(self, instance, target_node=target_node)
7812       finally:
7813         self.cfg.ReleaseDRBDMinors(instance.name)
7814         raise errors.OpExecError("Errors during disk copy: %s" %
7815                                  (",".join(errs),))
7816
7817     instance.primary_node = target_node
7818     self.cfg.Update(instance, feedback_fn)
7819
7820     self.LogInfo("Removing the disks on the original node")
7821     _RemoveDisks(self, instance, target_node=source_node)
7822
7823     # Only start the instance if it's marked as up
7824     if instance.admin_state == constants.ADMINST_UP:
7825       self.LogInfo("Starting instance %s on node %s",
7826                    instance.name, target_node)
7827
7828       disks_ok, _ = _AssembleInstanceDisks(self, instance,
7829                                            ignore_secondaries=True)
7830       if not disks_ok:
7831         _ShutdownInstanceDisks(self, instance)
7832         raise errors.OpExecError("Can't activate the instance's disks")
7833
7834       result = self.rpc.call_instance_start(target_node,
7835                                             (instance, None, None), False)
7836       msg = result.fail_msg
7837       if msg:
7838         _ShutdownInstanceDisks(self, instance)
7839         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
7840                                  (instance.name, target_node, msg))
7841
7842
7843 class LUNodeMigrate(LogicalUnit):
7844   """Migrate all instances from a node.
7845
7846   """
7847   HPATH = "node-migrate"
7848   HTYPE = constants.HTYPE_NODE
7849   REQ_BGL = False
7850
7851   def CheckArguments(self):
7852     pass
7853
7854   def ExpandNames(self):
7855     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
7856
7857     self.share_locks = _ShareAll()
7858     self.needed_locks = {
7859       locking.LEVEL_NODE: [self.op.node_name],
7860       }
7861
7862   def BuildHooksEnv(self):
7863     """Build hooks env.
7864
7865     This runs on the master, the primary and all the secondaries.
7866
7867     """
7868     return {
7869       "NODE_NAME": self.op.node_name,
7870       "ALLOW_RUNTIME_CHANGES": self.op.allow_runtime_changes,
7871       }
7872
7873   def BuildHooksNodes(self):
7874     """Build hooks nodes.
7875
7876     """
7877     nl = [self.cfg.GetMasterNode()]
7878     return (nl, nl)
7879
7880   def CheckPrereq(self):
7881     pass
7882
7883   def Exec(self, feedback_fn):
7884     # Prepare jobs for migration instances
7885     allow_runtime_changes = self.op.allow_runtime_changes
7886     jobs = [
7887       [opcodes.OpInstanceMigrate(instance_name=inst.name,
7888                                  mode=self.op.mode,
7889                                  live=self.op.live,
7890                                  iallocator=self.op.iallocator,
7891                                  target_node=self.op.target_node,
7892                                  allow_runtime_changes=allow_runtime_changes,
7893                                  ignore_ipolicy=self.op.ignore_ipolicy)]
7894       for inst in _GetNodePrimaryInstances(self.cfg, self.op.node_name)
7895       ]
7896
7897     # TODO: Run iallocator in this opcode and pass correct placement options to
7898     # OpInstanceMigrate. Since other jobs can modify the cluster between
7899     # running the iallocator and the actual migration, a good consistency model
7900     # will have to be found.
7901
7902     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
7903             frozenset([self.op.node_name]))
7904
7905     return ResultWithJobs(jobs)
7906
7907
7908 class TLMigrateInstance(Tasklet):
7909   """Tasklet class for instance migration.
7910
7911   @type live: boolean
7912   @ivar live: whether the migration will be done live or non-live;
7913       this variable is initalized only after CheckPrereq has run
7914   @type cleanup: boolean
7915   @ivar cleanup: Wheater we cleanup from a failed migration
7916   @type iallocator: string
7917   @ivar iallocator: The iallocator used to determine target_node
7918   @type target_node: string
7919   @ivar target_node: If given, the target_node to reallocate the instance to
7920   @type failover: boolean
7921   @ivar failover: Whether operation results in failover or migration
7922   @type fallback: boolean
7923   @ivar fallback: Whether fallback to failover is allowed if migration not
7924                   possible
7925   @type ignore_consistency: boolean
7926   @ivar ignore_consistency: Wheter we should ignore consistency between source
7927                             and target node
7928   @type shutdown_timeout: int
7929   @ivar shutdown_timeout: In case of failover timeout of the shutdown
7930   @type ignore_ipolicy: bool
7931   @ivar ignore_ipolicy: If true, we can ignore instance policy when migrating
7932
7933   """
7934
7935   # Constants
7936   _MIGRATION_POLL_INTERVAL = 1      # seconds
7937   _MIGRATION_FEEDBACK_INTERVAL = 10 # seconds
7938
7939   def __init__(self, lu, instance_name, cleanup=False,
7940                failover=False, fallback=False,
7941                ignore_consistency=False,
7942                allow_runtime_changes=True,
7943                shutdown_timeout=constants.DEFAULT_SHUTDOWN_TIMEOUT,
7944                ignore_ipolicy=False):
7945     """Initializes this class.
7946
7947     """
7948     Tasklet.__init__(self, lu)
7949
7950     # Parameters
7951     self.instance_name = instance_name
7952     self.cleanup = cleanup
7953     self.live = False # will be overridden later
7954     self.failover = failover
7955     self.fallback = fallback
7956     self.ignore_consistency = ignore_consistency
7957     self.shutdown_timeout = shutdown_timeout
7958     self.ignore_ipolicy = ignore_ipolicy
7959     self.allow_runtime_changes = allow_runtime_changes
7960
7961   def CheckPrereq(self):
7962     """Check prerequisites.
7963
7964     This checks that the instance is in the cluster.
7965
7966     """
7967     instance_name = _ExpandInstanceName(self.lu.cfg, self.instance_name)
7968     instance = self.cfg.GetInstanceInfo(instance_name)
7969     assert instance is not None
7970     self.instance = instance
7971     cluster = self.cfg.GetClusterInfo()
7972
7973     if (not self.cleanup and
7974         not instance.admin_state == constants.ADMINST_UP and
7975         not self.failover and self.fallback):
7976       self.lu.LogInfo("Instance is marked down or offline, fallback allowed,"
7977                       " switching to failover")
7978       self.failover = True
7979
7980     if instance.disk_template not in constants.DTS_MIRRORED:
7981       if self.failover:
7982         text = "failovers"
7983       else:
7984         text = "migrations"
7985       raise errors.OpPrereqError("Instance's disk layout '%s' does not allow"
7986                                  " %s" % (instance.disk_template, text),
7987                                  errors.ECODE_STATE)
7988
7989     if instance.disk_template in constants.DTS_EXT_MIRROR:
7990       _CheckIAllocatorOrNode(self.lu, "iallocator", "target_node")
7991
7992       if self.lu.op.iallocator:
7993         self._RunAllocator()
7994       else:
7995         # We set set self.target_node as it is required by
7996         # BuildHooksEnv
7997         self.target_node = self.lu.op.target_node
7998
7999       # Check that the target node is correct in terms of instance policy
8000       nodeinfo = self.cfg.GetNodeInfo(self.target_node)
8001       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
8002       ipolicy = _CalculateGroupIPolicy(cluster, group_info)
8003       _CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo,
8004                               ignore=self.ignore_ipolicy)
8005
8006       # self.target_node is already populated, either directly or by the
8007       # iallocator run
8008       target_node = self.target_node
8009       if self.target_node == instance.primary_node:
8010         raise errors.OpPrereqError("Cannot migrate instance %s"
8011                                    " to its primary (%s)" %
8012                                    (instance.name, instance.primary_node))
8013
8014       if len(self.lu.tasklets) == 1:
8015         # It is safe to release locks only when we're the only tasklet
8016         # in the LU
8017         _ReleaseLocks(self.lu, locking.LEVEL_NODE,
8018                       keep=[instance.primary_node, self.target_node])
8019
8020     else:
8021       secondary_nodes = instance.secondary_nodes
8022       if not secondary_nodes:
8023         raise errors.ConfigurationError("No secondary node but using"
8024                                         " %s disk template" %
8025                                         instance.disk_template)
8026       target_node = secondary_nodes[0]
8027       if self.lu.op.iallocator or (self.lu.op.target_node and
8028                                    self.lu.op.target_node != target_node):
8029         if self.failover:
8030           text = "failed over"
8031         else:
8032           text = "migrated"
8033         raise errors.OpPrereqError("Instances with disk template %s cannot"
8034                                    " be %s to arbitrary nodes"
8035                                    " (neither an iallocator nor a target"
8036                                    " node can be passed)" %
8037                                    (instance.disk_template, text),
8038                                    errors.ECODE_INVAL)
8039       nodeinfo = self.cfg.GetNodeInfo(target_node)
8040       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
8041       ipolicy = _CalculateGroupIPolicy(cluster, group_info)
8042       _CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo,
8043                               ignore=self.ignore_ipolicy)
8044
8045     i_be = cluster.FillBE(instance)
8046
8047     # check memory requirements on the secondary node
8048     if (not self.cleanup and
8049          (not self.failover or instance.admin_state == constants.ADMINST_UP)):
8050       self.tgt_free_mem = _CheckNodeFreeMemory(self.lu, target_node,
8051                                                "migrating instance %s" %
8052                                                instance.name,
8053                                                i_be[constants.BE_MINMEM],
8054                                                instance.hypervisor)
8055     else:
8056       self.lu.LogInfo("Not checking memory on the secondary node as"
8057                       " instance will not be started")
8058
8059     # check if failover must be forced instead of migration
8060     if (not self.cleanup and not self.failover and
8061         i_be[constants.BE_ALWAYS_FAILOVER]):
8062       if self.fallback:
8063         self.lu.LogInfo("Instance configured to always failover; fallback"
8064                         " to failover")
8065         self.failover = True
8066       else:
8067         raise errors.OpPrereqError("This instance has been configured to"
8068                                    " always failover, please allow failover",
8069                                    errors.ECODE_STATE)
8070
8071     # check bridge existance
8072     _CheckInstanceBridgesExist(self.lu, instance, node=target_node)
8073
8074     if not self.cleanup:
8075       _CheckNodeNotDrained(self.lu, target_node)
8076       if not self.failover:
8077         result = self.rpc.call_instance_migratable(instance.primary_node,
8078                                                    instance)
8079         if result.fail_msg and self.fallback:
8080           self.lu.LogInfo("Can't migrate, instance offline, fallback to"
8081                           " failover")
8082           self.failover = True
8083         else:
8084           result.Raise("Can't migrate, please use failover",
8085                        prereq=True, ecode=errors.ECODE_STATE)
8086
8087     assert not (self.failover and self.cleanup)
8088
8089     if not self.failover:
8090       if self.lu.op.live is not None and self.lu.op.mode is not None:
8091         raise errors.OpPrereqError("Only one of the 'live' and 'mode'"
8092                                    " parameters are accepted",
8093                                    errors.ECODE_INVAL)
8094       if self.lu.op.live is not None:
8095         if self.lu.op.live:
8096           self.lu.op.mode = constants.HT_MIGRATION_LIVE
8097         else:
8098           self.lu.op.mode = constants.HT_MIGRATION_NONLIVE
8099         # reset the 'live' parameter to None so that repeated
8100         # invocations of CheckPrereq do not raise an exception
8101         self.lu.op.live = None
8102       elif self.lu.op.mode is None:
8103         # read the default value from the hypervisor
8104         i_hv = cluster.FillHV(self.instance, skip_globals=False)
8105         self.lu.op.mode = i_hv[constants.HV_MIGRATION_MODE]
8106
8107       self.live = self.lu.op.mode == constants.HT_MIGRATION_LIVE
8108     else:
8109       # Failover is never live
8110       self.live = False
8111
8112     if not (self.failover or self.cleanup):
8113       remote_info = self.rpc.call_instance_info(instance.primary_node,
8114                                                 instance.name,
8115                                                 instance.hypervisor)
8116       remote_info.Raise("Error checking instance on node %s" %
8117                         instance.primary_node)
8118       instance_running = bool(remote_info.payload)
8119       if instance_running:
8120         self.current_mem = int(remote_info.payload["memory"])
8121
8122   def _RunAllocator(self):
8123     """Run the allocator based on input opcode.
8124
8125     """
8126     # FIXME: add a self.ignore_ipolicy option
8127     ial = IAllocator(self.cfg, self.rpc,
8128                      mode=constants.IALLOCATOR_MODE_RELOC,
8129                      name=self.instance_name,
8130                      relocate_from=[self.instance.primary_node],
8131                      )
8132
8133     ial.Run(self.lu.op.iallocator)
8134
8135     if not ial.success:
8136       raise errors.OpPrereqError("Can't compute nodes using"
8137                                  " iallocator '%s': %s" %
8138                                  (self.lu.op.iallocator, ial.info),
8139                                  errors.ECODE_NORES)
8140     if len(ial.result) != ial.required_nodes:
8141       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
8142                                  " of nodes (%s), required %s" %
8143                                  (self.lu.op.iallocator, len(ial.result),
8144                                   ial.required_nodes), errors.ECODE_FAULT)
8145     self.target_node = ial.result[0]
8146     self.lu.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
8147                  self.instance_name, self.lu.op.iallocator,
8148                  utils.CommaJoin(ial.result))
8149
8150   def _WaitUntilSync(self):
8151     """Poll with custom rpc for disk sync.
8152
8153     This uses our own step-based rpc call.
8154
8155     """
8156     self.feedback_fn("* wait until resync is done")
8157     all_done = False
8158     while not all_done:
8159       all_done = True
8160       result = self.rpc.call_drbd_wait_sync(self.all_nodes,
8161                                             self.nodes_ip,
8162                                             (self.instance.disks,
8163                                              self.instance))
8164       min_percent = 100
8165       for node, nres in result.items():
8166         nres.Raise("Cannot resync disks on node %s" % node)
8167         node_done, node_percent = nres.payload
8168         all_done = all_done and node_done
8169         if node_percent is not None:
8170           min_percent = min(min_percent, node_percent)
8171       if not all_done:
8172         if min_percent < 100:
8173           self.feedback_fn("   - progress: %.1f%%" % min_percent)
8174         time.sleep(2)
8175
8176   def _EnsureSecondary(self, node):
8177     """Demote a node to secondary.
8178
8179     """
8180     self.feedback_fn("* switching node %s to secondary mode" % node)
8181
8182     for dev in self.instance.disks:
8183       self.cfg.SetDiskID(dev, node)
8184
8185     result = self.rpc.call_blockdev_close(node, self.instance.name,
8186                                           self.instance.disks)
8187     result.Raise("Cannot change disk to secondary on node %s" % node)
8188
8189   def _GoStandalone(self):
8190     """Disconnect from the network.
8191
8192     """
8193     self.feedback_fn("* changing into standalone mode")
8194     result = self.rpc.call_drbd_disconnect_net(self.all_nodes, self.nodes_ip,
8195                                                self.instance.disks)
8196     for node, nres in result.items():
8197       nres.Raise("Cannot disconnect disks node %s" % node)
8198
8199   def _GoReconnect(self, multimaster):
8200     """Reconnect to the network.
8201
8202     """
8203     if multimaster:
8204       msg = "dual-master"
8205     else:
8206       msg = "single-master"
8207     self.feedback_fn("* changing disks into %s mode" % msg)
8208     result = self.rpc.call_drbd_attach_net(self.all_nodes, self.nodes_ip,
8209                                            (self.instance.disks, self.instance),
8210                                            self.instance.name, multimaster)
8211     for node, nres in result.items():
8212       nres.Raise("Cannot change disks config on node %s" % node)
8213
8214   def _ExecCleanup(self):
8215     """Try to cleanup after a failed migration.
8216
8217     The cleanup is done by:
8218       - check that the instance is running only on one node
8219         (and update the config if needed)
8220       - change disks on its secondary node to secondary
8221       - wait until disks are fully synchronized
8222       - disconnect from the network
8223       - change disks into single-master mode
8224       - wait again until disks are fully synchronized
8225
8226     """
8227     instance = self.instance
8228     target_node = self.target_node
8229     source_node = self.source_node
8230
8231     # check running on only one node
8232     self.feedback_fn("* checking where the instance actually runs"
8233                      " (if this hangs, the hypervisor might be in"
8234                      " a bad state)")
8235     ins_l = self.rpc.call_instance_list(self.all_nodes, [instance.hypervisor])
8236     for node, result in ins_l.items():
8237       result.Raise("Can't contact node %s" % node)
8238
8239     runningon_source = instance.name in ins_l[source_node].payload
8240     runningon_target = instance.name in ins_l[target_node].payload
8241
8242     if runningon_source and runningon_target:
8243       raise errors.OpExecError("Instance seems to be running on two nodes,"
8244                                " or the hypervisor is confused; you will have"
8245                                " to ensure manually that it runs only on one"
8246                                " and restart this operation")
8247
8248     if not (runningon_source or runningon_target):
8249       raise errors.OpExecError("Instance does not seem to be running at all;"
8250                                " in this case it's safer to repair by"
8251                                " running 'gnt-instance stop' to ensure disk"
8252                                " shutdown, and then restarting it")
8253
8254     if runningon_target:
8255       # the migration has actually succeeded, we need to update the config
8256       self.feedback_fn("* instance running on secondary node (%s),"
8257                        " updating config" % target_node)
8258       instance.primary_node = target_node
8259       self.cfg.Update(instance, self.feedback_fn)
8260       demoted_node = source_node
8261     else:
8262       self.feedback_fn("* instance confirmed to be running on its"
8263                        " primary node (%s)" % source_node)
8264       demoted_node = target_node
8265
8266     if instance.disk_template in constants.DTS_INT_MIRROR:
8267       self._EnsureSecondary(demoted_node)
8268       try:
8269         self._WaitUntilSync()
8270       except errors.OpExecError:
8271         # we ignore here errors, since if the device is standalone, it
8272         # won't be able to sync
8273         pass
8274       self._GoStandalone()
8275       self._GoReconnect(False)
8276       self._WaitUntilSync()
8277
8278     self.feedback_fn("* done")
8279
8280   def _RevertDiskStatus(self):
8281     """Try to revert the disk status after a failed migration.
8282
8283     """
8284     target_node = self.target_node
8285     if self.instance.disk_template in constants.DTS_EXT_MIRROR:
8286       return
8287
8288     try:
8289       self._EnsureSecondary(target_node)
8290       self._GoStandalone()
8291       self._GoReconnect(False)
8292       self._WaitUntilSync()
8293     except errors.OpExecError, err:
8294       self.lu.LogWarning("Migration failed and I can't reconnect the drives,"
8295                          " please try to recover the instance manually;"
8296                          " error '%s'" % str(err))
8297
8298   def _AbortMigration(self):
8299     """Call the hypervisor code to abort a started migration.
8300
8301     """
8302     instance = self.instance
8303     target_node = self.target_node
8304     source_node = self.source_node
8305     migration_info = self.migration_info
8306
8307     abort_result = self.rpc.call_instance_finalize_migration_dst(target_node,
8308                                                                  instance,
8309                                                                  migration_info,
8310                                                                  False)
8311     abort_msg = abort_result.fail_msg
8312     if abort_msg:
8313       logging.error("Aborting migration failed on target node %s: %s",
8314                     target_node, abort_msg)
8315       # Don't raise an exception here, as we stil have to try to revert the
8316       # disk status, even if this step failed.
8317
8318     abort_result = self.rpc.call_instance_finalize_migration_src(source_node,
8319         instance, False, self.live)
8320     abort_msg = abort_result.fail_msg
8321     if abort_msg:
8322       logging.error("Aborting migration failed on source node %s: %s",
8323                     source_node, abort_msg)
8324
8325   def _ExecMigration(self):
8326     """Migrate an instance.
8327
8328     The migrate is done by:
8329       - change the disks into dual-master mode
8330       - wait until disks are fully synchronized again
8331       - migrate the instance
8332       - change disks on the new secondary node (the old primary) to secondary
8333       - wait until disks are fully synchronized
8334       - change disks into single-master mode
8335
8336     """
8337     instance = self.instance
8338     target_node = self.target_node
8339     source_node = self.source_node
8340
8341     # Check for hypervisor version mismatch and warn the user.
8342     nodeinfo = self.rpc.call_node_info([source_node, target_node],
8343                                        None, [self.instance.hypervisor])
8344     for ninfo in nodeinfo.values():
8345       ninfo.Raise("Unable to retrieve node information from node '%s'" %
8346                   ninfo.node)
8347     (_, _, (src_info, )) = nodeinfo[source_node].payload
8348     (_, _, (dst_info, )) = nodeinfo[target_node].payload
8349
8350     if ((constants.HV_NODEINFO_KEY_VERSION in src_info) and
8351         (constants.HV_NODEINFO_KEY_VERSION in dst_info)):
8352       src_version = src_info[constants.HV_NODEINFO_KEY_VERSION]
8353       dst_version = dst_info[constants.HV_NODEINFO_KEY_VERSION]
8354       if src_version != dst_version:
8355         self.feedback_fn("* warning: hypervisor version mismatch between"
8356                          " source (%s) and target (%s) node" %
8357                          (src_version, dst_version))
8358
8359     self.feedback_fn("* checking disk consistency between source and target")
8360     for (idx, dev) in enumerate(instance.disks):
8361       if not _CheckDiskConsistency(self.lu, instance, dev, target_node, False):
8362         raise errors.OpExecError("Disk %s is degraded or not fully"
8363                                  " synchronized on target node,"
8364                                  " aborting migration" % idx)
8365
8366     if self.current_mem > self.tgt_free_mem:
8367       if not self.allow_runtime_changes:
8368         raise errors.OpExecError("Memory ballooning not allowed and not enough"
8369                                  " free memory to fit instance %s on target"
8370                                  " node %s (have %dMB, need %dMB)" %
8371                                  (instance.name, target_node,
8372                                   self.tgt_free_mem, self.current_mem))
8373       self.feedback_fn("* setting instance memory to %s" % self.tgt_free_mem)
8374       rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
8375                                                      instance,
8376                                                      self.tgt_free_mem)
8377       rpcres.Raise("Cannot modify instance runtime memory")
8378
8379     # First get the migration information from the remote node
8380     result = self.rpc.call_migration_info(source_node, instance)
8381     msg = result.fail_msg
8382     if msg:
8383       log_err = ("Failed fetching source migration information from %s: %s" %
8384                  (source_node, msg))
8385       logging.error(log_err)
8386       raise errors.OpExecError(log_err)
8387
8388     self.migration_info = migration_info = result.payload
8389
8390     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
8391       # Then switch the disks to master/master mode
8392       self._EnsureSecondary(target_node)
8393       self._GoStandalone()
8394       self._GoReconnect(True)
8395       self._WaitUntilSync()
8396
8397     self.feedback_fn("* preparing %s to accept the instance" % target_node)
8398     result = self.rpc.call_accept_instance(target_node,
8399                                            instance,
8400                                            migration_info,
8401                                            self.nodes_ip[target_node])
8402
8403     msg = result.fail_msg
8404     if msg:
8405       logging.error("Instance pre-migration failed, trying to revert"
8406                     " disk status: %s", msg)
8407       self.feedback_fn("Pre-migration failed, aborting")
8408       self._AbortMigration()
8409       self._RevertDiskStatus()
8410       raise errors.OpExecError("Could not pre-migrate instance %s: %s" %
8411                                (instance.name, msg))
8412
8413     self.feedback_fn("* migrating instance to %s" % target_node)
8414     result = self.rpc.call_instance_migrate(source_node, instance,
8415                                             self.nodes_ip[target_node],
8416                                             self.live)
8417     msg = result.fail_msg
8418     if msg:
8419       logging.error("Instance migration failed, trying to revert"
8420                     " disk status: %s", msg)
8421       self.feedback_fn("Migration failed, aborting")
8422       self._AbortMigration()
8423       self._RevertDiskStatus()
8424       raise errors.OpExecError("Could not migrate instance %s: %s" %
8425                                (instance.name, msg))
8426
8427     self.feedback_fn("* starting memory transfer")
8428     last_feedback = time.time()
8429     while True:
8430       result = self.rpc.call_instance_get_migration_status(source_node,
8431                                                            instance)
8432       msg = result.fail_msg
8433       ms = result.payload   # MigrationStatus instance
8434       if msg or (ms.status in constants.HV_MIGRATION_FAILED_STATUSES):
8435         logging.error("Instance migration failed, trying to revert"
8436                       " disk status: %s", msg)
8437         self.feedback_fn("Migration failed, aborting")
8438         self._AbortMigration()
8439         self._RevertDiskStatus()
8440         raise errors.OpExecError("Could not migrate instance %s: %s" %
8441                                  (instance.name, msg))
8442
8443       if result.payload.status != constants.HV_MIGRATION_ACTIVE:
8444         self.feedback_fn("* memory transfer complete")
8445         break
8446
8447       if (utils.TimeoutExpired(last_feedback,
8448                                self._MIGRATION_FEEDBACK_INTERVAL) and
8449           ms.transferred_ram is not None):
8450         mem_progress = 100 * float(ms.transferred_ram) / float(ms.total_ram)
8451         self.feedback_fn("* memory transfer progress: %.2f %%" % mem_progress)
8452         last_feedback = time.time()
8453
8454       time.sleep(self._MIGRATION_POLL_INTERVAL)
8455
8456     result = self.rpc.call_instance_finalize_migration_src(source_node,
8457                                                            instance,
8458                                                            True,
8459                                                            self.live)
8460     msg = result.fail_msg
8461     if msg:
8462       logging.error("Instance migration succeeded, but finalization failed"
8463                     " on the source node: %s", msg)
8464       raise errors.OpExecError("Could not finalize instance migration: %s" %
8465                                msg)
8466
8467     instance.primary_node = target_node
8468
8469     # distribute new instance config to the other nodes
8470     self.cfg.Update(instance, self.feedback_fn)
8471
8472     result = self.rpc.call_instance_finalize_migration_dst(target_node,
8473                                                            instance,
8474                                                            migration_info,
8475                                                            True)
8476     msg = result.fail_msg
8477     if msg:
8478       logging.error("Instance migration succeeded, but finalization failed"
8479                     " on the target node: %s", msg)
8480       raise errors.OpExecError("Could not finalize instance migration: %s" %
8481                                msg)
8482
8483     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
8484       self._EnsureSecondary(source_node)
8485       self._WaitUntilSync()
8486       self._GoStandalone()
8487       self._GoReconnect(False)
8488       self._WaitUntilSync()
8489
8490     # If the instance's disk template is `rbd' and there was a successful
8491     # migration, unmap the device from the source node.
8492     if self.instance.disk_template == constants.DT_RBD:
8493       disks = _ExpandCheckDisks(instance, instance.disks)
8494       self.feedback_fn("* unmapping instance's disks from %s" % source_node)
8495       for disk in disks:
8496         result = self.rpc.call_blockdev_shutdown(source_node, (disk, instance))
8497         msg = result.fail_msg
8498         if msg:
8499           logging.error("Migration was successful, but couldn't unmap the"
8500                         " block device %s on source node %s: %s",
8501                         disk.iv_name, source_node, msg)
8502           logging.error("You need to unmap the device %s manually on %s",
8503                         disk.iv_name, source_node)
8504
8505     self.feedback_fn("* done")
8506
8507   def _ExecFailover(self):
8508     """Failover an instance.
8509
8510     The failover is done by shutting it down on its present node and
8511     starting it on the secondary.
8512
8513     """
8514     instance = self.instance
8515     primary_node = self.cfg.GetNodeInfo(instance.primary_node)
8516
8517     source_node = instance.primary_node
8518     target_node = self.target_node
8519
8520     if instance.admin_state == constants.ADMINST_UP:
8521       self.feedback_fn("* checking disk consistency between source and target")
8522       for (idx, dev) in enumerate(instance.disks):
8523         # for drbd, these are drbd over lvm
8524         if not _CheckDiskConsistency(self.lu, instance, dev, target_node,
8525                                      False):
8526           if primary_node.offline:
8527             self.feedback_fn("Node %s is offline, ignoring degraded disk %s on"
8528                              " target node %s" %
8529                              (primary_node.name, idx, target_node))
8530           elif not self.ignore_consistency:
8531             raise errors.OpExecError("Disk %s is degraded on target node,"
8532                                      " aborting failover" % idx)
8533     else:
8534       self.feedback_fn("* not checking disk consistency as instance is not"
8535                        " running")
8536
8537     self.feedback_fn("* shutting down instance on source node")
8538     logging.info("Shutting down instance %s on node %s",
8539                  instance.name, source_node)
8540
8541     result = self.rpc.call_instance_shutdown(source_node, instance,
8542                                              self.shutdown_timeout)
8543     msg = result.fail_msg
8544     if msg:
8545       if self.ignore_consistency or primary_node.offline:
8546         self.lu.LogWarning("Could not shutdown instance %s on node %s,"
8547                            " proceeding anyway; please make sure node"
8548                            " %s is down; error details: %s",
8549                            instance.name, source_node, source_node, msg)
8550       else:
8551         raise errors.OpExecError("Could not shutdown instance %s on"
8552                                  " node %s: %s" %
8553                                  (instance.name, source_node, msg))
8554
8555     self.feedback_fn("* deactivating the instance's disks on source node")
8556     if not _ShutdownInstanceDisks(self.lu, instance, ignore_primary=True):
8557       raise errors.OpExecError("Can't shut down the instance's disks")
8558
8559     instance.primary_node = target_node
8560     # distribute new instance config to the other nodes
8561     self.cfg.Update(instance, self.feedback_fn)
8562
8563     # Only start the instance if it's marked as up
8564     if instance.admin_state == constants.ADMINST_UP:
8565       self.feedback_fn("* activating the instance's disks on target node %s" %
8566                        target_node)
8567       logging.info("Starting instance %s on node %s",
8568                    instance.name, target_node)
8569
8570       disks_ok, _ = _AssembleInstanceDisks(self.lu, instance,
8571                                            ignore_secondaries=True)
8572       if not disks_ok:
8573         _ShutdownInstanceDisks(self.lu, instance)
8574         raise errors.OpExecError("Can't activate the instance's disks")
8575
8576       self.feedback_fn("* starting the instance on the target node %s" %
8577                        target_node)
8578       result = self.rpc.call_instance_start(target_node, (instance, None, None),
8579                                             False)
8580       msg = result.fail_msg
8581       if msg:
8582         _ShutdownInstanceDisks(self.lu, instance)
8583         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
8584                                  (instance.name, target_node, msg))
8585
8586   def Exec(self, feedback_fn):
8587     """Perform the migration.
8588
8589     """
8590     self.feedback_fn = feedback_fn
8591     self.source_node = self.instance.primary_node
8592
8593     # FIXME: if we implement migrate-to-any in DRBD, this needs fixing
8594     if self.instance.disk_template in constants.DTS_INT_MIRROR:
8595       self.target_node = self.instance.secondary_nodes[0]
8596       # Otherwise self.target_node has been populated either
8597       # directly, or through an iallocator.
8598
8599     self.all_nodes = [self.source_node, self.target_node]
8600     self.nodes_ip = dict((name, node.secondary_ip) for (name, node)
8601                          in self.cfg.GetMultiNodeInfo(self.all_nodes))
8602
8603     if self.failover:
8604       feedback_fn("Failover instance %s" % self.instance.name)
8605       self._ExecFailover()
8606     else:
8607       feedback_fn("Migrating instance %s" % self.instance.name)
8608
8609       if self.cleanup:
8610         return self._ExecCleanup()
8611       else:
8612         return self._ExecMigration()
8613
8614
8615 def _CreateBlockDev(lu, node, instance, device, force_create, info,
8616                     force_open):
8617   """Wrapper around L{_CreateBlockDevInner}.
8618
8619   This method annotates the root device first.
8620
8621   """
8622   (disk,) = _AnnotateDiskParams(instance, [device], lu.cfg)
8623   return _CreateBlockDevInner(lu, node, instance, disk, force_create, info,
8624                               force_open)
8625
8626
8627 def _CreateBlockDevInner(lu, node, instance, device, force_create,
8628                          info, force_open):
8629   """Create a tree of block devices on a given node.
8630
8631   If this device type has to be created on secondaries, create it and
8632   all its children.
8633
8634   If not, just recurse to children keeping the same 'force' value.
8635
8636   @attention: The device has to be annotated already.
8637
8638   @param lu: the lu on whose behalf we execute
8639   @param node: the node on which to create the device
8640   @type instance: L{objects.Instance}
8641   @param instance: the instance which owns the device
8642   @type device: L{objects.Disk}
8643   @param device: the device to create
8644   @type force_create: boolean
8645   @param force_create: whether to force creation of this device; this
8646       will be change to True whenever we find a device which has
8647       CreateOnSecondary() attribute
8648   @param info: the extra 'metadata' we should attach to the device
8649       (this will be represented as a LVM tag)
8650   @type force_open: boolean
8651   @param force_open: this parameter will be passes to the
8652       L{backend.BlockdevCreate} function where it specifies
8653       whether we run on primary or not, and it affects both
8654       the child assembly and the device own Open() execution
8655
8656   """
8657   if device.CreateOnSecondary():
8658     force_create = True
8659
8660   if device.children:
8661     for child in device.children:
8662       _CreateBlockDevInner(lu, node, instance, child, force_create,
8663                            info, force_open)
8664
8665   if not force_create:
8666     return
8667
8668   _CreateSingleBlockDev(lu, node, instance, device, info, force_open)
8669
8670
8671 def _CreateSingleBlockDev(lu, node, instance, device, info, force_open):
8672   """Create a single block device on a given node.
8673
8674   This will not recurse over children of the device, so they must be
8675   created in advance.
8676
8677   @param lu: the lu on whose behalf we execute
8678   @param node: the node on which to create the device
8679   @type instance: L{objects.Instance}
8680   @param instance: the instance which owns the device
8681   @type device: L{objects.Disk}
8682   @param device: the device to create
8683   @param info: the extra 'metadata' we should attach to the device
8684       (this will be represented as a LVM tag)
8685   @type force_open: boolean
8686   @param force_open: this parameter will be passes to the
8687       L{backend.BlockdevCreate} function where it specifies
8688       whether we run on primary or not, and it affects both
8689       the child assembly and the device own Open() execution
8690
8691   """
8692   lu.cfg.SetDiskID(device, node)
8693   result = lu.rpc.call_blockdev_create(node, device, device.size,
8694                                        instance.name, force_open, info)
8695   result.Raise("Can't create block device %s on"
8696                " node %s for instance %s" % (device, node, instance.name))
8697   if device.physical_id is None:
8698     device.physical_id = result.payload
8699
8700
8701 def _GenerateUniqueNames(lu, exts):
8702   """Generate a suitable LV name.
8703
8704   This will generate a logical volume name for the given instance.
8705
8706   """
8707   results = []
8708   for val in exts:
8709     new_id = lu.cfg.GenerateUniqueID(lu.proc.GetECId())
8710     results.append("%s%s" % (new_id, val))
8711   return results
8712
8713
8714 def _GenerateDRBD8Branch(lu, primary, secondary, size, vgnames, names,
8715                          iv_name, p_minor, s_minor):
8716   """Generate a drbd8 device complete with its children.
8717
8718   """
8719   assert len(vgnames) == len(names) == 2
8720   port = lu.cfg.AllocatePort()
8721   shared_secret = lu.cfg.GenerateDRBDSecret(lu.proc.GetECId())
8722
8723   dev_data = objects.Disk(dev_type=constants.LD_LV, size=size,
8724                           logical_id=(vgnames[0], names[0]),
8725                           params={})
8726   dev_meta = objects.Disk(dev_type=constants.LD_LV, size=DRBD_META_SIZE,
8727                           logical_id=(vgnames[1], names[1]),
8728                           params={})
8729   drbd_dev = objects.Disk(dev_type=constants.LD_DRBD8, size=size,
8730                           logical_id=(primary, secondary, port,
8731                                       p_minor, s_minor,
8732                                       shared_secret),
8733                           children=[dev_data, dev_meta],
8734                           iv_name=iv_name, params={})
8735   return drbd_dev
8736
8737
8738 _DISK_TEMPLATE_NAME_PREFIX = {
8739   constants.DT_PLAIN: "",
8740   constants.DT_RBD: ".rbd",
8741   }
8742
8743
8744 _DISK_TEMPLATE_DEVICE_TYPE = {
8745   constants.DT_PLAIN: constants.LD_LV,
8746   constants.DT_FILE: constants.LD_FILE,
8747   constants.DT_SHARED_FILE: constants.LD_FILE,
8748   constants.DT_BLOCK: constants.LD_BLOCKDEV,
8749   constants.DT_RBD: constants.LD_RBD,
8750   }
8751
8752
8753 def _GenerateDiskTemplate(lu, template_name, instance_name, primary_node,
8754     secondary_nodes, disk_info, file_storage_dir, file_driver, base_index,
8755     feedback_fn, full_disk_params, _req_file_storage=opcodes.RequireFileStorage,
8756     _req_shr_file_storage=opcodes.RequireSharedFileStorage):
8757   """Generate the entire disk layout for a given template type.
8758
8759   """
8760   #TODO: compute space requirements
8761
8762   vgname = lu.cfg.GetVGName()
8763   disk_count = len(disk_info)
8764   disks = []
8765
8766   if template_name == constants.DT_DISKLESS:
8767     pass
8768   elif template_name == constants.DT_DRBD8:
8769     if len(secondary_nodes) != 1:
8770       raise errors.ProgrammerError("Wrong template configuration")
8771     remote_node = secondary_nodes[0]
8772     minors = lu.cfg.AllocateDRBDMinor(
8773       [primary_node, remote_node] * len(disk_info), instance_name)
8774
8775     (drbd_params, _, _) = objects.Disk.ComputeLDParams(template_name,
8776                                                        full_disk_params)
8777     drbd_default_metavg = drbd_params[constants.LDP_DEFAULT_METAVG]
8778
8779     names = []
8780     for lv_prefix in _GenerateUniqueNames(lu, [".disk%d" % (base_index + i)
8781                                                for i in range(disk_count)]):
8782       names.append(lv_prefix + "_data")
8783       names.append(lv_prefix + "_meta")
8784     for idx, disk in enumerate(disk_info):
8785       disk_index = idx + base_index
8786       data_vg = disk.get(constants.IDISK_VG, vgname)
8787       meta_vg = disk.get(constants.IDISK_METAVG, drbd_default_metavg)
8788       disk_dev = _GenerateDRBD8Branch(lu, primary_node, remote_node,
8789                                       disk[constants.IDISK_SIZE],
8790                                       [data_vg, meta_vg],
8791                                       names[idx * 2:idx * 2 + 2],
8792                                       "disk/%d" % disk_index,
8793                                       minors[idx * 2], minors[idx * 2 + 1])
8794       disk_dev.mode = disk[constants.IDISK_MODE]
8795       disks.append(disk_dev)
8796   else:
8797     if secondary_nodes:
8798       raise errors.ProgrammerError("Wrong template configuration")
8799
8800     if template_name == constants.DT_FILE:
8801       _req_file_storage()
8802     elif template_name == constants.DT_SHARED_FILE:
8803       _req_shr_file_storage()
8804
8805     name_prefix = _DISK_TEMPLATE_NAME_PREFIX.get(template_name, None)
8806     if name_prefix is None:
8807       names = None
8808     else:
8809       names = _GenerateUniqueNames(lu, ["%s.disk%s" %
8810                                         (name_prefix, base_index + i)
8811                                         for i in range(disk_count)])
8812
8813     if template_name == constants.DT_PLAIN:
8814       def logical_id_fn(idx, _, disk):
8815         vg = disk.get(constants.IDISK_VG, vgname)
8816         return (vg, names[idx])
8817     elif template_name in (constants.DT_FILE, constants.DT_SHARED_FILE):
8818       logical_id_fn = \
8819         lambda _, disk_index, disk: (file_driver,
8820                                      "%s/disk%d" % (file_storage_dir,
8821                                                     disk_index))
8822     elif template_name == constants.DT_BLOCK:
8823       logical_id_fn = \
8824         lambda idx, disk_index, disk: (constants.BLOCKDEV_DRIVER_MANUAL,
8825                                        disk[constants.IDISK_ADOPT])
8826     elif template_name == constants.DT_RBD:
8827       logical_id_fn = lambda idx, _, disk: ("rbd", names[idx])
8828     else:
8829       raise errors.ProgrammerError("Unknown disk template '%s'" % template_name)
8830
8831     dev_type = _DISK_TEMPLATE_DEVICE_TYPE[template_name]
8832
8833     for idx, disk in enumerate(disk_info):
8834       disk_index = idx + base_index
8835       size = disk[constants.IDISK_SIZE]
8836       feedback_fn("* disk %s, size %s" %
8837                   (disk_index, utils.FormatUnit(size, "h")))
8838       disks.append(objects.Disk(dev_type=dev_type, size=size,
8839                                 logical_id=logical_id_fn(idx, disk_index, disk),
8840                                 iv_name="disk/%d" % disk_index,
8841                                 mode=disk[constants.IDISK_MODE],
8842                                 params={}))
8843
8844   return disks
8845
8846
8847 def _GetInstanceInfoText(instance):
8848   """Compute that text that should be added to the disk's metadata.
8849
8850   """
8851   return "originstname+%s" % instance.name
8852
8853
8854 def _CalcEta(time_taken, written, total_size):
8855   """Calculates the ETA based on size written and total size.
8856
8857   @param time_taken: The time taken so far
8858   @param written: amount written so far
8859   @param total_size: The total size of data to be written
8860   @return: The remaining time in seconds
8861
8862   """
8863   avg_time = time_taken / float(written)
8864   return (total_size - written) * avg_time
8865
8866
8867 def _WipeDisks(lu, instance):
8868   """Wipes instance disks.
8869
8870   @type lu: L{LogicalUnit}
8871   @param lu: the logical unit on whose behalf we execute
8872   @type instance: L{objects.Instance}
8873   @param instance: the instance whose disks we should create
8874   @return: the success of the wipe
8875
8876   """
8877   node = instance.primary_node
8878
8879   for device in instance.disks:
8880     lu.cfg.SetDiskID(device, node)
8881
8882   logging.info("Pause sync of instance %s disks", instance.name)
8883   result = lu.rpc.call_blockdev_pause_resume_sync(node,
8884                                                   (instance.disks, instance),
8885                                                   True)
8886
8887   for idx, success in enumerate(result.payload):
8888     if not success:
8889       logging.warn("pause-sync of instance %s for disks %d failed",
8890                    instance.name, idx)
8891
8892   try:
8893     for idx, device in enumerate(instance.disks):
8894       # The wipe size is MIN_WIPE_CHUNK_PERCENT % of the instance disk but
8895       # MAX_WIPE_CHUNK at max
8896       wipe_chunk_size = min(constants.MAX_WIPE_CHUNK, device.size / 100.0 *
8897                             constants.MIN_WIPE_CHUNK_PERCENT)
8898       # we _must_ make this an int, otherwise rounding errors will
8899       # occur
8900       wipe_chunk_size = int(wipe_chunk_size)
8901
8902       lu.LogInfo("* Wiping disk %d", idx)
8903       logging.info("Wiping disk %d for instance %s, node %s using"
8904                    " chunk size %s", idx, instance.name, node, wipe_chunk_size)
8905
8906       offset = 0
8907       size = device.size
8908       last_output = 0
8909       start_time = time.time()
8910
8911       while offset < size:
8912         wipe_size = min(wipe_chunk_size, size - offset)
8913         logging.debug("Wiping disk %d, offset %s, chunk %s",
8914                       idx, offset, wipe_size)
8915         result = lu.rpc.call_blockdev_wipe(node, (device, instance), offset,
8916                                            wipe_size)
8917         result.Raise("Could not wipe disk %d at offset %d for size %d" %
8918                      (idx, offset, wipe_size))
8919         now = time.time()
8920         offset += wipe_size
8921         if now - last_output >= 60:
8922           eta = _CalcEta(now - start_time, offset, size)
8923           lu.LogInfo(" - done: %.1f%% ETA: %s" %
8924                      (offset / float(size) * 100, utils.FormatSeconds(eta)))
8925           last_output = now
8926   finally:
8927     logging.info("Resume sync of instance %s disks", instance.name)
8928
8929     result = lu.rpc.call_blockdev_pause_resume_sync(node,
8930                                                     (instance.disks, instance),
8931                                                     False)
8932
8933     for idx, success in enumerate(result.payload):
8934       if not success:
8935         lu.LogWarning("Resume sync of disk %d failed, please have a"
8936                       " look at the status and troubleshoot the issue", idx)
8937         logging.warn("resume-sync of instance %s for disks %d failed",
8938                      instance.name, idx)
8939
8940
8941 def _CreateDisks(lu, instance, to_skip=None, target_node=None):
8942   """Create all disks for an instance.
8943
8944   This abstracts away some work from AddInstance.
8945
8946   @type lu: L{LogicalUnit}
8947   @param lu: the logical unit on whose behalf we execute
8948   @type instance: L{objects.Instance}
8949   @param instance: the instance whose disks we should create
8950   @type to_skip: list
8951   @param to_skip: list of indices to skip
8952   @type target_node: string
8953   @param target_node: if passed, overrides the target node for creation
8954   @rtype: boolean
8955   @return: the success of the creation
8956
8957   """
8958   info = _GetInstanceInfoText(instance)
8959   if target_node is None:
8960     pnode = instance.primary_node
8961     all_nodes = instance.all_nodes
8962   else:
8963     pnode = target_node
8964     all_nodes = [pnode]
8965
8966   if instance.disk_template in constants.DTS_FILEBASED:
8967     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
8968     result = lu.rpc.call_file_storage_dir_create(pnode, file_storage_dir)
8969
8970     result.Raise("Failed to create directory '%s' on"
8971                  " node %s" % (file_storage_dir, pnode))
8972
8973   # Note: this needs to be kept in sync with adding of disks in
8974   # LUInstanceSetParams
8975   for idx, device in enumerate(instance.disks):
8976     if to_skip and idx in to_skip:
8977       continue
8978     logging.info("Creating disk %s for instance '%s'", idx, instance.name)
8979     #HARDCODE
8980     for node in all_nodes:
8981       f_create = node == pnode
8982       _CreateBlockDev(lu, node, instance, device, f_create, info, f_create)
8983
8984
8985 def _RemoveDisks(lu, instance, target_node=None, ignore_failures=False):
8986   """Remove all disks for an instance.
8987
8988   This abstracts away some work from `AddInstance()` and
8989   `RemoveInstance()`. Note that in case some of the devices couldn't
8990   be removed, the removal will continue with the other ones (compare
8991   with `_CreateDisks()`).
8992
8993   @type lu: L{LogicalUnit}
8994   @param lu: the logical unit on whose behalf we execute
8995   @type instance: L{objects.Instance}
8996   @param instance: the instance whose disks we should remove
8997   @type target_node: string
8998   @param target_node: used to override the node on which to remove the disks
8999   @rtype: boolean
9000   @return: the success of the removal
9001
9002   """
9003   logging.info("Removing block devices for instance %s", instance.name)
9004
9005   all_result = True
9006   ports_to_release = set()
9007   anno_disks = _AnnotateDiskParams(instance, instance.disks, lu.cfg)
9008   for (idx, device) in enumerate(anno_disks):
9009     if target_node:
9010       edata = [(target_node, device)]
9011     else:
9012       edata = device.ComputeNodeTree(instance.primary_node)
9013     for node, disk in edata:
9014       lu.cfg.SetDiskID(disk, node)
9015       msg = lu.rpc.call_blockdev_remove(node, disk).fail_msg
9016       if msg:
9017         lu.LogWarning("Could not remove disk %s on node %s,"
9018                       " continuing anyway: %s", idx, node, msg)
9019         all_result = False
9020
9021     # if this is a DRBD disk, return its port to the pool
9022     if device.dev_type in constants.LDS_DRBD:
9023       ports_to_release.add(device.logical_id[2])
9024
9025   if all_result or ignore_failures:
9026     for port in ports_to_release:
9027       lu.cfg.AddTcpUdpPort(port)
9028
9029   if instance.disk_template == constants.DT_FILE:
9030     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
9031     if target_node:
9032       tgt = target_node
9033     else:
9034       tgt = instance.primary_node
9035     result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
9036     if result.fail_msg:
9037       lu.LogWarning("Could not remove directory '%s' on node %s: %s",
9038                     file_storage_dir, instance.primary_node, result.fail_msg)
9039       all_result = False
9040
9041   return all_result
9042
9043
9044 def _ComputeDiskSizePerVG(disk_template, disks):
9045   """Compute disk size requirements in the volume group
9046
9047   """
9048   def _compute(disks, payload):
9049     """Universal algorithm.
9050
9051     """
9052     vgs = {}
9053     for disk in disks:
9054       vgs[disk[constants.IDISK_VG]] = \
9055         vgs.get(constants.IDISK_VG, 0) + disk[constants.IDISK_SIZE] + payload
9056
9057     return vgs
9058
9059   # Required free disk space as a function of disk and swap space
9060   req_size_dict = {
9061     constants.DT_DISKLESS: {},
9062     constants.DT_PLAIN: _compute(disks, 0),
9063     # 128 MB are added for drbd metadata for each disk
9064     constants.DT_DRBD8: _compute(disks, DRBD_META_SIZE),
9065     constants.DT_FILE: {},
9066     constants.DT_SHARED_FILE: {},
9067   }
9068
9069   if disk_template not in req_size_dict:
9070     raise errors.ProgrammerError("Disk template '%s' size requirement"
9071                                  " is unknown" % disk_template)
9072
9073   return req_size_dict[disk_template]
9074
9075
9076 def _ComputeDiskSize(disk_template, disks):
9077   """Compute disk size requirements in the volume group
9078
9079   """
9080   # Required free disk space as a function of disk and swap space
9081   req_size_dict = {
9082     constants.DT_DISKLESS: None,
9083     constants.DT_PLAIN: sum(d[constants.IDISK_SIZE] for d in disks),
9084     # 128 MB are added for drbd metadata for each disk
9085     constants.DT_DRBD8:
9086       sum(d[constants.IDISK_SIZE] + DRBD_META_SIZE for d in disks),
9087     constants.DT_FILE: None,
9088     constants.DT_SHARED_FILE: 0,
9089     constants.DT_BLOCK: 0,
9090     constants.DT_RBD: 0,
9091   }
9092
9093   if disk_template not in req_size_dict:
9094     raise errors.ProgrammerError("Disk template '%s' size requirement"
9095                                  " is unknown" % disk_template)
9096
9097   return req_size_dict[disk_template]
9098
9099
9100 def _FilterVmNodes(lu, nodenames):
9101   """Filters out non-vm_capable nodes from a list.
9102
9103   @type lu: L{LogicalUnit}
9104   @param lu: the logical unit for which we check
9105   @type nodenames: list
9106   @param nodenames: the list of nodes on which we should check
9107   @rtype: list
9108   @return: the list of vm-capable nodes
9109
9110   """
9111   vm_nodes = frozenset(lu.cfg.GetNonVmCapableNodeList())
9112   return [name for name in nodenames if name not in vm_nodes]
9113
9114
9115 def _CheckHVParams(lu, nodenames, hvname, hvparams):
9116   """Hypervisor parameter validation.
9117
9118   This function abstract the hypervisor parameter validation to be
9119   used in both instance create and instance modify.
9120
9121   @type lu: L{LogicalUnit}
9122   @param lu: the logical unit for which we check
9123   @type nodenames: list
9124   @param nodenames: the list of nodes on which we should check
9125   @type hvname: string
9126   @param hvname: the name of the hypervisor we should use
9127   @type hvparams: dict
9128   @param hvparams: the parameters which we need to check
9129   @raise errors.OpPrereqError: if the parameters are not valid
9130
9131   """
9132   nodenames = _FilterVmNodes(lu, nodenames)
9133
9134   cluster = lu.cfg.GetClusterInfo()
9135   hvfull = objects.FillDict(cluster.hvparams.get(hvname, {}), hvparams)
9136
9137   hvinfo = lu.rpc.call_hypervisor_validate_params(nodenames, hvname, hvfull)
9138   for node in nodenames:
9139     info = hvinfo[node]
9140     if info.offline:
9141       continue
9142     info.Raise("Hypervisor parameter validation failed on node %s" % node)
9143
9144
9145 def _CheckOSParams(lu, required, nodenames, osname, osparams):
9146   """OS parameters validation.
9147
9148   @type lu: L{LogicalUnit}
9149   @param lu: the logical unit for which we check
9150   @type required: boolean
9151   @param required: whether the validation should fail if the OS is not
9152       found
9153   @type nodenames: list
9154   @param nodenames: the list of nodes on which we should check
9155   @type osname: string
9156   @param osname: the name of the hypervisor we should use
9157   @type osparams: dict
9158   @param osparams: the parameters which we need to check
9159   @raise errors.OpPrereqError: if the parameters are not valid
9160
9161   """
9162   nodenames = _FilterVmNodes(lu, nodenames)
9163   result = lu.rpc.call_os_validate(nodenames, required, osname,
9164                                    [constants.OS_VALIDATE_PARAMETERS],
9165                                    osparams)
9166   for node, nres in result.items():
9167     # we don't check for offline cases since this should be run only
9168     # against the master node and/or an instance's nodes
9169     nres.Raise("OS Parameters validation failed on node %s" % node)
9170     if not nres.payload:
9171       lu.LogInfo("OS %s not found on node %s, validation skipped",
9172                  osname, node)
9173
9174
9175 class LUInstanceCreate(LogicalUnit):
9176   """Create an instance.
9177
9178   """
9179   HPATH = "instance-add"
9180   HTYPE = constants.HTYPE_INSTANCE
9181   REQ_BGL = False
9182
9183   def CheckArguments(self):
9184     """Check arguments.
9185
9186     """
9187     # do not require name_check to ease forward/backward compatibility
9188     # for tools
9189     if self.op.no_install and self.op.start:
9190       self.LogInfo("No-installation mode selected, disabling startup")
9191       self.op.start = False
9192     # validate/normalize the instance name
9193     self.op.instance_name = \
9194       netutils.Hostname.GetNormalizedName(self.op.instance_name)
9195
9196     if self.op.ip_check and not self.op.name_check:
9197       # TODO: make the ip check more flexible and not depend on the name check
9198       raise errors.OpPrereqError("Cannot do IP address check without a name"
9199                                  " check", errors.ECODE_INVAL)
9200
9201     # check nics' parameter names
9202     for nic in self.op.nics:
9203       utils.ForceDictType(nic, constants.INIC_PARAMS_TYPES)
9204
9205     # check disks. parameter names and consistent adopt/no-adopt strategy
9206     has_adopt = has_no_adopt = False
9207     for disk in self.op.disks:
9208       utils.ForceDictType(disk, constants.IDISK_PARAMS_TYPES)
9209       if constants.IDISK_ADOPT in disk:
9210         has_adopt = True
9211       else:
9212         has_no_adopt = True
9213     if has_adopt and has_no_adopt:
9214       raise errors.OpPrereqError("Either all disks are adopted or none is",
9215                                  errors.ECODE_INVAL)
9216     if has_adopt:
9217       if self.op.disk_template not in constants.DTS_MAY_ADOPT:
9218         raise errors.OpPrereqError("Disk adoption is not supported for the"
9219                                    " '%s' disk template" %
9220                                    self.op.disk_template,
9221                                    errors.ECODE_INVAL)
9222       if self.op.iallocator is not None:
9223         raise errors.OpPrereqError("Disk adoption not allowed with an"
9224                                    " iallocator script", errors.ECODE_INVAL)
9225       if self.op.mode == constants.INSTANCE_IMPORT:
9226         raise errors.OpPrereqError("Disk adoption not allowed for"
9227                                    " instance import", errors.ECODE_INVAL)
9228     else:
9229       if self.op.disk_template in constants.DTS_MUST_ADOPT:
9230         raise errors.OpPrereqError("Disk template %s requires disk adoption,"
9231                                    " but no 'adopt' parameter given" %
9232                                    self.op.disk_template,
9233                                    errors.ECODE_INVAL)
9234
9235     self.adopt_disks = has_adopt
9236
9237     # instance name verification
9238     if self.op.name_check:
9239       self.hostname1 = netutils.GetHostname(name=self.op.instance_name)
9240       self.op.instance_name = self.hostname1.name
9241       # used in CheckPrereq for ip ping check
9242       self.check_ip = self.hostname1.ip
9243     else:
9244       self.check_ip = None
9245
9246     # file storage checks
9247     if (self.op.file_driver and
9248         not self.op.file_driver in constants.FILE_DRIVER):
9249       raise errors.OpPrereqError("Invalid file driver name '%s'" %
9250                                  self.op.file_driver, errors.ECODE_INVAL)
9251
9252     if self.op.disk_template == constants.DT_FILE:
9253       opcodes.RequireFileStorage()
9254     elif self.op.disk_template == constants.DT_SHARED_FILE:
9255       opcodes.RequireSharedFileStorage()
9256
9257     ### Node/iallocator related checks
9258     _CheckIAllocatorOrNode(self, "iallocator", "pnode")
9259
9260     if self.op.pnode is not None:
9261       if self.op.disk_template in constants.DTS_INT_MIRROR:
9262         if self.op.snode is None:
9263           raise errors.OpPrereqError("The networked disk templates need"
9264                                      " a mirror node", errors.ECODE_INVAL)
9265       elif self.op.snode:
9266         self.LogWarning("Secondary node will be ignored on non-mirrored disk"
9267                         " template")
9268         self.op.snode = None
9269
9270     self._cds = _GetClusterDomainSecret()
9271
9272     if self.op.mode == constants.INSTANCE_IMPORT:
9273       # On import force_variant must be True, because if we forced it at
9274       # initial install, our only chance when importing it back is that it
9275       # works again!
9276       self.op.force_variant = True
9277
9278       if self.op.no_install:
9279         self.LogInfo("No-installation mode has no effect during import")
9280
9281     elif self.op.mode == constants.INSTANCE_CREATE:
9282       if self.op.os_type is None:
9283         raise errors.OpPrereqError("No guest OS specified",
9284                                    errors.ECODE_INVAL)
9285       if self.op.os_type in self.cfg.GetClusterInfo().blacklisted_os:
9286         raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
9287                                    " installation" % self.op.os_type,
9288                                    errors.ECODE_STATE)
9289       if self.op.disk_template is None:
9290         raise errors.OpPrereqError("No disk template specified",
9291                                    errors.ECODE_INVAL)
9292
9293     elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
9294       # Check handshake to ensure both clusters have the same domain secret
9295       src_handshake = self.op.source_handshake
9296       if not src_handshake:
9297         raise errors.OpPrereqError("Missing source handshake",
9298                                    errors.ECODE_INVAL)
9299
9300       errmsg = masterd.instance.CheckRemoteExportHandshake(self._cds,
9301                                                            src_handshake)
9302       if errmsg:
9303         raise errors.OpPrereqError("Invalid handshake: %s" % errmsg,
9304                                    errors.ECODE_INVAL)
9305
9306       # Load and check source CA
9307       self.source_x509_ca_pem = self.op.source_x509_ca
9308       if not self.source_x509_ca_pem:
9309         raise errors.OpPrereqError("Missing source X509 CA",
9310                                    errors.ECODE_INVAL)
9311
9312       try:
9313         (cert, _) = utils.LoadSignedX509Certificate(self.source_x509_ca_pem,
9314                                                     self._cds)
9315       except OpenSSL.crypto.Error, err:
9316         raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
9317                                    (err, ), errors.ECODE_INVAL)
9318
9319       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
9320       if errcode is not None:
9321         raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
9322                                    errors.ECODE_INVAL)
9323
9324       self.source_x509_ca = cert
9325
9326       src_instance_name = self.op.source_instance_name
9327       if not src_instance_name:
9328         raise errors.OpPrereqError("Missing source instance name",
9329                                    errors.ECODE_INVAL)
9330
9331       self.source_instance_name = \
9332           netutils.GetHostname(name=src_instance_name).name
9333
9334     else:
9335       raise errors.OpPrereqError("Invalid instance creation mode %r" %
9336                                  self.op.mode, errors.ECODE_INVAL)
9337
9338   def ExpandNames(self):
9339     """ExpandNames for CreateInstance.
9340
9341     Figure out the right locks for instance creation.
9342
9343     """
9344     self.needed_locks = {}
9345
9346     instance_name = self.op.instance_name
9347     # this is just a preventive check, but someone might still add this
9348     # instance in the meantime, and creation will fail at lock-add time
9349     if instance_name in self.cfg.GetInstanceList():
9350       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
9351                                  instance_name, errors.ECODE_EXISTS)
9352
9353     self.add_locks[locking.LEVEL_INSTANCE] = instance_name
9354
9355     if self.op.iallocator:
9356       # TODO: Find a solution to not lock all nodes in the cluster, e.g. by
9357       # specifying a group on instance creation and then selecting nodes from
9358       # that group
9359       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9360       self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
9361     else:
9362       self.op.pnode = _ExpandNodeName(self.cfg, self.op.pnode)
9363       nodelist = [self.op.pnode]
9364       if self.op.snode is not None:
9365         self.op.snode = _ExpandNodeName(self.cfg, self.op.snode)
9366         nodelist.append(self.op.snode)
9367       self.needed_locks[locking.LEVEL_NODE] = nodelist
9368       # Lock resources of instance's primary and secondary nodes (copy to
9369       # prevent accidential modification)
9370       self.needed_locks[locking.LEVEL_NODE_RES] = list(nodelist)
9371
9372     # in case of import lock the source node too
9373     if self.op.mode == constants.INSTANCE_IMPORT:
9374       src_node = self.op.src_node
9375       src_path = self.op.src_path
9376
9377       if src_path is None:
9378         self.op.src_path = src_path = self.op.instance_name
9379
9380       if src_node is None:
9381         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9382         self.op.src_node = None
9383         if os.path.isabs(src_path):
9384           raise errors.OpPrereqError("Importing an instance from a path"
9385                                      " requires a source node option",
9386                                      errors.ECODE_INVAL)
9387       else:
9388         self.op.src_node = src_node = _ExpandNodeName(self.cfg, src_node)
9389         if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
9390           self.needed_locks[locking.LEVEL_NODE].append(src_node)
9391         if not os.path.isabs(src_path):
9392           self.op.src_path = src_path = \
9393             utils.PathJoin(constants.EXPORT_DIR, src_path)
9394
9395   def _RunAllocator(self):
9396     """Run the allocator based on input opcode.
9397
9398     """
9399     nics = [n.ToDict() for n in self.nics]
9400     ial = IAllocator(self.cfg, self.rpc,
9401                      mode=constants.IALLOCATOR_MODE_ALLOC,
9402                      name=self.op.instance_name,
9403                      disk_template=self.op.disk_template,
9404                      tags=self.op.tags,
9405                      os=self.op.os_type,
9406                      vcpus=self.be_full[constants.BE_VCPUS],
9407                      memory=self.be_full[constants.BE_MAXMEM],
9408                      spindle_use=self.be_full[constants.BE_SPINDLE_USE],
9409                      disks=self.disks,
9410                      nics=nics,
9411                      hypervisor=self.op.hypervisor,
9412                      )
9413
9414     ial.Run(self.op.iallocator)
9415
9416     if not ial.success:
9417       raise errors.OpPrereqError("Can't compute nodes using"
9418                                  " iallocator '%s': %s" %
9419                                  (self.op.iallocator, ial.info),
9420                                  errors.ECODE_NORES)
9421     if len(ial.result) != ial.required_nodes:
9422       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
9423                                  " of nodes (%s), required %s" %
9424                                  (self.op.iallocator, len(ial.result),
9425                                   ial.required_nodes), errors.ECODE_FAULT)
9426     self.op.pnode = ial.result[0]
9427     self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
9428                  self.op.instance_name, self.op.iallocator,
9429                  utils.CommaJoin(ial.result))
9430     if ial.required_nodes == 2:
9431       self.op.snode = ial.result[1]
9432
9433   def BuildHooksEnv(self):
9434     """Build hooks env.
9435
9436     This runs on master, primary and secondary nodes of the instance.
9437
9438     """
9439     env = {
9440       "ADD_MODE": self.op.mode,
9441       }
9442     if self.op.mode == constants.INSTANCE_IMPORT:
9443       env["SRC_NODE"] = self.op.src_node
9444       env["SRC_PATH"] = self.op.src_path
9445       env["SRC_IMAGES"] = self.src_images
9446
9447     env.update(_BuildInstanceHookEnv(
9448       name=self.op.instance_name,
9449       primary_node=self.op.pnode,
9450       secondary_nodes=self.secondaries,
9451       status=self.op.start,
9452       os_type=self.op.os_type,
9453       minmem=self.be_full[constants.BE_MINMEM],
9454       maxmem=self.be_full[constants.BE_MAXMEM],
9455       vcpus=self.be_full[constants.BE_VCPUS],
9456       nics=_NICListToTuple(self, self.nics),
9457       disk_template=self.op.disk_template,
9458       disks=[(d[constants.IDISK_SIZE], d[constants.IDISK_MODE])
9459              for d in self.disks],
9460       bep=self.be_full,
9461       hvp=self.hv_full,
9462       hypervisor_name=self.op.hypervisor,
9463       tags=self.op.tags,
9464     ))
9465
9466     return env
9467
9468   def BuildHooksNodes(self):
9469     """Build hooks nodes.
9470
9471     """
9472     nl = [self.cfg.GetMasterNode(), self.op.pnode] + self.secondaries
9473     return nl, nl
9474
9475   def _ReadExportInfo(self):
9476     """Reads the export information from disk.
9477
9478     It will override the opcode source node and path with the actual
9479     information, if these two were not specified before.
9480
9481     @return: the export information
9482
9483     """
9484     assert self.op.mode == constants.INSTANCE_IMPORT
9485
9486     src_node = self.op.src_node
9487     src_path = self.op.src_path
9488
9489     if src_node is None:
9490       locked_nodes = self.owned_locks(locking.LEVEL_NODE)
9491       exp_list = self.rpc.call_export_list(locked_nodes)
9492       found = False
9493       for node in exp_list:
9494         if exp_list[node].fail_msg:
9495           continue
9496         if src_path in exp_list[node].payload:
9497           found = True
9498           self.op.src_node = src_node = node
9499           self.op.src_path = src_path = utils.PathJoin(constants.EXPORT_DIR,
9500                                                        src_path)
9501           break
9502       if not found:
9503         raise errors.OpPrereqError("No export found for relative path %s" %
9504                                     src_path, errors.ECODE_INVAL)
9505
9506     _CheckNodeOnline(self, src_node)
9507     result = self.rpc.call_export_info(src_node, src_path)
9508     result.Raise("No export or invalid export found in dir %s" % src_path)
9509
9510     export_info = objects.SerializableConfigParser.Loads(str(result.payload))
9511     if not export_info.has_section(constants.INISECT_EXP):
9512       raise errors.ProgrammerError("Corrupted export config",
9513                                    errors.ECODE_ENVIRON)
9514
9515     ei_version = export_info.get(constants.INISECT_EXP, "version")
9516     if (int(ei_version) != constants.EXPORT_VERSION):
9517       raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
9518                                  (ei_version, constants.EXPORT_VERSION),
9519                                  errors.ECODE_ENVIRON)
9520     return export_info
9521
9522   def _ReadExportParams(self, einfo):
9523     """Use export parameters as defaults.
9524
9525     In case the opcode doesn't specify (as in override) some instance
9526     parameters, then try to use them from the export information, if
9527     that declares them.
9528
9529     """
9530     self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
9531
9532     if self.op.disk_template is None:
9533       if einfo.has_option(constants.INISECT_INS, "disk_template"):
9534         self.op.disk_template = einfo.get(constants.INISECT_INS,
9535                                           "disk_template")
9536         if self.op.disk_template not in constants.DISK_TEMPLATES:
9537           raise errors.OpPrereqError("Disk template specified in configuration"
9538                                      " file is not one of the allowed values:"
9539                                      " %s" % " ".join(constants.DISK_TEMPLATES))
9540       else:
9541         raise errors.OpPrereqError("No disk template specified and the export"
9542                                    " is missing the disk_template information",
9543                                    errors.ECODE_INVAL)
9544
9545     if not self.op.disks:
9546       disks = []
9547       # TODO: import the disk iv_name too
9548       for idx in range(constants.MAX_DISKS):
9549         if einfo.has_option(constants.INISECT_INS, "disk%d_size" % idx):
9550           disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
9551           disks.append({constants.IDISK_SIZE: disk_sz})
9552       self.op.disks = disks
9553       if not disks and self.op.disk_template != constants.DT_DISKLESS:
9554         raise errors.OpPrereqError("No disk info specified and the export"
9555                                    " is missing the disk information",
9556                                    errors.ECODE_INVAL)
9557
9558     if not self.op.nics:
9559       nics = []
9560       for idx in range(constants.MAX_NICS):
9561         if einfo.has_option(constants.INISECT_INS, "nic%d_mac" % idx):
9562           ndict = {}
9563           for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
9564             v = einfo.get(constants.INISECT_INS, "nic%d_%s" % (idx, name))
9565             ndict[name] = v
9566           nics.append(ndict)
9567         else:
9568           break
9569       self.op.nics = nics
9570
9571     if not self.op.tags and einfo.has_option(constants.INISECT_INS, "tags"):
9572       self.op.tags = einfo.get(constants.INISECT_INS, "tags").split()
9573
9574     if (self.op.hypervisor is None and
9575         einfo.has_option(constants.INISECT_INS, "hypervisor")):
9576       self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
9577
9578     if einfo.has_section(constants.INISECT_HYP):
9579       # use the export parameters but do not override the ones
9580       # specified by the user
9581       for name, value in einfo.items(constants.INISECT_HYP):
9582         if name not in self.op.hvparams:
9583           self.op.hvparams[name] = value
9584
9585     if einfo.has_section(constants.INISECT_BEP):
9586       # use the parameters, without overriding
9587       for name, value in einfo.items(constants.INISECT_BEP):
9588         if name not in self.op.beparams:
9589           self.op.beparams[name] = value
9590         # Compatibility for the old "memory" be param
9591         if name == constants.BE_MEMORY:
9592           if constants.BE_MAXMEM not in self.op.beparams:
9593             self.op.beparams[constants.BE_MAXMEM] = value
9594           if constants.BE_MINMEM not in self.op.beparams:
9595             self.op.beparams[constants.BE_MINMEM] = value
9596     else:
9597       # try to read the parameters old style, from the main section
9598       for name in constants.BES_PARAMETERS:
9599         if (name not in self.op.beparams and
9600             einfo.has_option(constants.INISECT_INS, name)):
9601           self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
9602
9603     if einfo.has_section(constants.INISECT_OSP):
9604       # use the parameters, without overriding
9605       for name, value in einfo.items(constants.INISECT_OSP):
9606         if name not in self.op.osparams:
9607           self.op.osparams[name] = value
9608
9609   def _RevertToDefaults(self, cluster):
9610     """Revert the instance parameters to the default values.
9611
9612     """
9613     # hvparams
9614     hv_defs = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type, {})
9615     for name in self.op.hvparams.keys():
9616       if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
9617         del self.op.hvparams[name]
9618     # beparams
9619     be_defs = cluster.SimpleFillBE({})
9620     for name in self.op.beparams.keys():
9621       if name in be_defs and be_defs[name] == self.op.beparams[name]:
9622         del self.op.beparams[name]
9623     # nic params
9624     nic_defs = cluster.SimpleFillNIC({})
9625     for nic in self.op.nics:
9626       for name in constants.NICS_PARAMETERS:
9627         if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
9628           del nic[name]
9629     # osparams
9630     os_defs = cluster.SimpleFillOS(self.op.os_type, {})
9631     for name in self.op.osparams.keys():
9632       if name in os_defs and os_defs[name] == self.op.osparams[name]:
9633         del self.op.osparams[name]
9634
9635   def _CalculateFileStorageDir(self):
9636     """Calculate final instance file storage dir.
9637
9638     """
9639     # file storage dir calculation/check
9640     self.instance_file_storage_dir = None
9641     if self.op.disk_template in constants.DTS_FILEBASED:
9642       # build the full file storage dir path
9643       joinargs = []
9644
9645       if self.op.disk_template == constants.DT_SHARED_FILE:
9646         get_fsd_fn = self.cfg.GetSharedFileStorageDir
9647       else:
9648         get_fsd_fn = self.cfg.GetFileStorageDir
9649
9650       cfg_storagedir = get_fsd_fn()
9651       if not cfg_storagedir:
9652         raise errors.OpPrereqError("Cluster file storage dir not defined")
9653       joinargs.append(cfg_storagedir)
9654
9655       if self.op.file_storage_dir is not None:
9656         joinargs.append(self.op.file_storage_dir)
9657
9658       joinargs.append(self.op.instance_name)
9659
9660       # pylint: disable=W0142
9661       self.instance_file_storage_dir = utils.PathJoin(*joinargs)
9662
9663   def CheckPrereq(self): # pylint: disable=R0914
9664     """Check prerequisites.
9665
9666     """
9667     self._CalculateFileStorageDir()
9668
9669     if self.op.mode == constants.INSTANCE_IMPORT:
9670       export_info = self._ReadExportInfo()
9671       self._ReadExportParams(export_info)
9672       self._old_instance_name = export_info.get(constants.INISECT_INS, "name")
9673     else:
9674       self._old_instance_name = None
9675
9676     if (not self.cfg.GetVGName() and
9677         self.op.disk_template not in constants.DTS_NOT_LVM):
9678       raise errors.OpPrereqError("Cluster does not support lvm-based"
9679                                  " instances", errors.ECODE_STATE)
9680
9681     if (self.op.hypervisor is None or
9682         self.op.hypervisor == constants.VALUE_AUTO):
9683       self.op.hypervisor = self.cfg.GetHypervisorType()
9684
9685     cluster = self.cfg.GetClusterInfo()
9686     enabled_hvs = cluster.enabled_hypervisors
9687     if self.op.hypervisor not in enabled_hvs:
9688       raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
9689                                  " cluster (%s)" % (self.op.hypervisor,
9690                                   ",".join(enabled_hvs)),
9691                                  errors.ECODE_STATE)
9692
9693     # Check tag validity
9694     for tag in self.op.tags:
9695       objects.TaggableObject.ValidateTag(tag)
9696
9697     # check hypervisor parameter syntax (locally)
9698     utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
9699     filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type,
9700                                       self.op.hvparams)
9701     hv_type = hypervisor.GetHypervisor(self.op.hypervisor)
9702     hv_type.CheckParameterSyntax(filled_hvp)
9703     self.hv_full = filled_hvp
9704     # check that we don't specify global parameters on an instance
9705     _CheckGlobalHvParams(self.op.hvparams)
9706
9707     # fill and remember the beparams dict
9708     default_beparams = cluster.beparams[constants.PP_DEFAULT]
9709     for param, value in self.op.beparams.iteritems():
9710       if value == constants.VALUE_AUTO:
9711         self.op.beparams[param] = default_beparams[param]
9712     objects.UpgradeBeParams(self.op.beparams)
9713     utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
9714     self.be_full = cluster.SimpleFillBE(self.op.beparams)
9715
9716     # build os parameters
9717     self.os_full = cluster.SimpleFillOS(self.op.os_type, self.op.osparams)
9718
9719     # now that hvp/bep are in final format, let's reset to defaults,
9720     # if told to do so
9721     if self.op.identify_defaults:
9722       self._RevertToDefaults(cluster)
9723
9724     # NIC buildup
9725     self.nics = []
9726     for idx, nic in enumerate(self.op.nics):
9727       nic_mode_req = nic.get(constants.INIC_MODE, None)
9728       nic_mode = nic_mode_req
9729       if nic_mode is None or nic_mode == constants.VALUE_AUTO:
9730         nic_mode = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_MODE]
9731
9732       # in routed mode, for the first nic, the default ip is 'auto'
9733       if nic_mode == constants.NIC_MODE_ROUTED and idx == 0:
9734         default_ip_mode = constants.VALUE_AUTO
9735       else:
9736         default_ip_mode = constants.VALUE_NONE
9737
9738       # ip validity checks
9739       ip = nic.get(constants.INIC_IP, default_ip_mode)
9740       if ip is None or ip.lower() == constants.VALUE_NONE:
9741         nic_ip = None
9742       elif ip.lower() == constants.VALUE_AUTO:
9743         if not self.op.name_check:
9744           raise errors.OpPrereqError("IP address set to auto but name checks"
9745                                      " have been skipped",
9746                                      errors.ECODE_INVAL)
9747         nic_ip = self.hostname1.ip
9748       else:
9749         if not netutils.IPAddress.IsValid(ip):
9750           raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
9751                                      errors.ECODE_INVAL)
9752         nic_ip = ip
9753
9754       # TODO: check the ip address for uniqueness
9755       if nic_mode == constants.NIC_MODE_ROUTED and not nic_ip:
9756         raise errors.OpPrereqError("Routed nic mode requires an ip address",
9757                                    errors.ECODE_INVAL)
9758
9759       # MAC address verification
9760       mac = nic.get(constants.INIC_MAC, constants.VALUE_AUTO)
9761       if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
9762         mac = utils.NormalizeAndValidateMac(mac)
9763
9764         try:
9765           self.cfg.ReserveMAC(mac, self.proc.GetECId())
9766         except errors.ReservationError:
9767           raise errors.OpPrereqError("MAC address %s already in use"
9768                                      " in cluster" % mac,
9769                                      errors.ECODE_NOTUNIQUE)
9770
9771       #  Build nic parameters
9772       link = nic.get(constants.INIC_LINK, None)
9773       if link == constants.VALUE_AUTO:
9774         link = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_LINK]
9775       nicparams = {}
9776       if nic_mode_req:
9777         nicparams[constants.NIC_MODE] = nic_mode
9778       if link:
9779         nicparams[constants.NIC_LINK] = link
9780
9781       check_params = cluster.SimpleFillNIC(nicparams)
9782       objects.NIC.CheckParameterSyntax(check_params)
9783       self.nics.append(objects.NIC(mac=mac, ip=nic_ip, nicparams=nicparams))
9784
9785     # disk checks/pre-build
9786     default_vg = self.cfg.GetVGName()
9787     self.disks = []
9788     for disk in self.op.disks:
9789       mode = disk.get(constants.IDISK_MODE, constants.DISK_RDWR)
9790       if mode not in constants.DISK_ACCESS_SET:
9791         raise errors.OpPrereqError("Invalid disk access mode '%s'" %
9792                                    mode, errors.ECODE_INVAL)
9793       size = disk.get(constants.IDISK_SIZE, None)
9794       if size is None:
9795         raise errors.OpPrereqError("Missing disk size", errors.ECODE_INVAL)
9796       try:
9797         size = int(size)
9798       except (TypeError, ValueError):
9799         raise errors.OpPrereqError("Invalid disk size '%s'" % size,
9800                                    errors.ECODE_INVAL)
9801
9802       data_vg = disk.get(constants.IDISK_VG, default_vg)
9803       new_disk = {
9804         constants.IDISK_SIZE: size,
9805         constants.IDISK_MODE: mode,
9806         constants.IDISK_VG: data_vg,
9807         }
9808       if constants.IDISK_METAVG in disk:
9809         new_disk[constants.IDISK_METAVG] = disk[constants.IDISK_METAVG]
9810       if constants.IDISK_ADOPT in disk:
9811         new_disk[constants.IDISK_ADOPT] = disk[constants.IDISK_ADOPT]
9812       self.disks.append(new_disk)
9813
9814     if self.op.mode == constants.INSTANCE_IMPORT:
9815       disk_images = []
9816       for idx in range(len(self.disks)):
9817         option = "disk%d_dump" % idx
9818         if export_info.has_option(constants.INISECT_INS, option):
9819           # FIXME: are the old os-es, disk sizes, etc. useful?
9820           export_name = export_info.get(constants.INISECT_INS, option)
9821           image = utils.PathJoin(self.op.src_path, export_name)
9822           disk_images.append(image)
9823         else:
9824           disk_images.append(False)
9825
9826       self.src_images = disk_images
9827
9828       if self.op.instance_name == self._old_instance_name:
9829         for idx, nic in enumerate(self.nics):
9830           if nic.mac == constants.VALUE_AUTO:
9831             nic_mac_ini = "nic%d_mac" % idx
9832             nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
9833
9834     # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
9835
9836     # ip ping checks (we use the same ip that was resolved in ExpandNames)
9837     if self.op.ip_check:
9838       if netutils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
9839         raise errors.OpPrereqError("IP %s of instance %s already in use" %
9840                                    (self.check_ip, self.op.instance_name),
9841                                    errors.ECODE_NOTUNIQUE)
9842
9843     #### mac address generation
9844     # By generating here the mac address both the allocator and the hooks get
9845     # the real final mac address rather than the 'auto' or 'generate' value.
9846     # There is a race condition between the generation and the instance object
9847     # creation, which means that we know the mac is valid now, but we're not
9848     # sure it will be when we actually add the instance. If things go bad
9849     # adding the instance will abort because of a duplicate mac, and the
9850     # creation job will fail.
9851     for nic in self.nics:
9852       if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
9853         nic.mac = self.cfg.GenerateMAC(self.proc.GetECId())
9854
9855     #### allocator run
9856
9857     if self.op.iallocator is not None:
9858       self._RunAllocator()
9859
9860     # Release all unneeded node locks
9861     _ReleaseLocks(self, locking.LEVEL_NODE,
9862                   keep=filter(None, [self.op.pnode, self.op.snode,
9863                                      self.op.src_node]))
9864     _ReleaseLocks(self, locking.LEVEL_NODE_RES,
9865                   keep=filter(None, [self.op.pnode, self.op.snode,
9866                                      self.op.src_node]))
9867
9868     #### node related checks
9869
9870     # check primary node
9871     self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode)
9872     assert self.pnode is not None, \
9873       "Cannot retrieve locked node %s" % self.op.pnode
9874     if pnode.offline:
9875       raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
9876                                  pnode.name, errors.ECODE_STATE)
9877     if pnode.drained:
9878       raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
9879                                  pnode.name, errors.ECODE_STATE)
9880     if not pnode.vm_capable:
9881       raise errors.OpPrereqError("Cannot use non-vm_capable primary node"
9882                                  " '%s'" % pnode.name, errors.ECODE_STATE)
9883
9884     self.secondaries = []
9885
9886     # mirror node verification
9887     if self.op.disk_template in constants.DTS_INT_MIRROR:
9888       if self.op.snode == pnode.name:
9889         raise errors.OpPrereqError("The secondary node cannot be the"
9890                                    " primary node", errors.ECODE_INVAL)
9891       _CheckNodeOnline(self, self.op.snode)
9892       _CheckNodeNotDrained(self, self.op.snode)
9893       _CheckNodeVmCapable(self, self.op.snode)
9894       self.secondaries.append(self.op.snode)
9895
9896       snode = self.cfg.GetNodeInfo(self.op.snode)
9897       if pnode.group != snode.group:
9898         self.LogWarning("The primary and secondary nodes are in two"
9899                         " different node groups; the disk parameters"
9900                         " from the first disk's node group will be"
9901                         " used")
9902
9903     nodenames = [pnode.name] + self.secondaries
9904
9905     # Verify instance specs
9906     spindle_use = self.be_full.get(constants.BE_SPINDLE_USE, None)
9907     ispec = {
9908       constants.ISPEC_MEM_SIZE: self.be_full.get(constants.BE_MAXMEM, None),
9909       constants.ISPEC_CPU_COUNT: self.be_full.get(constants.BE_VCPUS, None),
9910       constants.ISPEC_DISK_COUNT: len(self.disks),
9911       constants.ISPEC_DISK_SIZE: [disk["size"] for disk in self.disks],
9912       constants.ISPEC_NIC_COUNT: len(self.nics),
9913       constants.ISPEC_SPINDLE_USE: spindle_use,
9914       }
9915
9916     group_info = self.cfg.GetNodeGroup(pnode.group)
9917     ipolicy = _CalculateGroupIPolicy(cluster, group_info)
9918     res = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec)
9919     if not self.op.ignore_ipolicy and res:
9920       raise errors.OpPrereqError(("Instance allocation to group %s violates"
9921                                   " policy: %s") % (pnode.group,
9922                                                     utils.CommaJoin(res)),
9923                                   errors.ECODE_INVAL)
9924
9925     if not self.adopt_disks:
9926       if self.op.disk_template == constants.DT_RBD:
9927         # _CheckRADOSFreeSpace() is just a placeholder.
9928         # Any function that checks prerequisites can be placed here.
9929         # Check if there is enough space on the RADOS cluster.
9930         _CheckRADOSFreeSpace()
9931       else:
9932         # Check lv size requirements, if not adopting
9933         req_sizes = _ComputeDiskSizePerVG(self.op.disk_template, self.disks)
9934         _CheckNodesFreeDiskPerVG(self, nodenames, req_sizes)
9935
9936     elif self.op.disk_template == constants.DT_PLAIN: # Check the adoption data
9937       all_lvs = set(["%s/%s" % (disk[constants.IDISK_VG],
9938                                 disk[constants.IDISK_ADOPT])
9939                      for disk in self.disks])
9940       if len(all_lvs) != len(self.disks):
9941         raise errors.OpPrereqError("Duplicate volume names given for adoption",
9942                                    errors.ECODE_INVAL)
9943       for lv_name in all_lvs:
9944         try:
9945           # FIXME: lv_name here is "vg/lv" need to ensure that other calls
9946           # to ReserveLV uses the same syntax
9947           self.cfg.ReserveLV(lv_name, self.proc.GetECId())
9948         except errors.ReservationError:
9949           raise errors.OpPrereqError("LV named %s used by another instance" %
9950                                      lv_name, errors.ECODE_NOTUNIQUE)
9951
9952       vg_names = self.rpc.call_vg_list([pnode.name])[pnode.name]
9953       vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
9954
9955       node_lvs = self.rpc.call_lv_list([pnode.name],
9956                                        vg_names.payload.keys())[pnode.name]
9957       node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
9958       node_lvs = node_lvs.payload
9959
9960       delta = all_lvs.difference(node_lvs.keys())
9961       if delta:
9962         raise errors.OpPrereqError("Missing logical volume(s): %s" %
9963                                    utils.CommaJoin(delta),
9964                                    errors.ECODE_INVAL)
9965       online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
9966       if online_lvs:
9967         raise errors.OpPrereqError("Online logical volumes found, cannot"
9968                                    " adopt: %s" % utils.CommaJoin(online_lvs),
9969                                    errors.ECODE_STATE)
9970       # update the size of disk based on what is found
9971       for dsk in self.disks:
9972         dsk[constants.IDISK_SIZE] = \
9973           int(float(node_lvs["%s/%s" % (dsk[constants.IDISK_VG],
9974                                         dsk[constants.IDISK_ADOPT])][0]))
9975
9976     elif self.op.disk_template == constants.DT_BLOCK:
9977       # Normalize and de-duplicate device paths
9978       all_disks = set([os.path.abspath(disk[constants.IDISK_ADOPT])
9979                        for disk in self.disks])
9980       if len(all_disks) != len(self.disks):
9981         raise errors.OpPrereqError("Duplicate disk names given for adoption",
9982                                    errors.ECODE_INVAL)
9983       baddisks = [d for d in all_disks
9984                   if not d.startswith(constants.ADOPTABLE_BLOCKDEV_ROOT)]
9985       if baddisks:
9986         raise errors.OpPrereqError("Device node(s) %s lie outside %s and"
9987                                    " cannot be adopted" %
9988                                    (", ".join(baddisks),
9989                                     constants.ADOPTABLE_BLOCKDEV_ROOT),
9990                                    errors.ECODE_INVAL)
9991
9992       node_disks = self.rpc.call_bdev_sizes([pnode.name],
9993                                             list(all_disks))[pnode.name]
9994       node_disks.Raise("Cannot get block device information from node %s" %
9995                        pnode.name)
9996       node_disks = node_disks.payload
9997       delta = all_disks.difference(node_disks.keys())
9998       if delta:
9999         raise errors.OpPrereqError("Missing block device(s): %s" %
10000                                    utils.CommaJoin(delta),
10001                                    errors.ECODE_INVAL)
10002       for dsk in self.disks:
10003         dsk[constants.IDISK_SIZE] = \
10004           int(float(node_disks[dsk[constants.IDISK_ADOPT]]))
10005
10006     _CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
10007
10008     _CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant)
10009     # check OS parameters (remotely)
10010     _CheckOSParams(self, True, nodenames, self.op.os_type, self.os_full)
10011
10012     _CheckNicsBridgesExist(self, self.nics, self.pnode.name)
10013
10014     # memory check on primary node
10015     #TODO(dynmem): use MINMEM for checking
10016     if self.op.start:
10017       _CheckNodeFreeMemory(self, self.pnode.name,
10018                            "creating instance %s" % self.op.instance_name,
10019                            self.be_full[constants.BE_MAXMEM],
10020                            self.op.hypervisor)
10021
10022     self.dry_run_result = list(nodenames)
10023
10024   def Exec(self, feedback_fn):
10025     """Create and add the instance to the cluster.
10026
10027     """
10028     instance = self.op.instance_name
10029     pnode_name = self.pnode.name
10030
10031     assert not (self.owned_locks(locking.LEVEL_NODE_RES) -
10032                 self.owned_locks(locking.LEVEL_NODE)), \
10033       "Node locks differ from node resource locks"
10034
10035     ht_kind = self.op.hypervisor
10036     if ht_kind in constants.HTS_REQ_PORT:
10037       network_port = self.cfg.AllocatePort()
10038     else:
10039       network_port = None
10040
10041     # This is ugly but we got a chicken-egg problem here
10042     # We can only take the group disk parameters, as the instance
10043     # has no disks yet (we are generating them right here).
10044     node = self.cfg.GetNodeInfo(pnode_name)
10045     nodegroup = self.cfg.GetNodeGroup(node.group)
10046     disks = _GenerateDiskTemplate(self,
10047                                   self.op.disk_template,
10048                                   instance, pnode_name,
10049                                   self.secondaries,
10050                                   self.disks,
10051                                   self.instance_file_storage_dir,
10052                                   self.op.file_driver,
10053                                   0,
10054                                   feedback_fn,
10055                                   self.cfg.GetGroupDiskParams(nodegroup))
10056
10057     iobj = objects.Instance(name=instance, os=self.op.os_type,
10058                             primary_node=pnode_name,
10059                             nics=self.nics, disks=disks,
10060                             disk_template=self.op.disk_template,
10061                             admin_state=constants.ADMINST_DOWN,
10062                             network_port=network_port,
10063                             beparams=self.op.beparams,
10064                             hvparams=self.op.hvparams,
10065                             hypervisor=self.op.hypervisor,
10066                             osparams=self.op.osparams,
10067                             )
10068
10069     if self.op.tags:
10070       for tag in self.op.tags:
10071         iobj.AddTag(tag)
10072
10073     if self.adopt_disks:
10074       if self.op.disk_template == constants.DT_PLAIN:
10075         # rename LVs to the newly-generated names; we need to construct
10076         # 'fake' LV disks with the old data, plus the new unique_id
10077         tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
10078         rename_to = []
10079         for t_dsk, a_dsk in zip(tmp_disks, self.disks):
10080           rename_to.append(t_dsk.logical_id)
10081           t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
10082           self.cfg.SetDiskID(t_dsk, pnode_name)
10083         result = self.rpc.call_blockdev_rename(pnode_name,
10084                                                zip(tmp_disks, rename_to))
10085         result.Raise("Failed to rename adoped LVs")
10086     else:
10087       feedback_fn("* creating instance disks...")
10088       try:
10089         _CreateDisks(self, iobj)
10090       except errors.OpExecError:
10091         self.LogWarning("Device creation failed, reverting...")
10092         try:
10093           _RemoveDisks(self, iobj)
10094         finally:
10095           self.cfg.ReleaseDRBDMinors(instance)
10096           raise
10097
10098     feedback_fn("adding instance %s to cluster config" % instance)
10099
10100     self.cfg.AddInstance(iobj, self.proc.GetECId())
10101
10102     # Declare that we don't want to remove the instance lock anymore, as we've
10103     # added the instance to the config
10104     del self.remove_locks[locking.LEVEL_INSTANCE]
10105
10106     if self.op.mode == constants.INSTANCE_IMPORT:
10107       # Release unused nodes
10108       _ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node])
10109     else:
10110       # Release all nodes
10111       _ReleaseLocks(self, locking.LEVEL_NODE)
10112
10113     disk_abort = False
10114     if not self.adopt_disks and self.cfg.GetClusterInfo().prealloc_wipe_disks:
10115       feedback_fn("* wiping instance disks...")
10116       try:
10117         _WipeDisks(self, iobj)
10118       except errors.OpExecError, err:
10119         logging.exception("Wiping disks failed")
10120         self.LogWarning("Wiping instance disks failed (%s)", err)
10121         disk_abort = True
10122
10123     if disk_abort:
10124       # Something is already wrong with the disks, don't do anything else
10125       pass
10126     elif self.op.wait_for_sync:
10127       disk_abort = not _WaitForSync(self, iobj)
10128     elif iobj.disk_template in constants.DTS_INT_MIRROR:
10129       # make sure the disks are not degraded (still sync-ing is ok)
10130       feedback_fn("* checking mirrors status")
10131       disk_abort = not _WaitForSync(self, iobj, oneshot=True)
10132     else:
10133       disk_abort = False
10134
10135     if disk_abort:
10136       _RemoveDisks(self, iobj)
10137       self.cfg.RemoveInstance(iobj.name)
10138       # Make sure the instance lock gets removed
10139       self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
10140       raise errors.OpExecError("There are some degraded disks for"
10141                                " this instance")
10142
10143     # Release all node resource locks
10144     _ReleaseLocks(self, locking.LEVEL_NODE_RES)
10145
10146     if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
10147       # we need to set the disks ID to the primary node, since the
10148       # preceding code might or might have not done it, depending on
10149       # disk template and other options
10150       for disk in iobj.disks:
10151         self.cfg.SetDiskID(disk, pnode_name)
10152       if self.op.mode == constants.INSTANCE_CREATE:
10153         if not self.op.no_install:
10154           pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
10155                         not self.op.wait_for_sync)
10156           if pause_sync:
10157             feedback_fn("* pausing disk sync to install instance OS")
10158             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
10159                                                               (iobj.disks,
10160                                                                iobj), True)
10161             for idx, success in enumerate(result.payload):
10162               if not success:
10163                 logging.warn("pause-sync of instance %s for disk %d failed",
10164                              instance, idx)
10165
10166           feedback_fn("* running the instance OS create scripts...")
10167           # FIXME: pass debug option from opcode to backend
10168           os_add_result = \
10169             self.rpc.call_instance_os_add(pnode_name, (iobj, None), False,
10170                                           self.op.debug_level)
10171           if pause_sync:
10172             feedback_fn("* resuming disk sync")
10173             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
10174                                                               (iobj.disks,
10175                                                                iobj), False)
10176             for idx, success in enumerate(result.payload):
10177               if not success:
10178                 logging.warn("resume-sync of instance %s for disk %d failed",
10179                              instance, idx)
10180
10181           os_add_result.Raise("Could not add os for instance %s"
10182                               " on node %s" % (instance, pnode_name))
10183
10184       else:
10185         if self.op.mode == constants.INSTANCE_IMPORT:
10186           feedback_fn("* running the instance OS import scripts...")
10187
10188           transfers = []
10189
10190           for idx, image in enumerate(self.src_images):
10191             if not image:
10192               continue
10193
10194             # FIXME: pass debug option from opcode to backend
10195             dt = masterd.instance.DiskTransfer("disk/%s" % idx,
10196                                                constants.IEIO_FILE, (image, ),
10197                                                constants.IEIO_SCRIPT,
10198                                                (iobj.disks[idx], idx),
10199                                                None)
10200             transfers.append(dt)
10201
10202           import_result = \
10203             masterd.instance.TransferInstanceData(self, feedback_fn,
10204                                                   self.op.src_node, pnode_name,
10205                                                   self.pnode.secondary_ip,
10206                                                   iobj, transfers)
10207           if not compat.all(import_result):
10208             self.LogWarning("Some disks for instance %s on node %s were not"
10209                             " imported successfully" % (instance, pnode_name))
10210
10211           rename_from = self._old_instance_name
10212
10213         elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
10214           feedback_fn("* preparing remote import...")
10215           # The source cluster will stop the instance before attempting to make
10216           # a connection. In some cases stopping an instance can take a long
10217           # time, hence the shutdown timeout is added to the connection
10218           # timeout.
10219           connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
10220                              self.op.source_shutdown_timeout)
10221           timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
10222
10223           assert iobj.primary_node == self.pnode.name
10224           disk_results = \
10225             masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
10226                                           self.source_x509_ca,
10227                                           self._cds, timeouts)
10228           if not compat.all(disk_results):
10229             # TODO: Should the instance still be started, even if some disks
10230             # failed to import (valid for local imports, too)?
10231             self.LogWarning("Some disks for instance %s on node %s were not"
10232                             " imported successfully" % (instance, pnode_name))
10233
10234           rename_from = self.source_instance_name
10235
10236         else:
10237           # also checked in the prereq part
10238           raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
10239                                        % self.op.mode)
10240
10241         # Run rename script on newly imported instance
10242         assert iobj.name == instance
10243         feedback_fn("Running rename script for %s" % instance)
10244         result = self.rpc.call_instance_run_rename(pnode_name, iobj,
10245                                                    rename_from,
10246                                                    self.op.debug_level)
10247         if result.fail_msg:
10248           self.LogWarning("Failed to run rename script for %s on node"
10249                           " %s: %s" % (instance, pnode_name, result.fail_msg))
10250
10251     assert not self.owned_locks(locking.LEVEL_NODE_RES)
10252
10253     if self.op.start:
10254       iobj.admin_state = constants.ADMINST_UP
10255       self.cfg.Update(iobj, feedback_fn)
10256       logging.info("Starting instance %s on node %s", instance, pnode_name)
10257       feedback_fn("* starting instance...")
10258       result = self.rpc.call_instance_start(pnode_name, (iobj, None, None),
10259                                             False)
10260       result.Raise("Could not start instance")
10261
10262     return list(iobj.all_nodes)
10263
10264
10265 def _CheckRADOSFreeSpace():
10266   """Compute disk size requirements inside the RADOS cluster.
10267
10268   """
10269   # For the RADOS cluster we assume there is always enough space.
10270   pass
10271
10272
10273 class LUInstanceConsole(NoHooksLU):
10274   """Connect to an instance's console.
10275
10276   This is somewhat special in that it returns the command line that
10277   you need to run on the master node in order to connect to the
10278   console.
10279
10280   """
10281   REQ_BGL = False
10282
10283   def ExpandNames(self):
10284     self.share_locks = _ShareAll()
10285     self._ExpandAndLockInstance()
10286
10287   def CheckPrereq(self):
10288     """Check prerequisites.
10289
10290     This checks that the instance is in the cluster.
10291
10292     """
10293     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
10294     assert self.instance is not None, \
10295       "Cannot retrieve locked instance %s" % self.op.instance_name
10296     _CheckNodeOnline(self, self.instance.primary_node)
10297
10298   def Exec(self, feedback_fn):
10299     """Connect to the console of an instance
10300
10301     """
10302     instance = self.instance
10303     node = instance.primary_node
10304
10305     node_insts = self.rpc.call_instance_list([node],
10306                                              [instance.hypervisor])[node]
10307     node_insts.Raise("Can't get node information from %s" % node)
10308
10309     if instance.name not in node_insts.payload:
10310       if instance.admin_state == constants.ADMINST_UP:
10311         state = constants.INSTST_ERRORDOWN
10312       elif instance.admin_state == constants.ADMINST_DOWN:
10313         state = constants.INSTST_ADMINDOWN
10314       else:
10315         state = constants.INSTST_ADMINOFFLINE
10316       raise errors.OpExecError("Instance %s is not running (state %s)" %
10317                                (instance.name, state))
10318
10319     logging.debug("Connecting to console of %s on %s", instance.name, node)
10320
10321     return _GetInstanceConsole(self.cfg.GetClusterInfo(), instance)
10322
10323
10324 def _GetInstanceConsole(cluster, instance):
10325   """Returns console information for an instance.
10326
10327   @type cluster: L{objects.Cluster}
10328   @type instance: L{objects.Instance}
10329   @rtype: dict
10330
10331   """
10332   hyper = hypervisor.GetHypervisor(instance.hypervisor)
10333   # beparams and hvparams are passed separately, to avoid editing the
10334   # instance and then saving the defaults in the instance itself.
10335   hvparams = cluster.FillHV(instance)
10336   beparams = cluster.FillBE(instance)
10337   console = hyper.GetInstanceConsole(instance, hvparams, beparams)
10338
10339   assert console.instance == instance.name
10340   assert console.Validate()
10341
10342   return console.ToDict()
10343
10344
10345 class LUInstanceReplaceDisks(LogicalUnit):
10346   """Replace the disks of an instance.
10347
10348   """
10349   HPATH = "mirrors-replace"
10350   HTYPE = constants.HTYPE_INSTANCE
10351   REQ_BGL = False
10352
10353   def CheckArguments(self):
10354     TLReplaceDisks.CheckArguments(self.op.mode, self.op.remote_node,
10355                                   self.op.iallocator)
10356
10357   def ExpandNames(self):
10358     self._ExpandAndLockInstance()
10359
10360     assert locking.LEVEL_NODE not in self.needed_locks
10361     assert locking.LEVEL_NODE_RES not in self.needed_locks
10362     assert locking.LEVEL_NODEGROUP not in self.needed_locks
10363
10364     assert self.op.iallocator is None or self.op.remote_node is None, \
10365       "Conflicting options"
10366
10367     if self.op.remote_node is not None:
10368       self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
10369
10370       # Warning: do not remove the locking of the new secondary here
10371       # unless DRBD8.AddChildren is changed to work in parallel;
10372       # currently it doesn't since parallel invocations of
10373       # FindUnusedMinor will conflict
10374       self.needed_locks[locking.LEVEL_NODE] = [self.op.remote_node]
10375       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
10376     else:
10377       self.needed_locks[locking.LEVEL_NODE] = []
10378       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
10379
10380       if self.op.iallocator is not None:
10381         # iallocator will select a new node in the same group
10382         self.needed_locks[locking.LEVEL_NODEGROUP] = []
10383
10384     self.needed_locks[locking.LEVEL_NODE_RES] = []
10385
10386     self.replacer = TLReplaceDisks(self, self.op.instance_name, self.op.mode,
10387                                    self.op.iallocator, self.op.remote_node,
10388                                    self.op.disks, False, self.op.early_release,
10389                                    self.op.ignore_ipolicy)
10390
10391     self.tasklets = [self.replacer]
10392
10393   def DeclareLocks(self, level):
10394     if level == locking.LEVEL_NODEGROUP:
10395       assert self.op.remote_node is None
10396       assert self.op.iallocator is not None
10397       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
10398
10399       self.share_locks[locking.LEVEL_NODEGROUP] = 1
10400       # Lock all groups used by instance optimistically; this requires going
10401       # via the node before it's locked, requiring verification later on
10402       self.needed_locks[locking.LEVEL_NODEGROUP] = \
10403         self.cfg.GetInstanceNodeGroups(self.op.instance_name)
10404
10405     elif level == locking.LEVEL_NODE:
10406       if self.op.iallocator is not None:
10407         assert self.op.remote_node is None
10408         assert not self.needed_locks[locking.LEVEL_NODE]
10409
10410         # Lock member nodes of all locked groups
10411         self.needed_locks[locking.LEVEL_NODE] = [node_name
10412           for group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
10413           for node_name in self.cfg.GetNodeGroup(group_uuid).members]
10414       else:
10415         self._LockInstancesNodes()
10416     elif level == locking.LEVEL_NODE_RES:
10417       # Reuse node locks
10418       self.needed_locks[locking.LEVEL_NODE_RES] = \
10419         self.needed_locks[locking.LEVEL_NODE]
10420
10421   def BuildHooksEnv(self):
10422     """Build hooks env.
10423
10424     This runs on the master, the primary and all the secondaries.
10425
10426     """
10427     instance = self.replacer.instance
10428     env = {
10429       "MODE": self.op.mode,
10430       "NEW_SECONDARY": self.op.remote_node,
10431       "OLD_SECONDARY": instance.secondary_nodes[0],
10432       }
10433     env.update(_BuildInstanceHookEnvByObject(self, instance))
10434     return env
10435
10436   def BuildHooksNodes(self):
10437     """Build hooks nodes.
10438
10439     """
10440     instance = self.replacer.instance
10441     nl = [
10442       self.cfg.GetMasterNode(),
10443       instance.primary_node,
10444       ]
10445     if self.op.remote_node is not None:
10446       nl.append(self.op.remote_node)
10447     return nl, nl
10448
10449   def CheckPrereq(self):
10450     """Check prerequisites.
10451
10452     """
10453     assert (self.glm.is_owned(locking.LEVEL_NODEGROUP) or
10454             self.op.iallocator is None)
10455
10456     # Verify if node group locks are still correct
10457     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
10458     if owned_groups:
10459       _CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
10460
10461     return LogicalUnit.CheckPrereq(self)
10462
10463
10464 class TLReplaceDisks(Tasklet):
10465   """Replaces disks for an instance.
10466
10467   Note: Locking is not within the scope of this class.
10468
10469   """
10470   def __init__(self, lu, instance_name, mode, iallocator_name, remote_node,
10471                disks, delay_iallocator, early_release, ignore_ipolicy):
10472     """Initializes this class.
10473
10474     """
10475     Tasklet.__init__(self, lu)
10476
10477     # Parameters
10478     self.instance_name = instance_name
10479     self.mode = mode
10480     self.iallocator_name = iallocator_name
10481     self.remote_node = remote_node
10482     self.disks = disks
10483     self.delay_iallocator = delay_iallocator
10484     self.early_release = early_release
10485     self.ignore_ipolicy = ignore_ipolicy
10486
10487     # Runtime data
10488     self.instance = None
10489     self.new_node = None
10490     self.target_node = None
10491     self.other_node = None
10492     self.remote_node_info = None
10493     self.node_secondary_ip = None
10494
10495   @staticmethod
10496   def CheckArguments(mode, remote_node, iallocator):
10497     """Helper function for users of this class.
10498
10499     """
10500     # check for valid parameter combination
10501     if mode == constants.REPLACE_DISK_CHG:
10502       if remote_node is None and iallocator is None:
10503         raise errors.OpPrereqError("When changing the secondary either an"
10504                                    " iallocator script must be used or the"
10505                                    " new node given", errors.ECODE_INVAL)
10506
10507       if remote_node is not None and iallocator is not None:
10508         raise errors.OpPrereqError("Give either the iallocator or the new"
10509                                    " secondary, not both", errors.ECODE_INVAL)
10510
10511     elif remote_node is not None or iallocator is not None:
10512       # Not replacing the secondary
10513       raise errors.OpPrereqError("The iallocator and new node options can"
10514                                  " only be used when changing the"
10515                                  " secondary node", errors.ECODE_INVAL)
10516
10517   @staticmethod
10518   def _RunAllocator(lu, iallocator_name, instance_name, relocate_from):
10519     """Compute a new secondary node using an IAllocator.
10520
10521     """
10522     ial = IAllocator(lu.cfg, lu.rpc,
10523                      mode=constants.IALLOCATOR_MODE_RELOC,
10524                      name=instance_name,
10525                      relocate_from=list(relocate_from))
10526
10527     ial.Run(iallocator_name)
10528
10529     if not ial.success:
10530       raise errors.OpPrereqError("Can't compute nodes using iallocator '%s':"
10531                                  " %s" % (iallocator_name, ial.info),
10532                                  errors.ECODE_NORES)
10533
10534     if len(ial.result) != ial.required_nodes:
10535       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
10536                                  " of nodes (%s), required %s" %
10537                                  (iallocator_name,
10538                                   len(ial.result), ial.required_nodes),
10539                                  errors.ECODE_FAULT)
10540
10541     remote_node_name = ial.result[0]
10542
10543     lu.LogInfo("Selected new secondary for instance '%s': %s",
10544                instance_name, remote_node_name)
10545
10546     return remote_node_name
10547
10548   def _FindFaultyDisks(self, node_name):
10549     """Wrapper for L{_FindFaultyInstanceDisks}.
10550
10551     """
10552     return _FindFaultyInstanceDisks(self.cfg, self.rpc, self.instance,
10553                                     node_name, True)
10554
10555   def _CheckDisksActivated(self, instance):
10556     """Checks if the instance disks are activated.
10557
10558     @param instance: The instance to check disks
10559     @return: True if they are activated, False otherwise
10560
10561     """
10562     nodes = instance.all_nodes
10563
10564     for idx, dev in enumerate(instance.disks):
10565       for node in nodes:
10566         self.lu.LogInfo("Checking disk/%d on %s", idx, node)
10567         self.cfg.SetDiskID(dev, node)
10568
10569         result = _BlockdevFind(self, node, dev, instance)
10570
10571         if result.offline:
10572           continue
10573         elif result.fail_msg or not result.payload:
10574           return False
10575
10576     return True
10577
10578   def CheckPrereq(self):
10579     """Check prerequisites.
10580
10581     This checks that the instance is in the cluster.
10582
10583     """
10584     self.instance = instance = self.cfg.GetInstanceInfo(self.instance_name)
10585     assert instance is not None, \
10586       "Cannot retrieve locked instance %s" % self.instance_name
10587
10588     if instance.disk_template != constants.DT_DRBD8:
10589       raise errors.OpPrereqError("Can only run replace disks for DRBD8-based"
10590                                  " instances", errors.ECODE_INVAL)
10591
10592     if len(instance.secondary_nodes) != 1:
10593       raise errors.OpPrereqError("The instance has a strange layout,"
10594                                  " expected one secondary but found %d" %
10595                                  len(instance.secondary_nodes),
10596                                  errors.ECODE_FAULT)
10597
10598     if not self.delay_iallocator:
10599       self._CheckPrereq2()
10600
10601   def _CheckPrereq2(self):
10602     """Check prerequisites, second part.
10603
10604     This function should always be part of CheckPrereq. It was separated and is
10605     now called from Exec because during node evacuation iallocator was only
10606     called with an unmodified cluster model, not taking planned changes into
10607     account.
10608
10609     """
10610     instance = self.instance
10611     secondary_node = instance.secondary_nodes[0]
10612
10613     if self.iallocator_name is None:
10614       remote_node = self.remote_node
10615     else:
10616       remote_node = self._RunAllocator(self.lu, self.iallocator_name,
10617                                        instance.name, instance.secondary_nodes)
10618
10619     if remote_node is None:
10620       self.remote_node_info = None
10621     else:
10622       assert remote_node in self.lu.owned_locks(locking.LEVEL_NODE), \
10623              "Remote node '%s' is not locked" % remote_node
10624
10625       self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
10626       assert self.remote_node_info is not None, \
10627         "Cannot retrieve locked node %s" % remote_node
10628
10629     if remote_node == self.instance.primary_node:
10630       raise errors.OpPrereqError("The specified node is the primary node of"
10631                                  " the instance", errors.ECODE_INVAL)
10632
10633     if remote_node == secondary_node:
10634       raise errors.OpPrereqError("The specified node is already the"
10635                                  " secondary node of the instance",
10636                                  errors.ECODE_INVAL)
10637
10638     if self.disks and self.mode in (constants.REPLACE_DISK_AUTO,
10639                                     constants.REPLACE_DISK_CHG):
10640       raise errors.OpPrereqError("Cannot specify disks to be replaced",
10641                                  errors.ECODE_INVAL)
10642
10643     if self.mode == constants.REPLACE_DISK_AUTO:
10644       if not self._CheckDisksActivated(instance):
10645         raise errors.OpPrereqError("Please run activate-disks on instance %s"
10646                                    " first" % self.instance_name,
10647                                    errors.ECODE_STATE)
10648       faulty_primary = self._FindFaultyDisks(instance.primary_node)
10649       faulty_secondary = self._FindFaultyDisks(secondary_node)
10650
10651       if faulty_primary and faulty_secondary:
10652         raise errors.OpPrereqError("Instance %s has faulty disks on more than"
10653                                    " one node and can not be repaired"
10654                                    " automatically" % self.instance_name,
10655                                    errors.ECODE_STATE)
10656
10657       if faulty_primary:
10658         self.disks = faulty_primary
10659         self.target_node = instance.primary_node
10660         self.other_node = secondary_node
10661         check_nodes = [self.target_node, self.other_node]
10662       elif faulty_secondary:
10663         self.disks = faulty_secondary
10664         self.target_node = secondary_node
10665         self.other_node = instance.primary_node
10666         check_nodes = [self.target_node, self.other_node]
10667       else:
10668         self.disks = []
10669         check_nodes = []
10670
10671     else:
10672       # Non-automatic modes
10673       if self.mode == constants.REPLACE_DISK_PRI:
10674         self.target_node = instance.primary_node
10675         self.other_node = secondary_node
10676         check_nodes = [self.target_node, self.other_node]
10677
10678       elif self.mode == constants.REPLACE_DISK_SEC:
10679         self.target_node = secondary_node
10680         self.other_node = instance.primary_node
10681         check_nodes = [self.target_node, self.other_node]
10682
10683       elif self.mode == constants.REPLACE_DISK_CHG:
10684         self.new_node = remote_node
10685         self.other_node = instance.primary_node
10686         self.target_node = secondary_node
10687         check_nodes = [self.new_node, self.other_node]
10688
10689         _CheckNodeNotDrained(self.lu, remote_node)
10690         _CheckNodeVmCapable(self.lu, remote_node)
10691
10692         old_node_info = self.cfg.GetNodeInfo(secondary_node)
10693         assert old_node_info is not None
10694         if old_node_info.offline and not self.early_release:
10695           # doesn't make sense to delay the release
10696           self.early_release = True
10697           self.lu.LogInfo("Old secondary %s is offline, automatically enabling"
10698                           " early-release mode", secondary_node)
10699
10700       else:
10701         raise errors.ProgrammerError("Unhandled disk replace mode (%s)" %
10702                                      self.mode)
10703
10704       # If not specified all disks should be replaced
10705       if not self.disks:
10706         self.disks = range(len(self.instance.disks))
10707
10708     # TODO: This is ugly, but right now we can't distinguish between internal
10709     # submitted opcode and external one. We should fix that.
10710     if self.remote_node_info:
10711       # We change the node, lets verify it still meets instance policy
10712       new_group_info = self.cfg.GetNodeGroup(self.remote_node_info.group)
10713       ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(),
10714                                        new_group_info)
10715       _CheckTargetNodeIPolicy(self, ipolicy, instance, self.remote_node_info,
10716                               ignore=self.ignore_ipolicy)
10717
10718     for node in check_nodes:
10719       _CheckNodeOnline(self.lu, node)
10720
10721     touched_nodes = frozenset(node_name for node_name in [self.new_node,
10722                                                           self.other_node,
10723                                                           self.target_node]
10724                               if node_name is not None)
10725
10726     # Release unneeded node and node resource locks
10727     _ReleaseLocks(self.lu, locking.LEVEL_NODE, keep=touched_nodes)
10728     _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES, keep=touched_nodes)
10729
10730     # Release any owned node group
10731     if self.lu.glm.is_owned(locking.LEVEL_NODEGROUP):
10732       _ReleaseLocks(self.lu, locking.LEVEL_NODEGROUP)
10733
10734     # Check whether disks are valid
10735     for disk_idx in self.disks:
10736       instance.FindDisk(disk_idx)
10737
10738     # Get secondary node IP addresses
10739     self.node_secondary_ip = dict((name, node.secondary_ip) for (name, node)
10740                                   in self.cfg.GetMultiNodeInfo(touched_nodes))
10741
10742   def Exec(self, feedback_fn):
10743     """Execute disk replacement.
10744
10745     This dispatches the disk replacement to the appropriate handler.
10746
10747     """
10748     if self.delay_iallocator:
10749       self._CheckPrereq2()
10750
10751     if __debug__:
10752       # Verify owned locks before starting operation
10753       owned_nodes = self.lu.owned_locks(locking.LEVEL_NODE)
10754       assert set(owned_nodes) == set(self.node_secondary_ip), \
10755           ("Incorrect node locks, owning %s, expected %s" %
10756            (owned_nodes, self.node_secondary_ip.keys()))
10757       assert (self.lu.owned_locks(locking.LEVEL_NODE) ==
10758               self.lu.owned_locks(locking.LEVEL_NODE_RES))
10759
10760       owned_instances = self.lu.owned_locks(locking.LEVEL_INSTANCE)
10761       assert list(owned_instances) == [self.instance_name], \
10762           "Instance '%s' not locked" % self.instance_name
10763
10764       assert not self.lu.glm.is_owned(locking.LEVEL_NODEGROUP), \
10765           "Should not own any node group lock at this point"
10766
10767     if not self.disks:
10768       feedback_fn("No disks need replacement")
10769       return
10770
10771     feedback_fn("Replacing disk(s) %s for %s" %
10772                 (utils.CommaJoin(self.disks), self.instance.name))
10773
10774     activate_disks = (self.instance.admin_state != constants.ADMINST_UP)
10775
10776     # Activate the instance disks if we're replacing them on a down instance
10777     if activate_disks:
10778       _StartInstanceDisks(self.lu, self.instance, True)
10779
10780     try:
10781       # Should we replace the secondary node?
10782       if self.new_node is not None:
10783         fn = self._ExecDrbd8Secondary
10784       else:
10785         fn = self._ExecDrbd8DiskOnly
10786
10787       result = fn(feedback_fn)
10788     finally:
10789       # Deactivate the instance disks if we're replacing them on a
10790       # down instance
10791       if activate_disks:
10792         _SafeShutdownInstanceDisks(self.lu, self.instance)
10793
10794     assert not self.lu.owned_locks(locking.LEVEL_NODE)
10795
10796     if __debug__:
10797       # Verify owned locks
10798       owned_nodes = self.lu.owned_locks(locking.LEVEL_NODE_RES)
10799       nodes = frozenset(self.node_secondary_ip)
10800       assert ((self.early_release and not owned_nodes) or
10801               (not self.early_release and not (set(owned_nodes) - nodes))), \
10802         ("Not owning the correct locks, early_release=%s, owned=%r,"
10803          " nodes=%r" % (self.early_release, owned_nodes, nodes))
10804
10805     return result
10806
10807   def _CheckVolumeGroup(self, nodes):
10808     self.lu.LogInfo("Checking volume groups")
10809
10810     vgname = self.cfg.GetVGName()
10811
10812     # Make sure volume group exists on all involved nodes
10813     results = self.rpc.call_vg_list(nodes)
10814     if not results:
10815       raise errors.OpExecError("Can't list volume groups on the nodes")
10816
10817     for node in nodes:
10818       res = results[node]
10819       res.Raise("Error checking node %s" % node)
10820       if vgname not in res.payload:
10821         raise errors.OpExecError("Volume group '%s' not found on node %s" %
10822                                  (vgname, node))
10823
10824   def _CheckDisksExistence(self, nodes):
10825     # Check disk existence
10826     for idx, dev in enumerate(self.instance.disks):
10827       if idx not in self.disks:
10828         continue
10829
10830       for node in nodes:
10831         self.lu.LogInfo("Checking disk/%d on %s" % (idx, node))
10832         self.cfg.SetDiskID(dev, node)
10833
10834         result = _BlockdevFind(self, node, dev, self.instance)
10835
10836         msg = result.fail_msg
10837         if msg or not result.payload:
10838           if not msg:
10839             msg = "disk not found"
10840           raise errors.OpExecError("Can't find disk/%d on node %s: %s" %
10841                                    (idx, node, msg))
10842
10843   def _CheckDisksConsistency(self, node_name, on_primary, ldisk):
10844     for idx, dev in enumerate(self.instance.disks):
10845       if idx not in self.disks:
10846         continue
10847
10848       self.lu.LogInfo("Checking disk/%d consistency on node %s" %
10849                       (idx, node_name))
10850
10851       if not _CheckDiskConsistency(self.lu, self.instance, dev, node_name,
10852                                    on_primary, ldisk=ldisk):
10853         raise errors.OpExecError("Node %s has degraded storage, unsafe to"
10854                                  " replace disks for instance %s" %
10855                                  (node_name, self.instance.name))
10856
10857   def _CreateNewStorage(self, node_name):
10858     """Create new storage on the primary or secondary node.
10859
10860     This is only used for same-node replaces, not for changing the
10861     secondary node, hence we don't want to modify the existing disk.
10862
10863     """
10864     iv_names = {}
10865
10866     disks = _AnnotateDiskParams(self.instance, self.instance.disks, self.cfg)
10867     for idx, dev in enumerate(disks):
10868       if idx not in self.disks:
10869         continue
10870
10871       self.lu.LogInfo("Adding storage on %s for disk/%d" % (node_name, idx))
10872
10873       self.cfg.SetDiskID(dev, node_name)
10874
10875       lv_names = [".disk%d_%s" % (idx, suffix) for suffix in ["data", "meta"]]
10876       names = _GenerateUniqueNames(self.lu, lv_names)
10877
10878       (data_disk, meta_disk) = dev.children
10879       vg_data = data_disk.logical_id[0]
10880       lv_data = objects.Disk(dev_type=constants.LD_LV, size=dev.size,
10881                              logical_id=(vg_data, names[0]),
10882                              params=data_disk.params)
10883       vg_meta = meta_disk.logical_id[0]
10884       lv_meta = objects.Disk(dev_type=constants.LD_LV, size=DRBD_META_SIZE,
10885                              logical_id=(vg_meta, names[1]),
10886                              params=meta_disk.params)
10887
10888       new_lvs = [lv_data, lv_meta]
10889       old_lvs = [child.Copy() for child in dev.children]
10890       iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
10891
10892       # we pass force_create=True to force the LVM creation
10893       for new_lv in new_lvs:
10894         _CreateBlockDevInner(self.lu, node_name, self.instance, new_lv, True,
10895                              _GetInstanceInfoText(self.instance), False)
10896
10897     return iv_names
10898
10899   def _CheckDevices(self, node_name, iv_names):
10900     for name, (dev, _, _) in iv_names.iteritems():
10901       self.cfg.SetDiskID(dev, node_name)
10902
10903       result = _BlockdevFind(self, node_name, dev, self.instance)
10904
10905       msg = result.fail_msg
10906       if msg or not result.payload:
10907         if not msg:
10908           msg = "disk not found"
10909         raise errors.OpExecError("Can't find DRBD device %s: %s" %
10910                                  (name, msg))
10911
10912       if result.payload.is_degraded:
10913         raise errors.OpExecError("DRBD device %s is degraded!" % name)
10914
10915   def _RemoveOldStorage(self, node_name, iv_names):
10916     for name, (_, old_lvs, _) in iv_names.iteritems():
10917       self.lu.LogInfo("Remove logical volumes for %s" % name)
10918
10919       for lv in old_lvs:
10920         self.cfg.SetDiskID(lv, node_name)
10921
10922         msg = self.rpc.call_blockdev_remove(node_name, lv).fail_msg
10923         if msg:
10924           self.lu.LogWarning("Can't remove old LV: %s" % msg,
10925                              hint="remove unused LVs manually")
10926
10927   def _ExecDrbd8DiskOnly(self, feedback_fn): # pylint: disable=W0613
10928     """Replace a disk on the primary or secondary for DRBD 8.
10929
10930     The algorithm for replace is quite complicated:
10931
10932       1. for each disk to be replaced:
10933
10934         1. create new LVs on the target node with unique names
10935         1. detach old LVs from the drbd device
10936         1. rename old LVs to name_replaced.<time_t>
10937         1. rename new LVs to old LVs
10938         1. attach the new LVs (with the old names now) to the drbd device
10939
10940       1. wait for sync across all devices
10941
10942       1. for each modified disk:
10943
10944         1. remove old LVs (which have the name name_replaces.<time_t>)
10945
10946     Failures are not very well handled.
10947
10948     """
10949     steps_total = 6
10950
10951     # Step: check device activation
10952     self.lu.LogStep(1, steps_total, "Check device existence")
10953     self._CheckDisksExistence([self.other_node, self.target_node])
10954     self._CheckVolumeGroup([self.target_node, self.other_node])
10955
10956     # Step: check other node consistency
10957     self.lu.LogStep(2, steps_total, "Check peer consistency")
10958     self._CheckDisksConsistency(self.other_node,
10959                                 self.other_node == self.instance.primary_node,
10960                                 False)
10961
10962     # Step: create new storage
10963     self.lu.LogStep(3, steps_total, "Allocate new storage")
10964     iv_names = self._CreateNewStorage(self.target_node)
10965
10966     # Step: for each lv, detach+rename*2+attach
10967     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
10968     for dev, old_lvs, new_lvs in iv_names.itervalues():
10969       self.lu.LogInfo("Detaching %s drbd from local storage" % dev.iv_name)
10970
10971       result = self.rpc.call_blockdev_removechildren(self.target_node, dev,
10972                                                      old_lvs)
10973       result.Raise("Can't detach drbd from local storage on node"
10974                    " %s for device %s" % (self.target_node, dev.iv_name))
10975       #dev.children = []
10976       #cfg.Update(instance)
10977
10978       # ok, we created the new LVs, so now we know we have the needed
10979       # storage; as such, we proceed on the target node to rename
10980       # old_lv to _old, and new_lv to old_lv; note that we rename LVs
10981       # using the assumption that logical_id == physical_id (which in
10982       # turn is the unique_id on that node)
10983
10984       # FIXME(iustin): use a better name for the replaced LVs
10985       temp_suffix = int(time.time())
10986       ren_fn = lambda d, suff: (d.physical_id[0],
10987                                 d.physical_id[1] + "_replaced-%s" % suff)
10988
10989       # Build the rename list based on what LVs exist on the node
10990       rename_old_to_new = []
10991       for to_ren in old_lvs:
10992         result = self.rpc.call_blockdev_find(self.target_node, to_ren)
10993         if not result.fail_msg and result.payload:
10994           # device exists
10995           rename_old_to_new.append((to_ren, ren_fn(to_ren, temp_suffix)))
10996
10997       self.lu.LogInfo("Renaming the old LVs on the target node")
10998       result = self.rpc.call_blockdev_rename(self.target_node,
10999                                              rename_old_to_new)
11000       result.Raise("Can't rename old LVs on node %s" % self.target_node)
11001
11002       # Now we rename the new LVs to the old LVs
11003       self.lu.LogInfo("Renaming the new LVs on the target node")
11004       rename_new_to_old = [(new, old.physical_id)
11005                            for old, new in zip(old_lvs, new_lvs)]
11006       result = self.rpc.call_blockdev_rename(self.target_node,
11007                                              rename_new_to_old)
11008       result.Raise("Can't rename new LVs on node %s" % self.target_node)
11009
11010       # Intermediate steps of in memory modifications
11011       for old, new in zip(old_lvs, new_lvs):
11012         new.logical_id = old.logical_id
11013         self.cfg.SetDiskID(new, self.target_node)
11014
11015       # We need to modify old_lvs so that removal later removes the
11016       # right LVs, not the newly added ones; note that old_lvs is a
11017       # copy here
11018       for disk in old_lvs:
11019         disk.logical_id = ren_fn(disk, temp_suffix)
11020         self.cfg.SetDiskID(disk, self.target_node)
11021
11022       # Now that the new lvs have the old name, we can add them to the device
11023       self.lu.LogInfo("Adding new mirror component on %s" % self.target_node)
11024       result = self.rpc.call_blockdev_addchildren(self.target_node,
11025                                                   (dev, self.instance), new_lvs)
11026       msg = result.fail_msg
11027       if msg:
11028         for new_lv in new_lvs:
11029           msg2 = self.rpc.call_blockdev_remove(self.target_node,
11030                                                new_lv).fail_msg
11031           if msg2:
11032             self.lu.LogWarning("Can't rollback device %s: %s", dev, msg2,
11033                                hint=("cleanup manually the unused logical"
11034                                      "volumes"))
11035         raise errors.OpExecError("Can't add local storage to drbd: %s" % msg)
11036
11037     cstep = itertools.count(5)
11038
11039     if self.early_release:
11040       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11041       self._RemoveOldStorage(self.target_node, iv_names)
11042       # TODO: Check if releasing locks early still makes sense
11043       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
11044     else:
11045       # Release all resource locks except those used by the instance
11046       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES,
11047                     keep=self.node_secondary_ip.keys())
11048
11049     # Release all node locks while waiting for sync
11050     _ReleaseLocks(self.lu, locking.LEVEL_NODE)
11051
11052     # TODO: Can the instance lock be downgraded here? Take the optional disk
11053     # shutdown in the caller into consideration.
11054
11055     # Wait for sync
11056     # This can fail as the old devices are degraded and _WaitForSync
11057     # does a combined result over all disks, so we don't check its return value
11058     self.lu.LogStep(cstep.next(), steps_total, "Sync devices")
11059     _WaitForSync(self.lu, self.instance)
11060
11061     # Check all devices manually
11062     self._CheckDevices(self.instance.primary_node, iv_names)
11063
11064     # Step: remove old storage
11065     if not self.early_release:
11066       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11067       self._RemoveOldStorage(self.target_node, iv_names)
11068
11069   def _ExecDrbd8Secondary(self, feedback_fn):
11070     """Replace the secondary node for DRBD 8.
11071
11072     The algorithm for replace is quite complicated:
11073       - for all disks of the instance:
11074         - create new LVs on the new node with same names
11075         - shutdown the drbd device on the old secondary
11076         - disconnect the drbd network on the primary
11077         - create the drbd device on the new secondary
11078         - network attach the drbd on the primary, using an artifice:
11079           the drbd code for Attach() will connect to the network if it
11080           finds a device which is connected to the good local disks but
11081           not network enabled
11082       - wait for sync across all devices
11083       - remove all disks from the old secondary
11084
11085     Failures are not very well handled.
11086
11087     """
11088     steps_total = 6
11089
11090     pnode = self.instance.primary_node
11091
11092     # Step: check device activation
11093     self.lu.LogStep(1, steps_total, "Check device existence")
11094     self._CheckDisksExistence([self.instance.primary_node])
11095     self._CheckVolumeGroup([self.instance.primary_node])
11096
11097     # Step: check other node consistency
11098     self.lu.LogStep(2, steps_total, "Check peer consistency")
11099     self._CheckDisksConsistency(self.instance.primary_node, True, True)
11100
11101     # Step: create new storage
11102     self.lu.LogStep(3, steps_total, "Allocate new storage")
11103     disks = _AnnotateDiskParams(self.instance, self.instance.disks, self.cfg)
11104     for idx, dev in enumerate(disks):
11105       self.lu.LogInfo("Adding new local storage on %s for disk/%d" %
11106                       (self.new_node, idx))
11107       # we pass force_create=True to force LVM creation
11108       for new_lv in dev.children:
11109         _CreateBlockDevInner(self.lu, self.new_node, self.instance, new_lv,
11110                              True, _GetInstanceInfoText(self.instance), False)
11111
11112     # Step 4: dbrd minors and drbd setups changes
11113     # after this, we must manually remove the drbd minors on both the
11114     # error and the success paths
11115     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
11116     minors = self.cfg.AllocateDRBDMinor([self.new_node
11117                                          for dev in self.instance.disks],
11118                                         self.instance.name)
11119     logging.debug("Allocated minors %r", minors)
11120
11121     iv_names = {}
11122     for idx, (dev, new_minor) in enumerate(zip(self.instance.disks, minors)):
11123       self.lu.LogInfo("activating a new drbd on %s for disk/%d" %
11124                       (self.new_node, idx))
11125       # create new devices on new_node; note that we create two IDs:
11126       # one without port, so the drbd will be activated without
11127       # networking information on the new node at this stage, and one
11128       # with network, for the latter activation in step 4
11129       (o_node1, o_node2, o_port, o_minor1, o_minor2, o_secret) = dev.logical_id
11130       if self.instance.primary_node == o_node1:
11131         p_minor = o_minor1
11132       else:
11133         assert self.instance.primary_node == o_node2, "Three-node instance?"
11134         p_minor = o_minor2
11135
11136       new_alone_id = (self.instance.primary_node, self.new_node, None,
11137                       p_minor, new_minor, o_secret)
11138       new_net_id = (self.instance.primary_node, self.new_node, o_port,
11139                     p_minor, new_minor, o_secret)
11140
11141       iv_names[idx] = (dev, dev.children, new_net_id)
11142       logging.debug("Allocated new_minor: %s, new_logical_id: %s", new_minor,
11143                     new_net_id)
11144       new_drbd = objects.Disk(dev_type=constants.LD_DRBD8,
11145                               logical_id=new_alone_id,
11146                               children=dev.children,
11147                               size=dev.size,
11148                               params={})
11149       (anno_new_drbd,) = _AnnotateDiskParams(self.instance, [new_drbd],
11150                                              self.cfg)
11151       try:
11152         _CreateSingleBlockDev(self.lu, self.new_node, self.instance,
11153                               anno_new_drbd,
11154                               _GetInstanceInfoText(self.instance), False)
11155       except errors.GenericError:
11156         self.cfg.ReleaseDRBDMinors(self.instance.name)
11157         raise
11158
11159     # We have new devices, shutdown the drbd on the old secondary
11160     for idx, dev in enumerate(self.instance.disks):
11161       self.lu.LogInfo("Shutting down drbd for disk/%d on old node" % idx)
11162       self.cfg.SetDiskID(dev, self.target_node)
11163       msg = self.rpc.call_blockdev_shutdown(self.target_node,
11164                                             (dev, self.instance)).fail_msg
11165       if msg:
11166         self.lu.LogWarning("Failed to shutdown drbd for disk/%d on old"
11167                            "node: %s" % (idx, msg),
11168                            hint=("Please cleanup this device manually as"
11169                                  " soon as possible"))
11170
11171     self.lu.LogInfo("Detaching primary drbds from the network (=> standalone)")
11172     result = self.rpc.call_drbd_disconnect_net([pnode], self.node_secondary_ip,
11173                                                self.instance.disks)[pnode]
11174
11175     msg = result.fail_msg
11176     if msg:
11177       # detaches didn't succeed (unlikely)
11178       self.cfg.ReleaseDRBDMinors(self.instance.name)
11179       raise errors.OpExecError("Can't detach the disks from the network on"
11180                                " old node: %s" % (msg,))
11181
11182     # if we managed to detach at least one, we update all the disks of
11183     # the instance to point to the new secondary
11184     self.lu.LogInfo("Updating instance configuration")
11185     for dev, _, new_logical_id in iv_names.itervalues():
11186       dev.logical_id = new_logical_id
11187       self.cfg.SetDiskID(dev, self.instance.primary_node)
11188
11189     self.cfg.Update(self.instance, feedback_fn)
11190
11191     # Release all node locks (the configuration has been updated)
11192     _ReleaseLocks(self.lu, locking.LEVEL_NODE)
11193
11194     # and now perform the drbd attach
11195     self.lu.LogInfo("Attaching primary drbds to new secondary"
11196                     " (standalone => connected)")
11197     result = self.rpc.call_drbd_attach_net([self.instance.primary_node,
11198                                             self.new_node],
11199                                            self.node_secondary_ip,
11200                                            (self.instance.disks, self.instance),
11201                                            self.instance.name,
11202                                            False)
11203     for to_node, to_result in result.items():
11204       msg = to_result.fail_msg
11205       if msg:
11206         self.lu.LogWarning("Can't attach drbd disks on node %s: %s",
11207                            to_node, msg,
11208                            hint=("please do a gnt-instance info to see the"
11209                                  " status of disks"))
11210
11211     cstep = itertools.count(5)
11212
11213     if self.early_release:
11214       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11215       self._RemoveOldStorage(self.target_node, iv_names)
11216       # TODO: Check if releasing locks early still makes sense
11217       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
11218     else:
11219       # Release all resource locks except those used by the instance
11220       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES,
11221                     keep=self.node_secondary_ip.keys())
11222
11223     # TODO: Can the instance lock be downgraded here? Take the optional disk
11224     # shutdown in the caller into consideration.
11225
11226     # Wait for sync
11227     # This can fail as the old devices are degraded and _WaitForSync
11228     # does a combined result over all disks, so we don't check its return value
11229     self.lu.LogStep(cstep.next(), steps_total, "Sync devices")
11230     _WaitForSync(self.lu, self.instance)
11231
11232     # Check all devices manually
11233     self._CheckDevices(self.instance.primary_node, iv_names)
11234
11235     # Step: remove old storage
11236     if not self.early_release:
11237       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11238       self._RemoveOldStorage(self.target_node, iv_names)
11239
11240
11241 class LURepairNodeStorage(NoHooksLU):
11242   """Repairs the volume group on a node.
11243
11244   """
11245   REQ_BGL = False
11246
11247   def CheckArguments(self):
11248     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
11249
11250     storage_type = self.op.storage_type
11251
11252     if (constants.SO_FIX_CONSISTENCY not in
11253         constants.VALID_STORAGE_OPERATIONS.get(storage_type, [])):
11254       raise errors.OpPrereqError("Storage units of type '%s' can not be"
11255                                  " repaired" % storage_type,
11256                                  errors.ECODE_INVAL)
11257
11258   def ExpandNames(self):
11259     self.needed_locks = {
11260       locking.LEVEL_NODE: [self.op.node_name],
11261       }
11262
11263   def _CheckFaultyDisks(self, instance, node_name):
11264     """Ensure faulty disks abort the opcode or at least warn."""
11265     try:
11266       if _FindFaultyInstanceDisks(self.cfg, self.rpc, instance,
11267                                   node_name, True):
11268         raise errors.OpPrereqError("Instance '%s' has faulty disks on"
11269                                    " node '%s'" % (instance.name, node_name),
11270                                    errors.ECODE_STATE)
11271     except errors.OpPrereqError, err:
11272       if self.op.ignore_consistency:
11273         self.proc.LogWarning(str(err.args[0]))
11274       else:
11275         raise
11276
11277   def CheckPrereq(self):
11278     """Check prerequisites.
11279
11280     """
11281     # Check whether any instance on this node has faulty disks
11282     for inst in _GetNodeInstances(self.cfg, self.op.node_name):
11283       if inst.admin_state != constants.ADMINST_UP:
11284         continue
11285       check_nodes = set(inst.all_nodes)
11286       check_nodes.discard(self.op.node_name)
11287       for inst_node_name in check_nodes:
11288         self._CheckFaultyDisks(inst, inst_node_name)
11289
11290   def Exec(self, feedback_fn):
11291     feedback_fn("Repairing storage unit '%s' on %s ..." %
11292                 (self.op.name, self.op.node_name))
11293
11294     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
11295     result = self.rpc.call_storage_execute(self.op.node_name,
11296                                            self.op.storage_type, st_args,
11297                                            self.op.name,
11298                                            constants.SO_FIX_CONSISTENCY)
11299     result.Raise("Failed to repair storage unit '%s' on %s" %
11300                  (self.op.name, self.op.node_name))
11301
11302
11303 class LUNodeEvacuate(NoHooksLU):
11304   """Evacuates instances off a list of nodes.
11305
11306   """
11307   REQ_BGL = False
11308
11309   _MODE2IALLOCATOR = {
11310     constants.NODE_EVAC_PRI: constants.IALLOCATOR_NEVAC_PRI,
11311     constants.NODE_EVAC_SEC: constants.IALLOCATOR_NEVAC_SEC,
11312     constants.NODE_EVAC_ALL: constants.IALLOCATOR_NEVAC_ALL,
11313     }
11314   assert frozenset(_MODE2IALLOCATOR.keys()) == constants.NODE_EVAC_MODES
11315   assert (frozenset(_MODE2IALLOCATOR.values()) ==
11316           constants.IALLOCATOR_NEVAC_MODES)
11317
11318   def CheckArguments(self):
11319     _CheckIAllocatorOrNode(self, "iallocator", "remote_node")
11320
11321   def ExpandNames(self):
11322     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
11323
11324     if self.op.remote_node is not None:
11325       self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
11326       assert self.op.remote_node
11327
11328       if self.op.remote_node == self.op.node_name:
11329         raise errors.OpPrereqError("Can not use evacuated node as a new"
11330                                    " secondary node", errors.ECODE_INVAL)
11331
11332       if self.op.mode != constants.NODE_EVAC_SEC:
11333         raise errors.OpPrereqError("Without the use of an iallocator only"
11334                                    " secondary instances can be evacuated",
11335                                    errors.ECODE_INVAL)
11336
11337     # Declare locks
11338     self.share_locks = _ShareAll()
11339     self.needed_locks = {
11340       locking.LEVEL_INSTANCE: [],
11341       locking.LEVEL_NODEGROUP: [],
11342       locking.LEVEL_NODE: [],
11343       }
11344
11345     # Determine nodes (via group) optimistically, needs verification once locks
11346     # have been acquired
11347     self.lock_nodes = self._DetermineNodes()
11348
11349   def _DetermineNodes(self):
11350     """Gets the list of nodes to operate on.
11351
11352     """
11353     if self.op.remote_node is None:
11354       # Iallocator will choose any node(s) in the same group
11355       group_nodes = self.cfg.GetNodeGroupMembersByNodes([self.op.node_name])
11356     else:
11357       group_nodes = frozenset([self.op.remote_node])
11358
11359     # Determine nodes to be locked
11360     return set([self.op.node_name]) | group_nodes
11361
11362   def _DetermineInstances(self):
11363     """Builds list of instances to operate on.
11364
11365     """
11366     assert self.op.mode in constants.NODE_EVAC_MODES
11367
11368     if self.op.mode == constants.NODE_EVAC_PRI:
11369       # Primary instances only
11370       inst_fn = _GetNodePrimaryInstances
11371       assert self.op.remote_node is None, \
11372         "Evacuating primary instances requires iallocator"
11373     elif self.op.mode == constants.NODE_EVAC_SEC:
11374       # Secondary instances only
11375       inst_fn = _GetNodeSecondaryInstances
11376     else:
11377       # All instances
11378       assert self.op.mode == constants.NODE_EVAC_ALL
11379       inst_fn = _GetNodeInstances
11380       # TODO: In 2.6, change the iallocator interface to take an evacuation mode
11381       # per instance
11382       raise errors.OpPrereqError("Due to an issue with the iallocator"
11383                                  " interface it is not possible to evacuate"
11384                                  " all instances at once; specify explicitly"
11385                                  " whether to evacuate primary or secondary"
11386                                  " instances",
11387                                  errors.ECODE_INVAL)
11388
11389     return inst_fn(self.cfg, self.op.node_name)
11390
11391   def DeclareLocks(self, level):
11392     if level == locking.LEVEL_INSTANCE:
11393       # Lock instances optimistically, needs verification once node and group
11394       # locks have been acquired
11395       self.needed_locks[locking.LEVEL_INSTANCE] = \
11396         set(i.name for i in self._DetermineInstances())
11397
11398     elif level == locking.LEVEL_NODEGROUP:
11399       # Lock node groups for all potential target nodes optimistically, needs
11400       # verification once nodes have been acquired
11401       self.needed_locks[locking.LEVEL_NODEGROUP] = \
11402         self.cfg.GetNodeGroupsFromNodes(self.lock_nodes)
11403
11404     elif level == locking.LEVEL_NODE:
11405       self.needed_locks[locking.LEVEL_NODE] = self.lock_nodes
11406
11407   def CheckPrereq(self):
11408     # Verify locks
11409     owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
11410     owned_nodes = self.owned_locks(locking.LEVEL_NODE)
11411     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
11412
11413     need_nodes = self._DetermineNodes()
11414
11415     if not owned_nodes.issuperset(need_nodes):
11416       raise errors.OpPrereqError("Nodes in same group as '%s' changed since"
11417                                  " locks were acquired, current nodes are"
11418                                  " are '%s', used to be '%s'; retry the"
11419                                  " operation" %
11420                                  (self.op.node_name,
11421                                   utils.CommaJoin(need_nodes),
11422                                   utils.CommaJoin(owned_nodes)),
11423                                  errors.ECODE_STATE)
11424
11425     wanted_groups = self.cfg.GetNodeGroupsFromNodes(owned_nodes)
11426     if owned_groups != wanted_groups:
11427       raise errors.OpExecError("Node groups changed since locks were acquired,"
11428                                " current groups are '%s', used to be '%s';"
11429                                " retry the operation" %
11430                                (utils.CommaJoin(wanted_groups),
11431                                 utils.CommaJoin(owned_groups)))
11432
11433     # Determine affected instances
11434     self.instances = self._DetermineInstances()
11435     self.instance_names = [i.name for i in self.instances]
11436
11437     if set(self.instance_names) != owned_instances:
11438       raise errors.OpExecError("Instances on node '%s' changed since locks"
11439                                " were acquired, current instances are '%s',"
11440                                " used to be '%s'; retry the operation" %
11441                                (self.op.node_name,
11442                                 utils.CommaJoin(self.instance_names),
11443                                 utils.CommaJoin(owned_instances)))
11444
11445     if self.instance_names:
11446       self.LogInfo("Evacuating instances from node '%s': %s",
11447                    self.op.node_name,
11448                    utils.CommaJoin(utils.NiceSort(self.instance_names)))
11449     else:
11450       self.LogInfo("No instances to evacuate from node '%s'",
11451                    self.op.node_name)
11452
11453     if self.op.remote_node is not None:
11454       for i in self.instances:
11455         if i.primary_node == self.op.remote_node:
11456           raise errors.OpPrereqError("Node %s is the primary node of"
11457                                      " instance %s, cannot use it as"
11458                                      " secondary" %
11459                                      (self.op.remote_node, i.name),
11460                                      errors.ECODE_INVAL)
11461
11462   def Exec(self, feedback_fn):
11463     assert (self.op.iallocator is not None) ^ (self.op.remote_node is not None)
11464
11465     if not self.instance_names:
11466       # No instances to evacuate
11467       jobs = []
11468
11469     elif self.op.iallocator is not None:
11470       # TODO: Implement relocation to other group
11471       ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_NODE_EVAC,
11472                        evac_mode=self._MODE2IALLOCATOR[self.op.mode],
11473                        instances=list(self.instance_names))
11474
11475       ial.Run(self.op.iallocator)
11476
11477       if not ial.success:
11478         raise errors.OpPrereqError("Can't compute node evacuation using"
11479                                    " iallocator '%s': %s" %
11480                                    (self.op.iallocator, ial.info),
11481                                    errors.ECODE_NORES)
11482
11483       jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, True)
11484
11485     elif self.op.remote_node is not None:
11486       assert self.op.mode == constants.NODE_EVAC_SEC
11487       jobs = [
11488         [opcodes.OpInstanceReplaceDisks(instance_name=instance_name,
11489                                         remote_node=self.op.remote_node,
11490                                         disks=[],
11491                                         mode=constants.REPLACE_DISK_CHG,
11492                                         early_release=self.op.early_release)]
11493         for instance_name in self.instance_names
11494         ]
11495
11496     else:
11497       raise errors.ProgrammerError("No iallocator or remote node")
11498
11499     return ResultWithJobs(jobs)
11500
11501
11502 def _SetOpEarlyRelease(early_release, op):
11503   """Sets C{early_release} flag on opcodes if available.
11504
11505   """
11506   try:
11507     op.early_release = early_release
11508   except AttributeError:
11509     assert not isinstance(op, opcodes.OpInstanceReplaceDisks)
11510
11511   return op
11512
11513
11514 def _NodeEvacDest(use_nodes, group, nodes):
11515   """Returns group or nodes depending on caller's choice.
11516
11517   """
11518   if use_nodes:
11519     return utils.CommaJoin(nodes)
11520   else:
11521     return group
11522
11523
11524 def _LoadNodeEvacResult(lu, alloc_result, early_release, use_nodes):
11525   """Unpacks the result of change-group and node-evacuate iallocator requests.
11526
11527   Iallocator modes L{constants.IALLOCATOR_MODE_NODE_EVAC} and
11528   L{constants.IALLOCATOR_MODE_CHG_GROUP}.
11529
11530   @type lu: L{LogicalUnit}
11531   @param lu: Logical unit instance
11532   @type alloc_result: tuple/list
11533   @param alloc_result: Result from iallocator
11534   @type early_release: bool
11535   @param early_release: Whether to release locks early if possible
11536   @type use_nodes: bool
11537   @param use_nodes: Whether to display node names instead of groups
11538
11539   """
11540   (moved, failed, jobs) = alloc_result
11541
11542   if failed:
11543     failreason = utils.CommaJoin("%s (%s)" % (name, reason)
11544                                  for (name, reason) in failed)
11545     lu.LogWarning("Unable to evacuate instances %s", failreason)
11546     raise errors.OpExecError("Unable to evacuate instances %s" % failreason)
11547
11548   if moved:
11549     lu.LogInfo("Instances to be moved: %s",
11550                utils.CommaJoin("%s (to %s)" %
11551                                (name, _NodeEvacDest(use_nodes, group, nodes))
11552                                for (name, group, nodes) in moved))
11553
11554   return [map(compat.partial(_SetOpEarlyRelease, early_release),
11555               map(opcodes.OpCode.LoadOpCode, ops))
11556           for ops in jobs]
11557
11558
11559 class LUInstanceGrowDisk(LogicalUnit):
11560   """Grow a disk of an instance.
11561
11562   """
11563   HPATH = "disk-grow"
11564   HTYPE = constants.HTYPE_INSTANCE
11565   REQ_BGL = False
11566
11567   def ExpandNames(self):
11568     self._ExpandAndLockInstance()
11569     self.needed_locks[locking.LEVEL_NODE] = []
11570     self.needed_locks[locking.LEVEL_NODE_RES] = []
11571     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
11572     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
11573
11574   def DeclareLocks(self, level):
11575     if level == locking.LEVEL_NODE:
11576       self._LockInstancesNodes()
11577     elif level == locking.LEVEL_NODE_RES:
11578       # Copy node locks
11579       self.needed_locks[locking.LEVEL_NODE_RES] = \
11580         self.needed_locks[locking.LEVEL_NODE][:]
11581
11582   def BuildHooksEnv(self):
11583     """Build hooks env.
11584
11585     This runs on the master, the primary and all the secondaries.
11586
11587     """
11588     env = {
11589       "DISK": self.op.disk,
11590       "AMOUNT": self.op.amount,
11591       "ABSOLUTE": self.op.absolute,
11592       }
11593     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
11594     return env
11595
11596   def BuildHooksNodes(self):
11597     """Build hooks nodes.
11598
11599     """
11600     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
11601     return (nl, nl)
11602
11603   def CheckPrereq(self):
11604     """Check prerequisites.
11605
11606     This checks that the instance is in the cluster.
11607
11608     """
11609     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
11610     assert instance is not None, \
11611       "Cannot retrieve locked instance %s" % self.op.instance_name
11612     nodenames = list(instance.all_nodes)
11613     for node in nodenames:
11614       _CheckNodeOnline(self, node)
11615
11616     self.instance = instance
11617
11618     if instance.disk_template not in constants.DTS_GROWABLE:
11619       raise errors.OpPrereqError("Instance's disk layout does not support"
11620                                  " growing", errors.ECODE_INVAL)
11621
11622     self.disk = instance.FindDisk(self.op.disk)
11623
11624     if self.op.absolute:
11625       self.target = self.op.amount
11626       self.delta = self.target - self.disk.size
11627       if self.delta < 0:
11628         raise errors.OpPrereqError("Requested size (%s) is smaller than "
11629                                    "current disk size (%s)" %
11630                                    (utils.FormatUnit(self.target, "h"),
11631                                     utils.FormatUnit(self.disk.size, "h")),
11632                                    errors.ECODE_STATE)
11633     else:
11634       self.delta = self.op.amount
11635       self.target = self.disk.size + self.delta
11636       if self.delta < 0:
11637         raise errors.OpPrereqError("Requested increment (%s) is negative" %
11638                                    utils.FormatUnit(self.delta, "h"),
11639                                    errors.ECODE_INVAL)
11640
11641     if instance.disk_template not in (constants.DT_FILE,
11642                                       constants.DT_SHARED_FILE,
11643                                       constants.DT_RBD):
11644       # TODO: check the free disk space for file, when that feature will be
11645       # supported
11646       _CheckNodesFreeDiskPerVG(self, nodenames,
11647                                self.disk.ComputeGrowth(self.delta))
11648
11649   def Exec(self, feedback_fn):
11650     """Execute disk grow.
11651
11652     """
11653     instance = self.instance
11654     disk = self.disk
11655
11656     assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
11657     assert (self.owned_locks(locking.LEVEL_NODE) ==
11658             self.owned_locks(locking.LEVEL_NODE_RES))
11659
11660     disks_ok, _ = _AssembleInstanceDisks(self, self.instance, disks=[disk])
11661     if not disks_ok:
11662       raise errors.OpExecError("Cannot activate block device to grow")
11663
11664     feedback_fn("Growing disk %s of instance '%s' by %s to %s" %
11665                 (self.op.disk, instance.name,
11666                  utils.FormatUnit(self.delta, "h"),
11667                  utils.FormatUnit(self.target, "h")))
11668
11669     # First run all grow ops in dry-run mode
11670     for node in instance.all_nodes:
11671       self.cfg.SetDiskID(disk, node)
11672       result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
11673                                            True)
11674       result.Raise("Grow request failed to node %s" % node)
11675
11676     # We know that (as far as we can test) operations across different
11677     # nodes will succeed, time to run it for real
11678     for node in instance.all_nodes:
11679       self.cfg.SetDiskID(disk, node)
11680       result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
11681                                            False)
11682       result.Raise("Grow request failed to node %s" % node)
11683
11684       # TODO: Rewrite code to work properly
11685       # DRBD goes into sync mode for a short amount of time after executing the
11686       # "resize" command. DRBD 8.x below version 8.0.13 contains a bug whereby
11687       # calling "resize" in sync mode fails. Sleeping for a short amount of
11688       # time is a work-around.
11689       time.sleep(5)
11690
11691     disk.RecordGrow(self.delta)
11692     self.cfg.Update(instance, feedback_fn)
11693
11694     # Changes have been recorded, release node lock
11695     _ReleaseLocks(self, locking.LEVEL_NODE)
11696
11697     # Downgrade lock while waiting for sync
11698     self.glm.downgrade(locking.LEVEL_INSTANCE)
11699
11700     if self.op.wait_for_sync:
11701       disk_abort = not _WaitForSync(self, instance, disks=[disk])
11702       if disk_abort:
11703         self.proc.LogWarning("Disk sync-ing has not returned a good"
11704                              " status; please check the instance")
11705       if instance.admin_state != constants.ADMINST_UP:
11706         _SafeShutdownInstanceDisks(self, instance, disks=[disk])
11707     elif instance.admin_state != constants.ADMINST_UP:
11708       self.proc.LogWarning("Not shutting down the disk even if the instance is"
11709                            " not supposed to be running because no wait for"
11710                            " sync mode was requested")
11711
11712     assert self.owned_locks(locking.LEVEL_NODE_RES)
11713     assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
11714
11715
11716 class LUInstanceQueryData(NoHooksLU):
11717   """Query runtime instance data.
11718
11719   """
11720   REQ_BGL = False
11721
11722   def ExpandNames(self):
11723     self.needed_locks = {}
11724
11725     # Use locking if requested or when non-static information is wanted
11726     if not (self.op.static or self.op.use_locking):
11727       self.LogWarning("Non-static data requested, locks need to be acquired")
11728       self.op.use_locking = True
11729
11730     if self.op.instances or not self.op.use_locking:
11731       # Expand instance names right here
11732       self.wanted_names = _GetWantedInstances(self, self.op.instances)
11733     else:
11734       # Will use acquired locks
11735       self.wanted_names = None
11736
11737     if self.op.use_locking:
11738       self.share_locks = _ShareAll()
11739
11740       if self.wanted_names is None:
11741         self.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET
11742       else:
11743         self.needed_locks[locking.LEVEL_INSTANCE] = self.wanted_names
11744
11745       self.needed_locks[locking.LEVEL_NODEGROUP] = []
11746       self.needed_locks[locking.LEVEL_NODE] = []
11747       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
11748
11749   def DeclareLocks(self, level):
11750     if self.op.use_locking:
11751       if level == locking.LEVEL_NODEGROUP:
11752         owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
11753
11754         # Lock all groups used by instances optimistically; this requires going
11755         # via the node before it's locked, requiring verification later on
11756         self.needed_locks[locking.LEVEL_NODEGROUP] = \
11757           frozenset(group_uuid
11758                     for instance_name in owned_instances
11759                     for group_uuid in
11760                       self.cfg.GetInstanceNodeGroups(instance_name))
11761
11762       elif level == locking.LEVEL_NODE:
11763         self._LockInstancesNodes()
11764
11765   def CheckPrereq(self):
11766     """Check prerequisites.
11767
11768     This only checks the optional instance list against the existing names.
11769
11770     """
11771     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
11772     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
11773     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
11774
11775     if self.wanted_names is None:
11776       assert self.op.use_locking, "Locking was not used"
11777       self.wanted_names = owned_instances
11778
11779     instances = dict(self.cfg.GetMultiInstanceInfo(self.wanted_names))
11780
11781     if self.op.use_locking:
11782       _CheckInstancesNodeGroups(self.cfg, instances, owned_groups, owned_nodes,
11783                                 None)
11784     else:
11785       assert not (owned_instances or owned_groups or owned_nodes)
11786
11787     self.wanted_instances = instances.values()
11788
11789   def _ComputeBlockdevStatus(self, node, instance, dev):
11790     """Returns the status of a block device
11791
11792     """
11793     if self.op.static or not node:
11794       return None
11795
11796     self.cfg.SetDiskID(dev, node)
11797
11798     result = self.rpc.call_blockdev_find(node, dev)
11799     if result.offline:
11800       return None
11801
11802     result.Raise("Can't compute disk status for %s" % instance.name)
11803
11804     status = result.payload
11805     if status is None:
11806       return None
11807
11808     return (status.dev_path, status.major, status.minor,
11809             status.sync_percent, status.estimated_time,
11810             status.is_degraded, status.ldisk_status)
11811
11812   def _ComputeDiskStatus(self, instance, snode, dev):
11813     """Compute block device status.
11814
11815     """
11816     (anno_dev,) = _AnnotateDiskParams(instance, [dev], self.cfg)
11817
11818     return self._ComputeDiskStatusInner(instance, snode, anno_dev)
11819
11820   def _ComputeDiskStatusInner(self, instance, snode, dev):
11821     """Compute block device status.
11822
11823     @attention: The device has to be annotated already.
11824
11825     """
11826     if dev.dev_type in constants.LDS_DRBD:
11827       # we change the snode then (otherwise we use the one passed in)
11828       if dev.logical_id[0] == instance.primary_node:
11829         snode = dev.logical_id[1]
11830       else:
11831         snode = dev.logical_id[0]
11832
11833     dev_pstatus = self._ComputeBlockdevStatus(instance.primary_node,
11834                                               instance, dev)
11835     dev_sstatus = self._ComputeBlockdevStatus(snode, instance, dev)
11836
11837     if dev.children:
11838       dev_children = map(compat.partial(self._ComputeDiskStatusInner,
11839                                         instance, snode),
11840                          dev.children)
11841     else:
11842       dev_children = []
11843
11844     return {
11845       "iv_name": dev.iv_name,
11846       "dev_type": dev.dev_type,
11847       "logical_id": dev.logical_id,
11848       "physical_id": dev.physical_id,
11849       "pstatus": dev_pstatus,
11850       "sstatus": dev_sstatus,
11851       "children": dev_children,
11852       "mode": dev.mode,
11853       "size": dev.size,
11854       }
11855
11856   def Exec(self, feedback_fn):
11857     """Gather and return data"""
11858     result = {}
11859
11860     cluster = self.cfg.GetClusterInfo()
11861
11862     node_names = itertools.chain(*(i.all_nodes for i in self.wanted_instances))
11863     nodes = dict(self.cfg.GetMultiNodeInfo(node_names))
11864
11865     groups = dict(self.cfg.GetMultiNodeGroupInfo(node.group
11866                                                  for node in nodes.values()))
11867
11868     group2name_fn = lambda uuid: groups[uuid].name
11869
11870     for instance in self.wanted_instances:
11871       pnode = nodes[instance.primary_node]
11872
11873       if self.op.static or pnode.offline:
11874         remote_state = None
11875         if pnode.offline:
11876           self.LogWarning("Primary node %s is marked offline, returning static"
11877                           " information only for instance %s" %
11878                           (pnode.name, instance.name))
11879       else:
11880         remote_info = self.rpc.call_instance_info(instance.primary_node,
11881                                                   instance.name,
11882                                                   instance.hypervisor)
11883         remote_info.Raise("Error checking node %s" % instance.primary_node)
11884         remote_info = remote_info.payload
11885         if remote_info and "state" in remote_info:
11886           remote_state = "up"
11887         else:
11888           if instance.admin_state == constants.ADMINST_UP:
11889             remote_state = "down"
11890           else:
11891             remote_state = instance.admin_state
11892
11893       disks = map(compat.partial(self._ComputeDiskStatus, instance, None),
11894                   instance.disks)
11895
11896       snodes_group_uuids = [nodes[snode_name].group
11897                             for snode_name in instance.secondary_nodes]
11898
11899       result[instance.name] = {
11900         "name": instance.name,
11901         "config_state": instance.admin_state,
11902         "run_state": remote_state,
11903         "pnode": instance.primary_node,
11904         "pnode_group_uuid": pnode.group,
11905         "pnode_group_name": group2name_fn(pnode.group),
11906         "snodes": instance.secondary_nodes,
11907         "snodes_group_uuids": snodes_group_uuids,
11908         "snodes_group_names": map(group2name_fn, snodes_group_uuids),
11909         "os": instance.os,
11910         # this happens to be the same format used for hooks
11911         "nics": _NICListToTuple(self, instance.nics),
11912         "disk_template": instance.disk_template,
11913         "disks": disks,
11914         "hypervisor": instance.hypervisor,
11915         "network_port": instance.network_port,
11916         "hv_instance": instance.hvparams,
11917         "hv_actual": cluster.FillHV(instance, skip_globals=True),
11918         "be_instance": instance.beparams,
11919         "be_actual": cluster.FillBE(instance),
11920         "os_instance": instance.osparams,
11921         "os_actual": cluster.SimpleFillOS(instance.os, instance.osparams),
11922         "serial_no": instance.serial_no,
11923         "mtime": instance.mtime,
11924         "ctime": instance.ctime,
11925         "uuid": instance.uuid,
11926         }
11927
11928     return result
11929
11930
11931 def PrepareContainerMods(mods, private_fn):
11932   """Prepares a list of container modifications by adding a private data field.
11933
11934   @type mods: list of tuples; (operation, index, parameters)
11935   @param mods: List of modifications
11936   @type private_fn: callable or None
11937   @param private_fn: Callable for constructing a private data field for a
11938     modification
11939   @rtype: list
11940
11941   """
11942   if private_fn is None:
11943     fn = lambda: None
11944   else:
11945     fn = private_fn
11946
11947   return [(op, idx, params, fn()) for (op, idx, params) in mods]
11948
11949
11950 #: Type description for changes as returned by L{ApplyContainerMods}'s
11951 #: callbacks
11952 _TApplyContModsCbChanges = \
11953   ht.TMaybeListOf(ht.TAnd(ht.TIsLength(2), ht.TItems([
11954     ht.TNonEmptyString,
11955     ht.TAny,
11956     ])))
11957
11958
11959 def ApplyContainerMods(kind, container, chgdesc, mods,
11960                        create_fn, modify_fn, remove_fn):
11961   """Applies descriptions in C{mods} to C{container}.
11962
11963   @type kind: string
11964   @param kind: One-word item description
11965   @type container: list
11966   @param container: Container to modify
11967   @type chgdesc: None or list
11968   @param chgdesc: List of applied changes
11969   @type mods: list
11970   @param mods: Modifications as returned by L{PrepareContainerMods}
11971   @type create_fn: callable
11972   @param create_fn: Callback for creating a new item (L{constants.DDM_ADD});
11973     receives absolute item index, parameters and private data object as added
11974     by L{PrepareContainerMods}, returns tuple containing new item and changes
11975     as list
11976   @type modify_fn: callable
11977   @param modify_fn: Callback for modifying an existing item
11978     (L{constants.DDM_MODIFY}); receives absolute item index, item, parameters
11979     and private data object as added by L{PrepareContainerMods}, returns
11980     changes as list
11981   @type remove_fn: callable
11982   @param remove_fn: Callback on removing item; receives absolute item index,
11983     item and private data object as added by L{PrepareContainerMods}
11984
11985   """
11986   for (op, idx, params, private) in mods:
11987     if idx == -1:
11988       # Append
11989       absidx = len(container) - 1
11990     elif idx < 0:
11991       raise IndexError("Not accepting negative indices other than -1")
11992     elif idx > len(container):
11993       raise IndexError("Got %s index %s, but there are only %s" %
11994                        (kind, idx, len(container)))
11995     else:
11996       absidx = idx
11997
11998     changes = None
11999
12000     if op == constants.DDM_ADD:
12001       # Calculate where item will be added
12002       if idx == -1:
12003         addidx = len(container)
12004       else:
12005         addidx = idx
12006
12007       if create_fn is None:
12008         item = params
12009       else:
12010         (item, changes) = create_fn(addidx, params, private)
12011
12012       if idx == -1:
12013         container.append(item)
12014       else:
12015         assert idx >= 0
12016         assert idx <= len(container)
12017         # list.insert does so before the specified index
12018         container.insert(idx, item)
12019     else:
12020       # Retrieve existing item
12021       try:
12022         item = container[absidx]
12023       except IndexError:
12024         raise IndexError("Invalid %s index %s" % (kind, idx))
12025
12026       if op == constants.DDM_REMOVE:
12027         assert not params
12028
12029         if remove_fn is not None:
12030           remove_fn(absidx, item, private)
12031
12032         changes = [("%s/%s" % (kind, absidx), "remove")]
12033
12034         assert container[absidx] == item
12035         del container[absidx]
12036       elif op == constants.DDM_MODIFY:
12037         if modify_fn is not None:
12038           changes = modify_fn(absidx, item, params, private)
12039       else:
12040         raise errors.ProgrammerError("Unhandled operation '%s'" % op)
12041
12042     assert _TApplyContModsCbChanges(changes)
12043
12044     if not (chgdesc is None or changes is None):
12045       chgdesc.extend(changes)
12046
12047
12048 def _UpdateIvNames(base_index, disks):
12049   """Updates the C{iv_name} attribute of disks.
12050
12051   @type disks: list of L{objects.Disk}
12052
12053   """
12054   for (idx, disk) in enumerate(disks):
12055     disk.iv_name = "disk/%s" % (base_index + idx, )
12056
12057
12058 class _InstNicModPrivate:
12059   """Data structure for network interface modifications.
12060
12061   Used by L{LUInstanceSetParams}.
12062
12063   """
12064   def __init__(self):
12065     self.params = None
12066     self.filled = None
12067
12068
12069 class LUInstanceSetParams(LogicalUnit):
12070   """Modifies an instances's parameters.
12071
12072   """
12073   HPATH = "instance-modify"
12074   HTYPE = constants.HTYPE_INSTANCE
12075   REQ_BGL = False
12076
12077   @staticmethod
12078   def _UpgradeDiskNicMods(kind, mods, verify_fn):
12079     assert ht.TList(mods)
12080     assert not mods or len(mods[0]) in (2, 3)
12081
12082     if mods and len(mods[0]) == 2:
12083       result = []
12084
12085       addremove = 0
12086       for op, params in mods:
12087         if op in (constants.DDM_ADD, constants.DDM_REMOVE):
12088           result.append((op, -1, params))
12089           addremove += 1
12090
12091           if addremove > 1:
12092             raise errors.OpPrereqError("Only one %s add or remove operation is"
12093                                        " supported at a time" % kind,
12094                                        errors.ECODE_INVAL)
12095         else:
12096           result.append((constants.DDM_MODIFY, op, params))
12097
12098       assert verify_fn(result)
12099     else:
12100       result = mods
12101
12102     return result
12103
12104   @staticmethod
12105   def _CheckMods(kind, mods, key_types, item_fn):
12106     """Ensures requested disk/NIC modifications are valid.
12107
12108     """
12109     for (op, _, params) in mods:
12110       assert ht.TDict(params)
12111
12112       utils.ForceDictType(params, key_types)
12113
12114       if op == constants.DDM_REMOVE:
12115         if params:
12116           raise errors.OpPrereqError("No settings should be passed when"
12117                                      " removing a %s" % kind,
12118                                      errors.ECODE_INVAL)
12119       elif op in (constants.DDM_ADD, constants.DDM_MODIFY):
12120         item_fn(op, params)
12121       else:
12122         raise errors.ProgrammerError("Unhandled operation '%s'" % op)
12123
12124   @staticmethod
12125   def _VerifyDiskModification(op, params):
12126     """Verifies a disk modification.
12127
12128     """
12129     if op == constants.DDM_ADD:
12130       mode = params.setdefault(constants.IDISK_MODE, constants.DISK_RDWR)
12131       if mode not in constants.DISK_ACCESS_SET:
12132         raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
12133                                    errors.ECODE_INVAL)
12134
12135       size = params.get(constants.IDISK_SIZE, None)
12136       if size is None:
12137         raise errors.OpPrereqError("Required disk parameter '%s' missing" %
12138                                    constants.IDISK_SIZE, errors.ECODE_INVAL)
12139
12140       try:
12141         size = int(size)
12142       except (TypeError, ValueError), err:
12143         raise errors.OpPrereqError("Invalid disk size parameter: %s" % err,
12144                                    errors.ECODE_INVAL)
12145
12146       params[constants.IDISK_SIZE] = size
12147
12148     elif op == constants.DDM_MODIFY and constants.IDISK_SIZE in params:
12149       raise errors.OpPrereqError("Disk size change not possible, use"
12150                                  " grow-disk", errors.ECODE_INVAL)
12151
12152   @staticmethod
12153   def _VerifyNicModification(op, params):
12154     """Verifies a network interface modification.
12155
12156     """
12157     if op in (constants.DDM_ADD, constants.DDM_MODIFY):
12158       ip = params.get(constants.INIC_IP, None)
12159       if ip is None:
12160         pass
12161       elif ip.lower() == constants.VALUE_NONE:
12162         params[constants.INIC_IP] = None
12163       elif not netutils.IPAddress.IsValid(ip):
12164         raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
12165                                    errors.ECODE_INVAL)
12166
12167       bridge = params.get("bridge", None)
12168       link = params.get(constants.INIC_LINK, None)
12169       if bridge and link:
12170         raise errors.OpPrereqError("Cannot pass 'bridge' and 'link'"
12171                                    " at the same time", errors.ECODE_INVAL)
12172       elif bridge and bridge.lower() == constants.VALUE_NONE:
12173         params["bridge"] = None
12174       elif link and link.lower() == constants.VALUE_NONE:
12175         params[constants.INIC_LINK] = None
12176
12177       if op == constants.DDM_ADD:
12178         macaddr = params.get(constants.INIC_MAC, None)
12179         if macaddr is None:
12180           params[constants.INIC_MAC] = constants.VALUE_AUTO
12181
12182       if constants.INIC_MAC in params:
12183         macaddr = params[constants.INIC_MAC]
12184         if macaddr not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
12185           macaddr = utils.NormalizeAndValidateMac(macaddr)
12186
12187         if op == constants.DDM_MODIFY and macaddr == constants.VALUE_AUTO:
12188           raise errors.OpPrereqError("'auto' is not a valid MAC address when"
12189                                      " modifying an existing NIC",
12190                                      errors.ECODE_INVAL)
12191
12192   def CheckArguments(self):
12193     if not (self.op.nics or self.op.disks or self.op.disk_template or
12194             self.op.hvparams or self.op.beparams or self.op.os_name or
12195             self.op.offline is not None or self.op.runtime_mem):
12196       raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
12197
12198     if self.op.hvparams:
12199       _CheckGlobalHvParams(self.op.hvparams)
12200
12201     self.op.disks = \
12202       self._UpgradeDiskNicMods("disk", self.op.disks,
12203         opcodes.OpInstanceSetParams.TestDiskModifications)
12204     self.op.nics = \
12205       self._UpgradeDiskNicMods("NIC", self.op.nics,
12206         opcodes.OpInstanceSetParams.TestNicModifications)
12207
12208     # Check disk modifications
12209     self._CheckMods("disk", self.op.disks, constants.IDISK_PARAMS_TYPES,
12210                     self._VerifyDiskModification)
12211
12212     if self.op.disks and self.op.disk_template is not None:
12213       raise errors.OpPrereqError("Disk template conversion and other disk"
12214                                  " changes not supported at the same time",
12215                                  errors.ECODE_INVAL)
12216
12217     if (self.op.disk_template and
12218         self.op.disk_template in constants.DTS_INT_MIRROR and
12219         self.op.remote_node is None):
12220       raise errors.OpPrereqError("Changing the disk template to a mirrored"
12221                                  " one requires specifying a secondary node",
12222                                  errors.ECODE_INVAL)
12223
12224     # Check NIC modifications
12225     self._CheckMods("NIC", self.op.nics, constants.INIC_PARAMS_TYPES,
12226                     self._VerifyNicModification)
12227
12228   def ExpandNames(self):
12229     self._ExpandAndLockInstance()
12230     # Can't even acquire node locks in shared mode as upcoming changes in
12231     # Ganeti 2.6 will start to modify the node object on disk conversion
12232     self.needed_locks[locking.LEVEL_NODE] = []
12233     self.needed_locks[locking.LEVEL_NODE_RES] = []
12234     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
12235
12236   def DeclareLocks(self, level):
12237     # TODO: Acquire group lock in shared mode (disk parameters)
12238     if level == locking.LEVEL_NODE:
12239       self._LockInstancesNodes()
12240       if self.op.disk_template and self.op.remote_node:
12241         self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
12242         self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
12243     elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
12244       # Copy node locks
12245       self.needed_locks[locking.LEVEL_NODE_RES] = \
12246         self.needed_locks[locking.LEVEL_NODE][:]
12247
12248   def BuildHooksEnv(self):
12249     """Build hooks env.
12250
12251     This runs on the master, primary and secondaries.
12252
12253     """
12254     args = dict()
12255     if constants.BE_MINMEM in self.be_new:
12256       args["minmem"] = self.be_new[constants.BE_MINMEM]
12257     if constants.BE_MAXMEM in self.be_new:
12258       args["maxmem"] = self.be_new[constants.BE_MAXMEM]
12259     if constants.BE_VCPUS in self.be_new:
12260       args["vcpus"] = self.be_new[constants.BE_VCPUS]
12261     # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
12262     # information at all.
12263
12264     if self._new_nics is not None:
12265       nics = []
12266
12267       for nic in self._new_nics:
12268         nicparams = self.cluster.SimpleFillNIC(nic.nicparams)
12269         mode = nicparams[constants.NIC_MODE]
12270         link = nicparams[constants.NIC_LINK]
12271         nics.append((nic.ip, nic.mac, mode, link))
12272
12273       args["nics"] = nics
12274
12275     env = _BuildInstanceHookEnvByObject(self, self.instance, override=args)
12276     if self.op.disk_template:
12277       env["NEW_DISK_TEMPLATE"] = self.op.disk_template
12278     if self.op.runtime_mem:
12279       env["RUNTIME_MEMORY"] = self.op.runtime_mem
12280
12281     return env
12282
12283   def BuildHooksNodes(self):
12284     """Build hooks nodes.
12285
12286     """
12287     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
12288     return (nl, nl)
12289
12290   def _PrepareNicModification(self, params, private, old_ip, old_params,
12291                               cluster, pnode):
12292     update_params_dict = dict([(key, params[key])
12293                                for key in constants.NICS_PARAMETERS
12294                                if key in params])
12295
12296     if "bridge" in params:
12297       update_params_dict[constants.NIC_LINK] = params["bridge"]
12298
12299     new_params = _GetUpdatedParams(old_params, update_params_dict)
12300     utils.ForceDictType(new_params, constants.NICS_PARAMETER_TYPES)
12301
12302     new_filled_params = cluster.SimpleFillNIC(new_params)
12303     objects.NIC.CheckParameterSyntax(new_filled_params)
12304
12305     new_mode = new_filled_params[constants.NIC_MODE]
12306     if new_mode == constants.NIC_MODE_BRIDGED:
12307       bridge = new_filled_params[constants.NIC_LINK]
12308       msg = self.rpc.call_bridges_exist(pnode, [bridge]).fail_msg
12309       if msg:
12310         msg = "Error checking bridges on node '%s': %s" % (pnode, msg)
12311         if self.op.force:
12312           self.warn.append(msg)
12313         else:
12314           raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
12315
12316     elif new_mode == constants.NIC_MODE_ROUTED:
12317       ip = params.get(constants.INIC_IP, old_ip)
12318       if ip is None:
12319         raise errors.OpPrereqError("Cannot set the NIC IP address to None"
12320                                    " on a routed NIC", errors.ECODE_INVAL)
12321
12322     if constants.INIC_MAC in params:
12323       mac = params[constants.INIC_MAC]
12324       if mac is None:
12325         raise errors.OpPrereqError("Cannot unset the NIC MAC address",
12326                                    errors.ECODE_INVAL)
12327       elif mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
12328         # otherwise generate the MAC address
12329         params[constants.INIC_MAC] = \
12330           self.cfg.GenerateMAC(self.proc.GetECId())
12331       else:
12332         # or validate/reserve the current one
12333         try:
12334           self.cfg.ReserveMAC(mac, self.proc.GetECId())
12335         except errors.ReservationError:
12336           raise errors.OpPrereqError("MAC address '%s' already in use"
12337                                      " in cluster" % mac,
12338                                      errors.ECODE_NOTUNIQUE)
12339
12340     private.params = new_params
12341     private.filled = new_filled_params
12342
12343   def CheckPrereq(self):
12344     """Check prerequisites.
12345
12346     This only checks the instance list against the existing names.
12347
12348     """
12349     # checking the new params on the primary/secondary nodes
12350
12351     instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
12352     cluster = self.cluster = self.cfg.GetClusterInfo()
12353     assert self.instance is not None, \
12354       "Cannot retrieve locked instance %s" % self.op.instance_name
12355     pnode = instance.primary_node
12356     nodelist = list(instance.all_nodes)
12357     pnode_info = self.cfg.GetNodeInfo(pnode)
12358     self.diskparams = self.cfg.GetInstanceDiskParams(instance)
12359
12360     # Prepare disk/NIC modifications
12361     self.diskmod = PrepareContainerMods(self.op.disks, None)
12362     self.nicmod = PrepareContainerMods(self.op.nics, _InstNicModPrivate)
12363
12364     # OS change
12365     if self.op.os_name and not self.op.force:
12366       _CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
12367                       self.op.force_variant)
12368       instance_os = self.op.os_name
12369     else:
12370       instance_os = instance.os
12371
12372     assert not (self.op.disk_template and self.op.disks), \
12373       "Can't modify disk template and apply disk changes at the same time"
12374
12375     if self.op.disk_template:
12376       if instance.disk_template == self.op.disk_template:
12377         raise errors.OpPrereqError("Instance already has disk template %s" %
12378                                    instance.disk_template, errors.ECODE_INVAL)
12379
12380       if (instance.disk_template,
12381           self.op.disk_template) not in self._DISK_CONVERSIONS:
12382         raise errors.OpPrereqError("Unsupported disk template conversion from"
12383                                    " %s to %s" % (instance.disk_template,
12384                                                   self.op.disk_template),
12385                                    errors.ECODE_INVAL)
12386       _CheckInstanceState(self, instance, INSTANCE_DOWN,
12387                           msg="cannot change disk template")
12388       if self.op.disk_template in constants.DTS_INT_MIRROR:
12389         if self.op.remote_node == pnode:
12390           raise errors.OpPrereqError("Given new secondary node %s is the same"
12391                                      " as the primary node of the instance" %
12392                                      self.op.remote_node, errors.ECODE_STATE)
12393         _CheckNodeOnline(self, self.op.remote_node)
12394         _CheckNodeNotDrained(self, self.op.remote_node)
12395         # FIXME: here we assume that the old instance type is DT_PLAIN
12396         assert instance.disk_template == constants.DT_PLAIN
12397         disks = [{constants.IDISK_SIZE: d.size,
12398                   constants.IDISK_VG: d.logical_id[0]}
12399                  for d in instance.disks]
12400         required = _ComputeDiskSizePerVG(self.op.disk_template, disks)
12401         _CheckNodesFreeDiskPerVG(self, [self.op.remote_node], required)
12402
12403         snode_info = self.cfg.GetNodeInfo(self.op.remote_node)
12404         snode_group = self.cfg.GetNodeGroup(snode_info.group)
12405         ipolicy = _CalculateGroupIPolicy(cluster, snode_group)
12406         _CheckTargetNodeIPolicy(self, ipolicy, instance, snode_info,
12407                                 ignore=self.op.ignore_ipolicy)
12408         if pnode_info.group != snode_info.group:
12409           self.LogWarning("The primary and secondary nodes are in two"
12410                           " different node groups; the disk parameters"
12411                           " from the first disk's node group will be"
12412                           " used")
12413
12414     # hvparams processing
12415     if self.op.hvparams:
12416       hv_type = instance.hypervisor
12417       i_hvdict = _GetUpdatedParams(instance.hvparams, self.op.hvparams)
12418       utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
12419       hv_new = cluster.SimpleFillHV(hv_type, instance.os, i_hvdict)
12420
12421       # local check
12422       hypervisor.GetHypervisor(hv_type).CheckParameterSyntax(hv_new)
12423       _CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
12424       self.hv_proposed = self.hv_new = hv_new # the new actual values
12425       self.hv_inst = i_hvdict # the new dict (without defaults)
12426     else:
12427       self.hv_proposed = cluster.SimpleFillHV(instance.hypervisor, instance.os,
12428                                               instance.hvparams)
12429       self.hv_new = self.hv_inst = {}
12430
12431     # beparams processing
12432     if self.op.beparams:
12433       i_bedict = _GetUpdatedParams(instance.beparams, self.op.beparams,
12434                                    use_none=True)
12435       objects.UpgradeBeParams(i_bedict)
12436       utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
12437       be_new = cluster.SimpleFillBE(i_bedict)
12438       self.be_proposed = self.be_new = be_new # the new actual values
12439       self.be_inst = i_bedict # the new dict (without defaults)
12440     else:
12441       self.be_new = self.be_inst = {}
12442       self.be_proposed = cluster.SimpleFillBE(instance.beparams)
12443     be_old = cluster.FillBE(instance)
12444
12445     # CPU param validation -- checking every time a parameter is
12446     # changed to cover all cases where either CPU mask or vcpus have
12447     # changed
12448     if (constants.BE_VCPUS in self.be_proposed and
12449         constants.HV_CPU_MASK in self.hv_proposed):
12450       cpu_list = \
12451         utils.ParseMultiCpuMask(self.hv_proposed[constants.HV_CPU_MASK])
12452       # Verify mask is consistent with number of vCPUs. Can skip this
12453       # test if only 1 entry in the CPU mask, which means same mask
12454       # is applied to all vCPUs.
12455       if (len(cpu_list) > 1 and
12456           len(cpu_list) != self.be_proposed[constants.BE_VCPUS]):
12457         raise errors.OpPrereqError("Number of vCPUs [%d] does not match the"
12458                                    " CPU mask [%s]" %
12459                                    (self.be_proposed[constants.BE_VCPUS],
12460                                     self.hv_proposed[constants.HV_CPU_MASK]),
12461                                    errors.ECODE_INVAL)
12462
12463       # Only perform this test if a new CPU mask is given
12464       if constants.HV_CPU_MASK in self.hv_new:
12465         # Calculate the largest CPU number requested
12466         max_requested_cpu = max(map(max, cpu_list))
12467         # Check that all of the instance's nodes have enough physical CPUs to
12468         # satisfy the requested CPU mask
12469         _CheckNodesPhysicalCPUs(self, instance.all_nodes,
12470                                 max_requested_cpu + 1, instance.hypervisor)
12471
12472     # osparams processing
12473     if self.op.osparams:
12474       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
12475       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
12476       self.os_inst = i_osdict # the new dict (without defaults)
12477     else:
12478       self.os_inst = {}
12479
12480     self.warn = []
12481
12482     #TODO(dynmem): do the appropriate check involving MINMEM
12483     if (constants.BE_MAXMEM in self.op.beparams and not self.op.force and
12484         be_new[constants.BE_MAXMEM] > be_old[constants.BE_MAXMEM]):
12485       mem_check_list = [pnode]
12486       if be_new[constants.BE_AUTO_BALANCE]:
12487         # either we changed auto_balance to yes or it was from before
12488         mem_check_list.extend(instance.secondary_nodes)
12489       instance_info = self.rpc.call_instance_info(pnode, instance.name,
12490                                                   instance.hypervisor)
12491       nodeinfo = self.rpc.call_node_info(mem_check_list, None,
12492                                          [instance.hypervisor])
12493       pninfo = nodeinfo[pnode]
12494       msg = pninfo.fail_msg
12495       if msg:
12496         # Assume the primary node is unreachable and go ahead
12497         self.warn.append("Can't get info from primary node %s: %s" %
12498                          (pnode, msg))
12499       else:
12500         (_, _, (pnhvinfo, )) = pninfo.payload
12501         if not isinstance(pnhvinfo.get("memory_free", None), int):
12502           self.warn.append("Node data from primary node %s doesn't contain"
12503                            " free memory information" % pnode)
12504         elif instance_info.fail_msg:
12505           self.warn.append("Can't get instance runtime information: %s" %
12506                           instance_info.fail_msg)
12507         else:
12508           if instance_info.payload:
12509             current_mem = int(instance_info.payload["memory"])
12510           else:
12511             # Assume instance not running
12512             # (there is a slight race condition here, but it's not very
12513             # probable, and we have no other way to check)
12514             # TODO: Describe race condition
12515             current_mem = 0
12516           #TODO(dynmem): do the appropriate check involving MINMEM
12517           miss_mem = (be_new[constants.BE_MAXMEM] - current_mem -
12518                       pnhvinfo["memory_free"])
12519           if miss_mem > 0:
12520             raise errors.OpPrereqError("This change will prevent the instance"
12521                                        " from starting, due to %d MB of memory"
12522                                        " missing on its primary node" %
12523                                        miss_mem,
12524                                        errors.ECODE_NORES)
12525
12526       if be_new[constants.BE_AUTO_BALANCE]:
12527         for node, nres in nodeinfo.items():
12528           if node not in instance.secondary_nodes:
12529             continue
12530           nres.Raise("Can't get info from secondary node %s" % node,
12531                      prereq=True, ecode=errors.ECODE_STATE)
12532           (_, _, (nhvinfo, )) = nres.payload
12533           if not isinstance(nhvinfo.get("memory_free", None), int):
12534             raise errors.OpPrereqError("Secondary node %s didn't return free"
12535                                        " memory information" % node,
12536                                        errors.ECODE_STATE)
12537           #TODO(dynmem): do the appropriate check involving MINMEM
12538           elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
12539             raise errors.OpPrereqError("This change will prevent the instance"
12540                                        " from failover to its secondary node"
12541                                        " %s, due to not enough memory" % node,
12542                                        errors.ECODE_STATE)
12543
12544     if self.op.runtime_mem:
12545       remote_info = self.rpc.call_instance_info(instance.primary_node,
12546                                                 instance.name,
12547                                                 instance.hypervisor)
12548       remote_info.Raise("Error checking node %s" % instance.primary_node)
12549       if not remote_info.payload: # not running already
12550         raise errors.OpPrereqError("Instance %s is not running" % instance.name,
12551                                    errors.ECODE_STATE)
12552
12553       current_memory = remote_info.payload["memory"]
12554       if (not self.op.force and
12555            (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or
12556             self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])):
12557         raise errors.OpPrereqError("Instance %s must have memory between %d"
12558                                    " and %d MB of memory unless --force is"
12559                                    " given" % (instance.name,
12560                                     self.be_proposed[constants.BE_MINMEM],
12561                                     self.be_proposed[constants.BE_MAXMEM]),
12562                                    errors.ECODE_INVAL)
12563
12564       if self.op.runtime_mem > current_memory:
12565         _CheckNodeFreeMemory(self, instance.primary_node,
12566                              "ballooning memory for instance %s" %
12567                              instance.name,
12568                              self.op.memory - current_memory,
12569                              instance.hypervisor)
12570
12571     if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
12572       raise errors.OpPrereqError("Disk operations not supported for"
12573                                  " diskless instances",
12574                                  errors.ECODE_INVAL)
12575
12576     def _PrepareNicCreate(_, params, private):
12577       self._PrepareNicModification(params, private, None, {}, cluster, pnode)
12578       return (None, None)
12579
12580     def _PrepareNicMod(_, nic, params, private):
12581       self._PrepareNicModification(params, private, nic.ip,
12582                                    nic.nicparams, cluster, pnode)
12583       return None
12584
12585     # Verify NIC changes (operating on copy)
12586     nics = instance.nics[:]
12587     ApplyContainerMods("NIC", nics, None, self.nicmod,
12588                        _PrepareNicCreate, _PrepareNicMod, None)
12589     if len(nics) > constants.MAX_NICS:
12590       raise errors.OpPrereqError("Instance has too many network interfaces"
12591                                  " (%d), cannot add more" % constants.MAX_NICS,
12592                                  errors.ECODE_STATE)
12593
12594     # Verify disk changes (operating on a copy)
12595     disks = instance.disks[:]
12596     ApplyContainerMods("disk", disks, None, self.diskmod, None, None, None)
12597     if len(disks) > constants.MAX_DISKS:
12598       raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
12599                                  " more" % constants.MAX_DISKS,
12600                                  errors.ECODE_STATE)
12601
12602     if self.op.offline is not None:
12603       if self.op.offline:
12604         msg = "can't change to offline"
12605       else:
12606         msg = "can't change to online"
12607       _CheckInstanceState(self, instance, CAN_CHANGE_INSTANCE_OFFLINE, msg=msg)
12608
12609     # Pre-compute NIC changes (necessary to use result in hooks)
12610     self._nic_chgdesc = []
12611     if self.nicmod:
12612       # Operate on copies as this is still in prereq
12613       nics = [nic.Copy() for nic in instance.nics]
12614       ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
12615                          self._CreateNewNic, self._ApplyNicMods, None)
12616       self._new_nics = nics
12617     else:
12618       self._new_nics = None
12619
12620   def _ConvertPlainToDrbd(self, feedback_fn):
12621     """Converts an instance from plain to drbd.
12622
12623     """
12624     feedback_fn("Converting template to drbd")
12625     instance = self.instance
12626     pnode = instance.primary_node
12627     snode = self.op.remote_node
12628
12629     assert instance.disk_template == constants.DT_PLAIN
12630
12631     # create a fake disk info for _GenerateDiskTemplate
12632     disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
12633                   constants.IDISK_VG: d.logical_id[0]}
12634                  for d in instance.disks]
12635     new_disks = _GenerateDiskTemplate(self, self.op.disk_template,
12636                                       instance.name, pnode, [snode],
12637                                       disk_info, None, None, 0, feedback_fn,
12638                                       self.diskparams)
12639     anno_disks = rpc.AnnotateDiskParams(constants.DT_DRBD8, new_disks,
12640                                         self.diskparams)
12641     info = _GetInstanceInfoText(instance)
12642     feedback_fn("Creating additional volumes...")
12643     # first, create the missing data and meta devices
12644     for disk in anno_disks:
12645       # unfortunately this is... not too nice
12646       _CreateSingleBlockDev(self, pnode, instance, disk.children[1],
12647                             info, True)
12648       for child in disk.children:
12649         _CreateSingleBlockDev(self, snode, instance, child, info, True)
12650     # at this stage, all new LVs have been created, we can rename the
12651     # old ones
12652     feedback_fn("Renaming original volumes...")
12653     rename_list = [(o, n.children[0].logical_id)
12654                    for (o, n) in zip(instance.disks, new_disks)]
12655     result = self.rpc.call_blockdev_rename(pnode, rename_list)
12656     result.Raise("Failed to rename original LVs")
12657
12658     feedback_fn("Initializing DRBD devices...")
12659     # all child devices are in place, we can now create the DRBD devices
12660     for disk in anno_disks:
12661       for node in [pnode, snode]:
12662         f_create = node == pnode
12663         _CreateSingleBlockDev(self, node, instance, disk, info, f_create)
12664
12665     # at this point, the instance has been modified
12666     instance.disk_template = constants.DT_DRBD8
12667     instance.disks = new_disks
12668     self.cfg.Update(instance, feedback_fn)
12669
12670     # Release node locks while waiting for sync
12671     _ReleaseLocks(self, locking.LEVEL_NODE)
12672
12673     # disks are created, waiting for sync
12674     disk_abort = not _WaitForSync(self, instance,
12675                                   oneshot=not self.op.wait_for_sync)
12676     if disk_abort:
12677       raise errors.OpExecError("There are some degraded disks for"
12678                                " this instance, please cleanup manually")
12679
12680     # Node resource locks will be released by caller
12681
12682   def _ConvertDrbdToPlain(self, feedback_fn):
12683     """Converts an instance from drbd to plain.
12684
12685     """
12686     instance = self.instance
12687
12688     assert len(instance.secondary_nodes) == 1
12689     assert instance.disk_template == constants.DT_DRBD8
12690
12691     pnode = instance.primary_node
12692     snode = instance.secondary_nodes[0]
12693     feedback_fn("Converting template to plain")
12694
12695     old_disks = _AnnotateDiskParams(instance, instance.disks, self.cfg)
12696     new_disks = [d.children[0] for d in instance.disks]
12697
12698     # copy over size and mode
12699     for parent, child in zip(old_disks, new_disks):
12700       child.size = parent.size
12701       child.mode = parent.mode
12702
12703     # this is a DRBD disk, return its port to the pool
12704     # NOTE: this must be done right before the call to cfg.Update!
12705     for disk in old_disks:
12706       tcp_port = disk.logical_id[2]
12707       self.cfg.AddTcpUdpPort(tcp_port)
12708
12709     # update instance structure
12710     instance.disks = new_disks
12711     instance.disk_template = constants.DT_PLAIN
12712     self.cfg.Update(instance, feedback_fn)
12713
12714     # Release locks in case removing disks takes a while
12715     _ReleaseLocks(self, locking.LEVEL_NODE)
12716
12717     feedback_fn("Removing volumes on the secondary node...")
12718     for disk in old_disks:
12719       self.cfg.SetDiskID(disk, snode)
12720       msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
12721       if msg:
12722         self.LogWarning("Could not remove block device %s on node %s,"
12723                         " continuing anyway: %s", disk.iv_name, snode, msg)
12724
12725     feedback_fn("Removing unneeded volumes on the primary node...")
12726     for idx, disk in enumerate(old_disks):
12727       meta = disk.children[1]
12728       self.cfg.SetDiskID(meta, pnode)
12729       msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
12730       if msg:
12731         self.LogWarning("Could not remove metadata for disk %d on node %s,"
12732                         " continuing anyway: %s", idx, pnode, msg)
12733
12734   def _CreateNewDisk(self, idx, params, _):
12735     """Creates a new disk.
12736
12737     """
12738     instance = self.instance
12739
12740     # add a new disk
12741     if instance.disk_template in constants.DTS_FILEBASED:
12742       (file_driver, file_path) = instance.disks[0].logical_id
12743       file_path = os.path.dirname(file_path)
12744     else:
12745       file_driver = file_path = None
12746
12747     disk = \
12748       _GenerateDiskTemplate(self, instance.disk_template, instance.name,
12749                             instance.primary_node, instance.secondary_nodes,
12750                             [params], file_path, file_driver, idx,
12751                             self.Log, self.diskparams)[0]
12752
12753     info = _GetInstanceInfoText(instance)
12754
12755     logging.info("Creating volume %s for instance %s",
12756                  disk.iv_name, instance.name)
12757     # Note: this needs to be kept in sync with _CreateDisks
12758     #HARDCODE
12759     for node in instance.all_nodes:
12760       f_create = (node == instance.primary_node)
12761       try:
12762         _CreateBlockDev(self, node, instance, disk, f_create, info, f_create)
12763       except errors.OpExecError, err:
12764         self.LogWarning("Failed to create volume %s (%s) on node '%s': %s",
12765                         disk.iv_name, disk, node, err)
12766
12767     return (disk, [
12768       ("disk/%d" % idx, "add:size=%s,mode=%s" % (disk.size, disk.mode)),
12769       ])
12770
12771   @staticmethod
12772   def _ModifyDisk(idx, disk, params, _):
12773     """Modifies a disk.
12774
12775     """
12776     disk.mode = params[constants.IDISK_MODE]
12777
12778     return [
12779       ("disk.mode/%d" % idx, disk.mode),
12780       ]
12781
12782   def _RemoveDisk(self, idx, root, _):
12783     """Removes a disk.
12784
12785     """
12786     (anno_disk,) = _AnnotateDiskParams(self.instance, [root], self.cfg)
12787     for node, disk in anno_disk.ComputeNodeTree(self.instance.primary_node):
12788       self.cfg.SetDiskID(disk, node)
12789       msg = self.rpc.call_blockdev_remove(node, disk).fail_msg
12790       if msg:
12791         self.LogWarning("Could not remove disk/%d on node '%s': %s,"
12792                         " continuing anyway", idx, node, msg)
12793
12794     # if this is a DRBD disk, return its port to the pool
12795     if root.dev_type in constants.LDS_DRBD:
12796       self.cfg.AddTcpUdpPort(root.logical_id[2])
12797
12798   @staticmethod
12799   def _CreateNewNic(idx, params, private):
12800     """Creates data structure for a new network interface.
12801
12802     """
12803     mac = params[constants.INIC_MAC]
12804     ip = params.get(constants.INIC_IP, None)
12805     nicparams = private.params
12806
12807     return (objects.NIC(mac=mac, ip=ip, nicparams=nicparams), [
12808       ("nic.%d" % idx,
12809        "add:mac=%s,ip=%s,mode=%s,link=%s" %
12810        (mac, ip, private.filled[constants.NIC_MODE],
12811        private.filled[constants.NIC_LINK])),
12812       ])
12813
12814   @staticmethod
12815   def _ApplyNicMods(idx, nic, params, private):
12816     """Modifies a network interface.
12817
12818     """
12819     changes = []
12820
12821     for key in [constants.INIC_MAC, constants.INIC_IP]:
12822       if key in params:
12823         changes.append(("nic.%s/%d" % (key, idx), params[key]))
12824         setattr(nic, key, params[key])
12825
12826     if private.params:
12827       nic.nicparams = private.params
12828
12829       for (key, val) in params.items():
12830         changes.append(("nic.%s/%d" % (key, idx), val))
12831
12832     return changes
12833
12834   def Exec(self, feedback_fn):
12835     """Modifies an instance.
12836
12837     All parameters take effect only at the next restart of the instance.
12838
12839     """
12840     # Process here the warnings from CheckPrereq, as we don't have a
12841     # feedback_fn there.
12842     # TODO: Replace with self.LogWarning
12843     for warn in self.warn:
12844       feedback_fn("WARNING: %s" % warn)
12845
12846     assert ((self.op.disk_template is None) ^
12847             bool(self.owned_locks(locking.LEVEL_NODE_RES))), \
12848       "Not owning any node resource locks"
12849
12850     result = []
12851     instance = self.instance
12852
12853     # runtime memory
12854     if self.op.runtime_mem:
12855       rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
12856                                                      instance,
12857                                                      self.op.runtime_mem)
12858       rpcres.Raise("Cannot modify instance runtime memory")
12859       result.append(("runtime_memory", self.op.runtime_mem))
12860
12861     # Apply disk changes
12862     ApplyContainerMods("disk", instance.disks, result, self.diskmod,
12863                        self._CreateNewDisk, self._ModifyDisk, self._RemoveDisk)
12864     _UpdateIvNames(0, instance.disks)
12865
12866     if self.op.disk_template:
12867       if __debug__:
12868         check_nodes = set(instance.all_nodes)
12869         if self.op.remote_node:
12870           check_nodes.add(self.op.remote_node)
12871         for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
12872           owned = self.owned_locks(level)
12873           assert not (check_nodes - owned), \
12874             ("Not owning the correct locks, owning %r, expected at least %r" %
12875              (owned, check_nodes))
12876
12877       r_shut = _ShutdownInstanceDisks(self, instance)
12878       if not r_shut:
12879         raise errors.OpExecError("Cannot shutdown instance disks, unable to"
12880                                  " proceed with disk template conversion")
12881       mode = (instance.disk_template, self.op.disk_template)
12882       try:
12883         self._DISK_CONVERSIONS[mode](self, feedback_fn)
12884       except:
12885         self.cfg.ReleaseDRBDMinors(instance.name)
12886         raise
12887       result.append(("disk_template", self.op.disk_template))
12888
12889       assert instance.disk_template == self.op.disk_template, \
12890         ("Expected disk template '%s', found '%s'" %
12891          (self.op.disk_template, instance.disk_template))
12892
12893     # Release node and resource locks if there are any (they might already have
12894     # been released during disk conversion)
12895     _ReleaseLocks(self, locking.LEVEL_NODE)
12896     _ReleaseLocks(self, locking.LEVEL_NODE_RES)
12897
12898     # Apply NIC changes
12899     if self._new_nics is not None:
12900       instance.nics = self._new_nics
12901       result.extend(self._nic_chgdesc)
12902
12903     # hvparams changes
12904     if self.op.hvparams:
12905       instance.hvparams = self.hv_inst
12906       for key, val in self.op.hvparams.iteritems():
12907         result.append(("hv/%s" % key, val))
12908
12909     # beparams changes
12910     if self.op.beparams:
12911       instance.beparams = self.be_inst
12912       for key, val in self.op.beparams.iteritems():
12913         result.append(("be/%s" % key, val))
12914
12915     # OS change
12916     if self.op.os_name:
12917       instance.os = self.op.os_name
12918
12919     # osparams changes
12920     if self.op.osparams:
12921       instance.osparams = self.os_inst
12922       for key, val in self.op.osparams.iteritems():
12923         result.append(("os/%s" % key, val))
12924
12925     if self.op.offline is None:
12926       # Ignore
12927       pass
12928     elif self.op.offline:
12929       # Mark instance as offline
12930       self.cfg.MarkInstanceOffline(instance.name)
12931       result.append(("admin_state", constants.ADMINST_OFFLINE))
12932     else:
12933       # Mark instance as online, but stopped
12934       self.cfg.MarkInstanceDown(instance.name)
12935       result.append(("admin_state", constants.ADMINST_DOWN))
12936
12937     self.cfg.Update(instance, feedback_fn)
12938
12939     assert not (self.owned_locks(locking.LEVEL_NODE_RES) or
12940                 self.owned_locks(locking.LEVEL_NODE)), \
12941       "All node locks should have been released by now"
12942
12943     return result
12944
12945   _DISK_CONVERSIONS = {
12946     (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
12947     (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
12948     }
12949
12950
12951 class LUInstanceChangeGroup(LogicalUnit):
12952   HPATH = "instance-change-group"
12953   HTYPE = constants.HTYPE_INSTANCE
12954   REQ_BGL = False
12955
12956   def ExpandNames(self):
12957     self.share_locks = _ShareAll()
12958     self.needed_locks = {
12959       locking.LEVEL_NODEGROUP: [],
12960       locking.LEVEL_NODE: [],
12961       }
12962
12963     self._ExpandAndLockInstance()
12964
12965     if self.op.target_groups:
12966       self.req_target_uuids = map(self.cfg.LookupNodeGroup,
12967                                   self.op.target_groups)
12968     else:
12969       self.req_target_uuids = None
12970
12971     self.op.iallocator = _GetDefaultIAllocator(self.cfg, self.op.iallocator)
12972
12973   def DeclareLocks(self, level):
12974     if level == locking.LEVEL_NODEGROUP:
12975       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
12976
12977       if self.req_target_uuids:
12978         lock_groups = set(self.req_target_uuids)
12979
12980         # Lock all groups used by instance optimistically; this requires going
12981         # via the node before it's locked, requiring verification later on
12982         instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_name)
12983         lock_groups.update(instance_groups)
12984       else:
12985         # No target groups, need to lock all of them
12986         lock_groups = locking.ALL_SET
12987
12988       self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
12989
12990     elif level == locking.LEVEL_NODE:
12991       if self.req_target_uuids:
12992         # Lock all nodes used by instances
12993         self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
12994         self._LockInstancesNodes()
12995
12996         # Lock all nodes in all potential target groups
12997         lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) -
12998                        self.cfg.GetInstanceNodeGroups(self.op.instance_name))
12999         member_nodes = [node_name
13000                         for group in lock_groups
13001                         for node_name in self.cfg.GetNodeGroup(group).members]
13002         self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
13003       else:
13004         # Lock all nodes as all groups are potential targets
13005         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13006
13007   def CheckPrereq(self):
13008     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
13009     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
13010     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
13011
13012     assert (self.req_target_uuids is None or
13013             owned_groups.issuperset(self.req_target_uuids))
13014     assert owned_instances == set([self.op.instance_name])
13015
13016     # Get instance information
13017     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
13018
13019     # Check if node groups for locked instance are still correct
13020     assert owned_nodes.issuperset(self.instance.all_nodes), \
13021       ("Instance %s's nodes changed while we kept the lock" %
13022        self.op.instance_name)
13023
13024     inst_groups = _CheckInstanceNodeGroups(self.cfg, self.op.instance_name,
13025                                            owned_groups)
13026
13027     if self.req_target_uuids:
13028       # User requested specific target groups
13029       self.target_uuids = frozenset(self.req_target_uuids)
13030     else:
13031       # All groups except those used by the instance are potential targets
13032       self.target_uuids = owned_groups - inst_groups
13033
13034     conflicting_groups = self.target_uuids & inst_groups
13035     if conflicting_groups:
13036       raise errors.OpPrereqError("Can't use group(s) '%s' as targets, they are"
13037                                  " used by the instance '%s'" %
13038                                  (utils.CommaJoin(conflicting_groups),
13039                                   self.op.instance_name),
13040                                  errors.ECODE_INVAL)
13041
13042     if not self.target_uuids:
13043       raise errors.OpPrereqError("There are no possible target groups",
13044                                  errors.ECODE_INVAL)
13045
13046   def BuildHooksEnv(self):
13047     """Build hooks env.
13048
13049     """
13050     assert self.target_uuids
13051
13052     env = {
13053       "TARGET_GROUPS": " ".join(self.target_uuids),
13054       }
13055
13056     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
13057
13058     return env
13059
13060   def BuildHooksNodes(self):
13061     """Build hooks nodes.
13062
13063     """
13064     mn = self.cfg.GetMasterNode()
13065     return ([mn], [mn])
13066
13067   def Exec(self, feedback_fn):
13068     instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
13069
13070     assert instances == [self.op.instance_name], "Instance not locked"
13071
13072     ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_CHG_GROUP,
13073                      instances=instances, target_groups=list(self.target_uuids))
13074
13075     ial.Run(self.op.iallocator)
13076
13077     if not ial.success:
13078       raise errors.OpPrereqError("Can't compute solution for changing group of"
13079                                  " instance '%s' using iallocator '%s': %s" %
13080                                  (self.op.instance_name, self.op.iallocator,
13081                                   ial.info),
13082                                  errors.ECODE_NORES)
13083
13084     jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
13085
13086     self.LogInfo("Iallocator returned %s job(s) for changing group of"
13087                  " instance '%s'", len(jobs), self.op.instance_name)
13088
13089     return ResultWithJobs(jobs)
13090
13091
13092 class LUBackupQuery(NoHooksLU):
13093   """Query the exports list
13094
13095   """
13096   REQ_BGL = False
13097
13098   def CheckArguments(self):
13099     self.expq = _ExportQuery(qlang.MakeSimpleFilter("node", self.op.nodes),
13100                              ["node", "export"], self.op.use_locking)
13101
13102   def ExpandNames(self):
13103     self.expq.ExpandNames(self)
13104
13105   def DeclareLocks(self, level):
13106     self.expq.DeclareLocks(self, level)
13107
13108   def Exec(self, feedback_fn):
13109     result = {}
13110
13111     for (node, expname) in self.expq.OldStyleQuery(self):
13112       if expname is None:
13113         result[node] = False
13114       else:
13115         result.setdefault(node, []).append(expname)
13116
13117     return result
13118
13119
13120 class _ExportQuery(_QueryBase):
13121   FIELDS = query.EXPORT_FIELDS
13122
13123   #: The node name is not a unique key for this query
13124   SORT_FIELD = "node"
13125
13126   def ExpandNames(self, lu):
13127     lu.needed_locks = {}
13128
13129     # The following variables interact with _QueryBase._GetNames
13130     if self.names:
13131       self.wanted = _GetWantedNodes(lu, self.names)
13132     else:
13133       self.wanted = locking.ALL_SET
13134
13135     self.do_locking = self.use_locking
13136
13137     if self.do_locking:
13138       lu.share_locks = _ShareAll()
13139       lu.needed_locks = {
13140         locking.LEVEL_NODE: self.wanted,
13141         }
13142
13143   def DeclareLocks(self, lu, level):
13144     pass
13145
13146   def _GetQueryData(self, lu):
13147     """Computes the list of nodes and their attributes.
13148
13149     """
13150     # Locking is not used
13151     # TODO
13152     assert not (compat.any(lu.glm.is_owned(level)
13153                            for level in locking.LEVELS
13154                            if level != locking.LEVEL_CLUSTER) or
13155                 self.do_locking or self.use_locking)
13156
13157     nodes = self._GetNames(lu, lu.cfg.GetNodeList(), locking.LEVEL_NODE)
13158
13159     result = []
13160
13161     for (node, nres) in lu.rpc.call_export_list(nodes).items():
13162       if nres.fail_msg:
13163         result.append((node, None))
13164       else:
13165         result.extend((node, expname) for expname in nres.payload)
13166
13167     return result
13168
13169
13170 class LUBackupPrepare(NoHooksLU):
13171   """Prepares an instance for an export and returns useful information.
13172
13173   """
13174   REQ_BGL = False
13175
13176   def ExpandNames(self):
13177     self._ExpandAndLockInstance()
13178
13179   def CheckPrereq(self):
13180     """Check prerequisites.
13181
13182     """
13183     instance_name = self.op.instance_name
13184
13185     self.instance = self.cfg.GetInstanceInfo(instance_name)
13186     assert self.instance is not None, \
13187           "Cannot retrieve locked instance %s" % self.op.instance_name
13188     _CheckNodeOnline(self, self.instance.primary_node)
13189
13190     self._cds = _GetClusterDomainSecret()
13191
13192   def Exec(self, feedback_fn):
13193     """Prepares an instance for an export.
13194
13195     """
13196     instance = self.instance
13197
13198     if self.op.mode == constants.EXPORT_MODE_REMOTE:
13199       salt = utils.GenerateSecret(8)
13200
13201       feedback_fn("Generating X509 certificate on %s" % instance.primary_node)
13202       result = self.rpc.call_x509_cert_create(instance.primary_node,
13203                                               constants.RIE_CERT_VALIDITY)
13204       result.Raise("Can't create X509 key and certificate on %s" % result.node)
13205
13206       (name, cert_pem) = result.payload
13207
13208       cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
13209                                              cert_pem)
13210
13211       return {
13212         "handshake": masterd.instance.ComputeRemoteExportHandshake(self._cds),
13213         "x509_key_name": (name, utils.Sha1Hmac(self._cds, name, salt=salt),
13214                           salt),
13215         "x509_ca": utils.SignX509Certificate(cert, self._cds, salt),
13216         }
13217
13218     return None
13219
13220
13221 class LUBackupExport(LogicalUnit):
13222   """Export an instance to an image in the cluster.
13223
13224   """
13225   HPATH = "instance-export"
13226   HTYPE = constants.HTYPE_INSTANCE
13227   REQ_BGL = False
13228
13229   def CheckArguments(self):
13230     """Check the arguments.
13231
13232     """
13233     self.x509_key_name = self.op.x509_key_name
13234     self.dest_x509_ca_pem = self.op.destination_x509_ca
13235
13236     if self.op.mode == constants.EXPORT_MODE_REMOTE:
13237       if not self.x509_key_name:
13238         raise errors.OpPrereqError("Missing X509 key name for encryption",
13239                                    errors.ECODE_INVAL)
13240
13241       if not self.dest_x509_ca_pem:
13242         raise errors.OpPrereqError("Missing destination X509 CA",
13243                                    errors.ECODE_INVAL)
13244
13245   def ExpandNames(self):
13246     self._ExpandAndLockInstance()
13247
13248     # Lock all nodes for local exports
13249     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13250       # FIXME: lock only instance primary and destination node
13251       #
13252       # Sad but true, for now we have do lock all nodes, as we don't know where
13253       # the previous export might be, and in this LU we search for it and
13254       # remove it from its current node. In the future we could fix this by:
13255       #  - making a tasklet to search (share-lock all), then create the
13256       #    new one, then one to remove, after
13257       #  - removing the removal operation altogether
13258       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13259
13260   def DeclareLocks(self, level):
13261     """Last minute lock declaration."""
13262     # All nodes are locked anyway, so nothing to do here.
13263
13264   def BuildHooksEnv(self):
13265     """Build hooks env.
13266
13267     This will run on the master, primary node and target node.
13268
13269     """
13270     env = {
13271       "EXPORT_MODE": self.op.mode,
13272       "EXPORT_NODE": self.op.target_node,
13273       "EXPORT_DO_SHUTDOWN": self.op.shutdown,
13274       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
13275       # TODO: Generic function for boolean env variables
13276       "REMOVE_INSTANCE": str(bool(self.op.remove_instance)),
13277       }
13278
13279     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
13280
13281     return env
13282
13283   def BuildHooksNodes(self):
13284     """Build hooks nodes.
13285
13286     """
13287     nl = [self.cfg.GetMasterNode(), self.instance.primary_node]
13288
13289     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13290       nl.append(self.op.target_node)
13291
13292     return (nl, nl)
13293
13294   def CheckPrereq(self):
13295     """Check prerequisites.
13296
13297     This checks that the instance and node names are valid.
13298
13299     """
13300     instance_name = self.op.instance_name
13301
13302     self.instance = self.cfg.GetInstanceInfo(instance_name)
13303     assert self.instance is not None, \
13304           "Cannot retrieve locked instance %s" % self.op.instance_name
13305     _CheckNodeOnline(self, self.instance.primary_node)
13306
13307     if (self.op.remove_instance and
13308         self.instance.admin_state == constants.ADMINST_UP and
13309         not self.op.shutdown):
13310       raise errors.OpPrereqError("Can not remove instance without shutting it"
13311                                  " down before")
13312
13313     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13314       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
13315       self.dst_node = self.cfg.GetNodeInfo(self.op.target_node)
13316       assert self.dst_node is not None
13317
13318       _CheckNodeOnline(self, self.dst_node.name)
13319       _CheckNodeNotDrained(self, self.dst_node.name)
13320
13321       self._cds = None
13322       self.dest_disk_info = None
13323       self.dest_x509_ca = None
13324
13325     elif self.op.mode == constants.EXPORT_MODE_REMOTE:
13326       self.dst_node = None
13327
13328       if len(self.op.target_node) != len(self.instance.disks):
13329         raise errors.OpPrereqError(("Received destination information for %s"
13330                                     " disks, but instance %s has %s disks") %
13331                                    (len(self.op.target_node), instance_name,
13332                                     len(self.instance.disks)),
13333                                    errors.ECODE_INVAL)
13334
13335       cds = _GetClusterDomainSecret()
13336
13337       # Check X509 key name
13338       try:
13339         (key_name, hmac_digest, hmac_salt) = self.x509_key_name
13340       except (TypeError, ValueError), err:
13341         raise errors.OpPrereqError("Invalid data for X509 key name: %s" % err)
13342
13343       if not utils.VerifySha1Hmac(cds, key_name, hmac_digest, salt=hmac_salt):
13344         raise errors.OpPrereqError("HMAC for X509 key name is wrong",
13345                                    errors.ECODE_INVAL)
13346
13347       # Load and verify CA
13348       try:
13349         (cert, _) = utils.LoadSignedX509Certificate(self.dest_x509_ca_pem, cds)
13350       except OpenSSL.crypto.Error, err:
13351         raise errors.OpPrereqError("Unable to load destination X509 CA (%s)" %
13352                                    (err, ), errors.ECODE_INVAL)
13353
13354       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
13355       if errcode is not None:
13356         raise errors.OpPrereqError("Invalid destination X509 CA (%s)" %
13357                                    (msg, ), errors.ECODE_INVAL)
13358
13359       self.dest_x509_ca = cert
13360
13361       # Verify target information
13362       disk_info = []
13363       for idx, disk_data in enumerate(self.op.target_node):
13364         try:
13365           (host, port, magic) = \
13366             masterd.instance.CheckRemoteExportDiskInfo(cds, idx, disk_data)
13367         except errors.GenericError, err:
13368           raise errors.OpPrereqError("Target info for disk %s: %s" %
13369                                      (idx, err), errors.ECODE_INVAL)
13370
13371         disk_info.append((host, port, magic))
13372
13373       assert len(disk_info) == len(self.op.target_node)
13374       self.dest_disk_info = disk_info
13375
13376     else:
13377       raise errors.ProgrammerError("Unhandled export mode %r" %
13378                                    self.op.mode)
13379
13380     # instance disk type verification
13381     # TODO: Implement export support for file-based disks
13382     for disk in self.instance.disks:
13383       if disk.dev_type == constants.LD_FILE:
13384         raise errors.OpPrereqError("Export not supported for instances with"
13385                                    " file-based disks", errors.ECODE_INVAL)
13386
13387   def _CleanupExports(self, feedback_fn):
13388     """Removes exports of current instance from all other nodes.
13389
13390     If an instance in a cluster with nodes A..D was exported to node C, its
13391     exports will be removed from the nodes A, B and D.
13392
13393     """
13394     assert self.op.mode != constants.EXPORT_MODE_REMOTE
13395
13396     nodelist = self.cfg.GetNodeList()
13397     nodelist.remove(self.dst_node.name)
13398
13399     # on one-node clusters nodelist will be empty after the removal
13400     # if we proceed the backup would be removed because OpBackupQuery
13401     # substitutes an empty list with the full cluster node list.
13402     iname = self.instance.name
13403     if nodelist:
13404       feedback_fn("Removing old exports for instance %s" % iname)
13405       exportlist = self.rpc.call_export_list(nodelist)
13406       for node in exportlist:
13407         if exportlist[node].fail_msg:
13408           continue
13409         if iname in exportlist[node].payload:
13410           msg = self.rpc.call_export_remove(node, iname).fail_msg
13411           if msg:
13412             self.LogWarning("Could not remove older export for instance %s"
13413                             " on node %s: %s", iname, node, msg)
13414
13415   def Exec(self, feedback_fn):
13416     """Export an instance to an image in the cluster.
13417
13418     """
13419     assert self.op.mode in constants.EXPORT_MODES
13420
13421     instance = self.instance
13422     src_node = instance.primary_node
13423
13424     if self.op.shutdown:
13425       # shutdown the instance, but not the disks
13426       feedback_fn("Shutting down instance %s" % instance.name)
13427       result = self.rpc.call_instance_shutdown(src_node, instance,
13428                                                self.op.shutdown_timeout)
13429       # TODO: Maybe ignore failures if ignore_remove_failures is set
13430       result.Raise("Could not shutdown instance %s on"
13431                    " node %s" % (instance.name, src_node))
13432
13433     # set the disks ID correctly since call_instance_start needs the
13434     # correct drbd minor to create the symlinks
13435     for disk in instance.disks:
13436       self.cfg.SetDiskID(disk, src_node)
13437
13438     activate_disks = (instance.admin_state != constants.ADMINST_UP)
13439
13440     if activate_disks:
13441       # Activate the instance disks if we'exporting a stopped instance
13442       feedback_fn("Activating disks for %s" % instance.name)
13443       _StartInstanceDisks(self, instance, None)
13444
13445     try:
13446       helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
13447                                                      instance)
13448
13449       helper.CreateSnapshots()
13450       try:
13451         if (self.op.shutdown and
13452             instance.admin_state == constants.ADMINST_UP and
13453             not self.op.remove_instance):
13454           assert not activate_disks
13455           feedback_fn("Starting instance %s" % instance.name)
13456           result = self.rpc.call_instance_start(src_node,
13457                                                 (instance, None, None), False)
13458           msg = result.fail_msg
13459           if msg:
13460             feedback_fn("Failed to start instance: %s" % msg)
13461             _ShutdownInstanceDisks(self, instance)
13462             raise errors.OpExecError("Could not start instance: %s" % msg)
13463
13464         if self.op.mode == constants.EXPORT_MODE_LOCAL:
13465           (fin_resu, dresults) = helper.LocalExport(self.dst_node)
13466         elif self.op.mode == constants.EXPORT_MODE_REMOTE:
13467           connect_timeout = constants.RIE_CONNECT_TIMEOUT
13468           timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
13469
13470           (key_name, _, _) = self.x509_key_name
13471
13472           dest_ca_pem = \
13473             OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
13474                                             self.dest_x509_ca)
13475
13476           (fin_resu, dresults) = helper.RemoteExport(self.dest_disk_info,
13477                                                      key_name, dest_ca_pem,
13478                                                      timeouts)
13479       finally:
13480         helper.Cleanup()
13481
13482       # Check for backwards compatibility
13483       assert len(dresults) == len(instance.disks)
13484       assert compat.all(isinstance(i, bool) for i in dresults), \
13485              "Not all results are boolean: %r" % dresults
13486
13487     finally:
13488       if activate_disks:
13489         feedback_fn("Deactivating disks for %s" % instance.name)
13490         _ShutdownInstanceDisks(self, instance)
13491
13492     if not (compat.all(dresults) and fin_resu):
13493       failures = []
13494       if not fin_resu:
13495         failures.append("export finalization")
13496       if not compat.all(dresults):
13497         fdsk = utils.CommaJoin(idx for (idx, dsk) in enumerate(dresults)
13498                                if not dsk)
13499         failures.append("disk export: disk(s) %s" % fdsk)
13500
13501       raise errors.OpExecError("Export failed, errors in %s" %
13502                                utils.CommaJoin(failures))
13503
13504     # At this point, the export was successful, we can cleanup/finish
13505
13506     # Remove instance if requested
13507     if self.op.remove_instance:
13508       feedback_fn("Removing instance %s" % instance.name)
13509       _RemoveInstance(self, feedback_fn, instance,
13510                       self.op.ignore_remove_failures)
13511
13512     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13513       self._CleanupExports(feedback_fn)
13514
13515     return fin_resu, dresults
13516
13517
13518 class LUBackupRemove(NoHooksLU):
13519   """Remove exports related to the named instance.
13520
13521   """
13522   REQ_BGL = False
13523
13524   def ExpandNames(self):
13525     self.needed_locks = {}
13526     # We need all nodes to be locked in order for RemoveExport to work, but we
13527     # don't need to lock the instance itself, as nothing will happen to it (and
13528     # we can remove exports also for a removed instance)
13529     self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13530
13531   def Exec(self, feedback_fn):
13532     """Remove any export.
13533
13534     """
13535     instance_name = self.cfg.ExpandInstanceName(self.op.instance_name)
13536     # If the instance was not found we'll try with the name that was passed in.
13537     # This will only work if it was an FQDN, though.
13538     fqdn_warn = False
13539     if not instance_name:
13540       fqdn_warn = True
13541       instance_name = self.op.instance_name
13542
13543     locked_nodes = self.owned_locks(locking.LEVEL_NODE)
13544     exportlist = self.rpc.call_export_list(locked_nodes)
13545     found = False
13546     for node in exportlist:
13547       msg = exportlist[node].fail_msg
13548       if msg:
13549         self.LogWarning("Failed to query node %s (continuing): %s", node, msg)
13550         continue
13551       if instance_name in exportlist[node].payload:
13552         found = True
13553         result = self.rpc.call_export_remove(node, instance_name)
13554         msg = result.fail_msg
13555         if msg:
13556           logging.error("Could not remove export for instance %s"
13557                         " on node %s: %s", instance_name, node, msg)
13558
13559     if fqdn_warn and not found:
13560       feedback_fn("Export not found. If trying to remove an export belonging"
13561                   " to a deleted instance please use its Fully Qualified"
13562                   " Domain Name.")
13563
13564
13565 class LUGroupAdd(LogicalUnit):
13566   """Logical unit for creating node groups.
13567
13568   """
13569   HPATH = "group-add"
13570   HTYPE = constants.HTYPE_GROUP
13571   REQ_BGL = False
13572
13573   def ExpandNames(self):
13574     # We need the new group's UUID here so that we can create and acquire the
13575     # corresponding lock. Later, in Exec(), we'll indicate to cfg.AddNodeGroup
13576     # that it should not check whether the UUID exists in the configuration.
13577     self.group_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
13578     self.needed_locks = {}
13579     self.add_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
13580
13581   def CheckPrereq(self):
13582     """Check prerequisites.
13583
13584     This checks that the given group name is not an existing node group
13585     already.
13586
13587     """
13588     try:
13589       existing_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13590     except errors.OpPrereqError:
13591       pass
13592     else:
13593       raise errors.OpPrereqError("Desired group name '%s' already exists as a"
13594                                  " node group (UUID: %s)" %
13595                                  (self.op.group_name, existing_uuid),
13596                                  errors.ECODE_EXISTS)
13597
13598     if self.op.ndparams:
13599       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
13600
13601     if self.op.hv_state:
13602       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None)
13603     else:
13604       self.new_hv_state = None
13605
13606     if self.op.disk_state:
13607       self.new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state, None)
13608     else:
13609       self.new_disk_state = None
13610
13611     if self.op.diskparams:
13612       for templ in constants.DISK_TEMPLATES:
13613         if templ in self.op.diskparams:
13614           utils.ForceDictType(self.op.diskparams[templ],
13615                               constants.DISK_DT_TYPES)
13616       self.new_diskparams = self.op.diskparams
13617       try:
13618         utils.VerifyDictOptions(self.new_diskparams, constants.DISK_DT_DEFAULTS)
13619       except errors.OpPrereqError, err:
13620         raise errors.OpPrereqError("While verify diskparams options: %s" % err,
13621                                    errors.ECODE_INVAL)
13622     else:
13623       self.new_diskparams = {}
13624
13625     if self.op.ipolicy:
13626       cluster = self.cfg.GetClusterInfo()
13627       full_ipolicy = cluster.SimpleFillIPolicy(self.op.ipolicy)
13628       try:
13629         objects.InstancePolicy.CheckParameterSyntax(full_ipolicy)
13630       except errors.ConfigurationError, err:
13631         raise errors.OpPrereqError("Invalid instance policy: %s" % err,
13632                                    errors.ECODE_INVAL)
13633
13634   def BuildHooksEnv(self):
13635     """Build hooks env.
13636
13637     """
13638     return {
13639       "GROUP_NAME": self.op.group_name,
13640       }
13641
13642   def BuildHooksNodes(self):
13643     """Build hooks nodes.
13644
13645     """
13646     mn = self.cfg.GetMasterNode()
13647     return ([mn], [mn])
13648
13649   def Exec(self, feedback_fn):
13650     """Add the node group to the cluster.
13651
13652     """
13653     group_obj = objects.NodeGroup(name=self.op.group_name, members=[],
13654                                   uuid=self.group_uuid,
13655                                   alloc_policy=self.op.alloc_policy,
13656                                   ndparams=self.op.ndparams,
13657                                   diskparams=self.new_diskparams,
13658                                   ipolicy=self.op.ipolicy,
13659                                   hv_state_static=self.new_hv_state,
13660                                   disk_state_static=self.new_disk_state)
13661
13662     self.cfg.AddNodeGroup(group_obj, self.proc.GetECId(), check_uuid=False)
13663     del self.remove_locks[locking.LEVEL_NODEGROUP]
13664
13665
13666 class LUGroupAssignNodes(NoHooksLU):
13667   """Logical unit for assigning nodes to groups.
13668
13669   """
13670   REQ_BGL = False
13671
13672   def ExpandNames(self):
13673     # These raise errors.OpPrereqError on their own:
13674     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13675     self.op.nodes = _GetWantedNodes(self, self.op.nodes)
13676
13677     # We want to lock all the affected nodes and groups. We have readily
13678     # available the list of nodes, and the *destination* group. To gather the
13679     # list of "source" groups, we need to fetch node information later on.
13680     self.needed_locks = {
13681       locking.LEVEL_NODEGROUP: set([self.group_uuid]),
13682       locking.LEVEL_NODE: self.op.nodes,
13683       }
13684
13685   def DeclareLocks(self, level):
13686     if level == locking.LEVEL_NODEGROUP:
13687       assert len(self.needed_locks[locking.LEVEL_NODEGROUP]) == 1
13688
13689       # Try to get all affected nodes' groups without having the group or node
13690       # lock yet. Needs verification later in the code flow.
13691       groups = self.cfg.GetNodeGroupsFromNodes(self.op.nodes)
13692
13693       self.needed_locks[locking.LEVEL_NODEGROUP].update(groups)
13694
13695   def CheckPrereq(self):
13696     """Check prerequisites.
13697
13698     """
13699     assert self.needed_locks[locking.LEVEL_NODEGROUP]
13700     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
13701             frozenset(self.op.nodes))
13702
13703     expected_locks = (set([self.group_uuid]) |
13704                       self.cfg.GetNodeGroupsFromNodes(self.op.nodes))
13705     actual_locks = self.owned_locks(locking.LEVEL_NODEGROUP)
13706     if actual_locks != expected_locks:
13707       raise errors.OpExecError("Nodes changed groups since locks were acquired,"
13708                                " current groups are '%s', used to be '%s'" %
13709                                (utils.CommaJoin(expected_locks),
13710                                 utils.CommaJoin(actual_locks)))
13711
13712     self.node_data = self.cfg.GetAllNodesInfo()
13713     self.group = self.cfg.GetNodeGroup(self.group_uuid)
13714     instance_data = self.cfg.GetAllInstancesInfo()
13715
13716     if self.group is None:
13717       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
13718                                (self.op.group_name, self.group_uuid))
13719
13720     (new_splits, previous_splits) = \
13721       self.CheckAssignmentForSplitInstances([(node, self.group_uuid)
13722                                              for node in self.op.nodes],
13723                                             self.node_data, instance_data)
13724
13725     if new_splits:
13726       fmt_new_splits = utils.CommaJoin(utils.NiceSort(new_splits))
13727
13728       if not self.op.force:
13729         raise errors.OpExecError("The following instances get split by this"
13730                                  " change and --force was not given: %s" %
13731                                  fmt_new_splits)
13732       else:
13733         self.LogWarning("This operation will split the following instances: %s",
13734                         fmt_new_splits)
13735
13736         if previous_splits:
13737           self.LogWarning("In addition, these already-split instances continue"
13738                           " to be split across groups: %s",
13739                           utils.CommaJoin(utils.NiceSort(previous_splits)))
13740
13741   def Exec(self, feedback_fn):
13742     """Assign nodes to a new group.
13743
13744     """
13745     mods = [(node_name, self.group_uuid) for node_name in self.op.nodes]
13746
13747     self.cfg.AssignGroupNodes(mods)
13748
13749   @staticmethod
13750   def CheckAssignmentForSplitInstances(changes, node_data, instance_data):
13751     """Check for split instances after a node assignment.
13752
13753     This method considers a series of node assignments as an atomic operation,
13754     and returns information about split instances after applying the set of
13755     changes.
13756
13757     In particular, it returns information about newly split instances, and
13758     instances that were already split, and remain so after the change.
13759
13760     Only instances whose disk template is listed in constants.DTS_INT_MIRROR are
13761     considered.
13762
13763     @type changes: list of (node_name, new_group_uuid) pairs.
13764     @param changes: list of node assignments to consider.
13765     @param node_data: a dict with data for all nodes
13766     @param instance_data: a dict with all instances to consider
13767     @rtype: a two-tuple
13768     @return: a list of instances that were previously okay and result split as a
13769       consequence of this change, and a list of instances that were previously
13770       split and this change does not fix.
13771
13772     """
13773     changed_nodes = dict((node, group) for node, group in changes
13774                          if node_data[node].group != group)
13775
13776     all_split_instances = set()
13777     previously_split_instances = set()
13778
13779     def InstanceNodes(instance):
13780       return [instance.primary_node] + list(instance.secondary_nodes)
13781
13782     for inst in instance_data.values():
13783       if inst.disk_template not in constants.DTS_INT_MIRROR:
13784         continue
13785
13786       instance_nodes = InstanceNodes(inst)
13787
13788       if len(set(node_data[node].group for node in instance_nodes)) > 1:
13789         previously_split_instances.add(inst.name)
13790
13791       if len(set(changed_nodes.get(node, node_data[node].group)
13792                  for node in instance_nodes)) > 1:
13793         all_split_instances.add(inst.name)
13794
13795     return (list(all_split_instances - previously_split_instances),
13796             list(previously_split_instances & all_split_instances))
13797
13798
13799 class _GroupQuery(_QueryBase):
13800   FIELDS = query.GROUP_FIELDS
13801
13802   def ExpandNames(self, lu):
13803     lu.needed_locks = {}
13804
13805     self._all_groups = lu.cfg.GetAllNodeGroupsInfo()
13806     self._cluster = lu.cfg.GetClusterInfo()
13807     name_to_uuid = dict((g.name, g.uuid) for g in self._all_groups.values())
13808
13809     if not self.names:
13810       self.wanted = [name_to_uuid[name]
13811                      for name in utils.NiceSort(name_to_uuid.keys())]
13812     else:
13813       # Accept names to be either names or UUIDs.
13814       missing = []
13815       self.wanted = []
13816       all_uuid = frozenset(self._all_groups.keys())
13817
13818       for name in self.names:
13819         if name in all_uuid:
13820           self.wanted.append(name)
13821         elif name in name_to_uuid:
13822           self.wanted.append(name_to_uuid[name])
13823         else:
13824           missing.append(name)
13825
13826       if missing:
13827         raise errors.OpPrereqError("Some groups do not exist: %s" %
13828                                    utils.CommaJoin(missing),
13829                                    errors.ECODE_NOENT)
13830
13831   def DeclareLocks(self, lu, level):
13832     pass
13833
13834   def _GetQueryData(self, lu):
13835     """Computes the list of node groups and their attributes.
13836
13837     """
13838     do_nodes = query.GQ_NODE in self.requested_data
13839     do_instances = query.GQ_INST in self.requested_data
13840
13841     group_to_nodes = None
13842     group_to_instances = None
13843
13844     # For GQ_NODE, we need to map group->[nodes], and group->[instances] for
13845     # GQ_INST. The former is attainable with just GetAllNodesInfo(), but for the
13846     # latter GetAllInstancesInfo() is not enough, for we have to go through
13847     # instance->node. Hence, we will need to process nodes even if we only need
13848     # instance information.
13849     if do_nodes or do_instances:
13850       all_nodes = lu.cfg.GetAllNodesInfo()
13851       group_to_nodes = dict((uuid, []) for uuid in self.wanted)
13852       node_to_group = {}
13853
13854       for node in all_nodes.values():
13855         if node.group in group_to_nodes:
13856           group_to_nodes[node.group].append(node.name)
13857           node_to_group[node.name] = node.group
13858
13859       if do_instances:
13860         all_instances = lu.cfg.GetAllInstancesInfo()
13861         group_to_instances = dict((uuid, []) for uuid in self.wanted)
13862
13863         for instance in all_instances.values():
13864           node = instance.primary_node
13865           if node in node_to_group:
13866             group_to_instances[node_to_group[node]].append(instance.name)
13867
13868         if not do_nodes:
13869           # Do not pass on node information if it was not requested.
13870           group_to_nodes = None
13871
13872     return query.GroupQueryData(self._cluster,
13873                                 [self._all_groups[uuid]
13874                                  for uuid in self.wanted],
13875                                 group_to_nodes, group_to_instances,
13876                                 query.GQ_DISKPARAMS in self.requested_data)
13877
13878
13879 class LUGroupQuery(NoHooksLU):
13880   """Logical unit for querying node groups.
13881
13882   """
13883   REQ_BGL = False
13884
13885   def CheckArguments(self):
13886     self.gq = _GroupQuery(qlang.MakeSimpleFilter("name", self.op.names),
13887                           self.op.output_fields, False)
13888
13889   def ExpandNames(self):
13890     self.gq.ExpandNames(self)
13891
13892   def DeclareLocks(self, level):
13893     self.gq.DeclareLocks(self, level)
13894
13895   def Exec(self, feedback_fn):
13896     return self.gq.OldStyleQuery(self)
13897
13898
13899 class LUGroupSetParams(LogicalUnit):
13900   """Modifies the parameters of a node group.
13901
13902   """
13903   HPATH = "group-modify"
13904   HTYPE = constants.HTYPE_GROUP
13905   REQ_BGL = False
13906
13907   def CheckArguments(self):
13908     all_changes = [
13909       self.op.ndparams,
13910       self.op.diskparams,
13911       self.op.alloc_policy,
13912       self.op.hv_state,
13913       self.op.disk_state,
13914       self.op.ipolicy,
13915       ]
13916
13917     if all_changes.count(None) == len(all_changes):
13918       raise errors.OpPrereqError("Please pass at least one modification",
13919                                  errors.ECODE_INVAL)
13920
13921   def ExpandNames(self):
13922     # This raises errors.OpPrereqError on its own:
13923     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
13924
13925     self.needed_locks = {
13926       locking.LEVEL_INSTANCE: [],
13927       locking.LEVEL_NODEGROUP: [self.group_uuid],
13928       }
13929
13930     self.share_locks[locking.LEVEL_INSTANCE] = 1
13931
13932   def DeclareLocks(self, level):
13933     if level == locking.LEVEL_INSTANCE:
13934       assert not self.needed_locks[locking.LEVEL_INSTANCE]
13935
13936       # Lock instances optimistically, needs verification once group lock has
13937       # been acquired
13938       self.needed_locks[locking.LEVEL_INSTANCE] = \
13939           self.cfg.GetNodeGroupInstances(self.group_uuid)
13940
13941   @staticmethod
13942   def _UpdateAndVerifyDiskParams(old, new):
13943     """Updates and verifies disk parameters.
13944
13945     """
13946     new_params = _GetUpdatedParams(old, new)
13947     utils.ForceDictType(new_params, constants.DISK_DT_TYPES)
13948     return new_params
13949
13950   def CheckPrereq(self):
13951     """Check prerequisites.
13952
13953     """
13954     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
13955
13956     # Check if locked instances are still correct
13957     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
13958
13959     self.group = self.cfg.GetNodeGroup(self.group_uuid)
13960     cluster = self.cfg.GetClusterInfo()
13961
13962     if self.group is None:
13963       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
13964                                (self.op.group_name, self.group_uuid))
13965
13966     if self.op.ndparams:
13967       new_ndparams = _GetUpdatedParams(self.group.ndparams, self.op.ndparams)
13968       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
13969       self.new_ndparams = new_ndparams
13970
13971     if self.op.diskparams:
13972       diskparams = self.group.diskparams
13973       uavdp = self._UpdateAndVerifyDiskParams
13974       # For each disktemplate subdict update and verify the values
13975       new_diskparams = dict((dt,
13976                              uavdp(diskparams.get(dt, {}),
13977                                    self.op.diskparams[dt]))
13978                             for dt in constants.DISK_TEMPLATES
13979                             if dt in self.op.diskparams)
13980       # As we've all subdicts of diskparams ready, lets merge the actual
13981       # dict with all updated subdicts
13982       self.new_diskparams = objects.FillDict(diskparams, new_diskparams)
13983       try:
13984         utils.VerifyDictOptions(self.new_diskparams, constants.DISK_DT_DEFAULTS)
13985       except errors.OpPrereqError, err:
13986         raise errors.OpPrereqError("While verify diskparams options: %s" % err,
13987                                    errors.ECODE_INVAL)
13988
13989     if self.op.hv_state:
13990       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
13991                                                  self.group.hv_state_static)
13992
13993     if self.op.disk_state:
13994       self.new_disk_state = \
13995         _MergeAndVerifyDiskState(self.op.disk_state,
13996                                  self.group.disk_state_static)
13997
13998     if self.op.ipolicy:
13999       self.new_ipolicy = _GetUpdatedIPolicy(self.group.ipolicy,
14000                                             self.op.ipolicy,
14001                                             group_policy=True)
14002
14003       new_ipolicy = cluster.SimpleFillIPolicy(self.new_ipolicy)
14004       inst_filter = lambda inst: inst.name in owned_instances
14005       instances = self.cfg.GetInstancesInfoByFilter(inst_filter).values()
14006       violations = \
14007           _ComputeNewInstanceViolations(_CalculateGroupIPolicy(cluster,
14008                                                                self.group),
14009                                         new_ipolicy, instances)
14010
14011       if violations:
14012         self.LogWarning("After the ipolicy change the following instances"
14013                         " violate them: %s",
14014                         utils.CommaJoin(violations))
14015
14016   def BuildHooksEnv(self):
14017     """Build hooks env.
14018
14019     """
14020     return {
14021       "GROUP_NAME": self.op.group_name,
14022       "NEW_ALLOC_POLICY": self.op.alloc_policy,
14023       }
14024
14025   def BuildHooksNodes(self):
14026     """Build hooks nodes.
14027
14028     """
14029     mn = self.cfg.GetMasterNode()
14030     return ([mn], [mn])
14031
14032   def Exec(self, feedback_fn):
14033     """Modifies the node group.
14034
14035     """
14036     result = []
14037
14038     if self.op.ndparams:
14039       self.group.ndparams = self.new_ndparams
14040       result.append(("ndparams", str(self.group.ndparams)))
14041
14042     if self.op.diskparams:
14043       self.group.diskparams = self.new_diskparams
14044       result.append(("diskparams", str(self.group.diskparams)))
14045
14046     if self.op.alloc_policy:
14047       self.group.alloc_policy = self.op.alloc_policy
14048
14049     if self.op.hv_state:
14050       self.group.hv_state_static = self.new_hv_state
14051
14052     if self.op.disk_state:
14053       self.group.disk_state_static = self.new_disk_state
14054
14055     if self.op.ipolicy:
14056       self.group.ipolicy = self.new_ipolicy
14057
14058     self.cfg.Update(self.group, feedback_fn)
14059     return result
14060
14061
14062 class LUGroupRemove(LogicalUnit):
14063   HPATH = "group-remove"
14064   HTYPE = constants.HTYPE_GROUP
14065   REQ_BGL = False
14066
14067   def ExpandNames(self):
14068     # This will raises errors.OpPrereqError on its own:
14069     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14070     self.needed_locks = {
14071       locking.LEVEL_NODEGROUP: [self.group_uuid],
14072       }
14073
14074   def CheckPrereq(self):
14075     """Check prerequisites.
14076
14077     This checks that the given group name exists as a node group, that is
14078     empty (i.e., contains no nodes), and that is not the last group of the
14079     cluster.
14080
14081     """
14082     # Verify that the group is empty.
14083     group_nodes = [node.name
14084                    for node in self.cfg.GetAllNodesInfo().values()
14085                    if node.group == self.group_uuid]
14086
14087     if group_nodes:
14088       raise errors.OpPrereqError("Group '%s' not empty, has the following"
14089                                  " nodes: %s" %
14090                                  (self.op.group_name,
14091                                   utils.CommaJoin(utils.NiceSort(group_nodes))),
14092                                  errors.ECODE_STATE)
14093
14094     # Verify the cluster would not be left group-less.
14095     if len(self.cfg.GetNodeGroupList()) == 1:
14096       raise errors.OpPrereqError("Group '%s' is the only group,"
14097                                  " cannot be removed" %
14098                                  self.op.group_name,
14099                                  errors.ECODE_STATE)
14100
14101   def BuildHooksEnv(self):
14102     """Build hooks env.
14103
14104     """
14105     return {
14106       "GROUP_NAME": self.op.group_name,
14107       }
14108
14109   def BuildHooksNodes(self):
14110     """Build hooks nodes.
14111
14112     """
14113     mn = self.cfg.GetMasterNode()
14114     return ([mn], [mn])
14115
14116   def Exec(self, feedback_fn):
14117     """Remove the node group.
14118
14119     """
14120     try:
14121       self.cfg.RemoveNodeGroup(self.group_uuid)
14122     except errors.ConfigurationError:
14123       raise errors.OpExecError("Group '%s' with UUID %s disappeared" %
14124                                (self.op.group_name, self.group_uuid))
14125
14126     self.remove_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
14127
14128
14129 class LUGroupRename(LogicalUnit):
14130   HPATH = "group-rename"
14131   HTYPE = constants.HTYPE_GROUP
14132   REQ_BGL = False
14133
14134   def ExpandNames(self):
14135     # This raises errors.OpPrereqError on its own:
14136     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14137
14138     self.needed_locks = {
14139       locking.LEVEL_NODEGROUP: [self.group_uuid],
14140       }
14141
14142   def CheckPrereq(self):
14143     """Check prerequisites.
14144
14145     Ensures requested new name is not yet used.
14146
14147     """
14148     try:
14149       new_name_uuid = self.cfg.LookupNodeGroup(self.op.new_name)
14150     except errors.OpPrereqError:
14151       pass
14152     else:
14153       raise errors.OpPrereqError("Desired new name '%s' clashes with existing"
14154                                  " node group (UUID: %s)" %
14155                                  (self.op.new_name, new_name_uuid),
14156                                  errors.ECODE_EXISTS)
14157
14158   def BuildHooksEnv(self):
14159     """Build hooks env.
14160
14161     """
14162     return {
14163       "OLD_NAME": self.op.group_name,
14164       "NEW_NAME": self.op.new_name,
14165       }
14166
14167   def BuildHooksNodes(self):
14168     """Build hooks nodes.
14169
14170     """
14171     mn = self.cfg.GetMasterNode()
14172
14173     all_nodes = self.cfg.GetAllNodesInfo()
14174     all_nodes.pop(mn, None)
14175
14176     run_nodes = [mn]
14177     run_nodes.extend(node.name for node in all_nodes.values()
14178                      if node.group == self.group_uuid)
14179
14180     return (run_nodes, run_nodes)
14181
14182   def Exec(self, feedback_fn):
14183     """Rename the node group.
14184
14185     """
14186     group = self.cfg.GetNodeGroup(self.group_uuid)
14187
14188     if group is None:
14189       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
14190                                (self.op.group_name, self.group_uuid))
14191
14192     group.name = self.op.new_name
14193     self.cfg.Update(group, feedback_fn)
14194
14195     return self.op.new_name
14196
14197
14198 class LUGroupEvacuate(LogicalUnit):
14199   HPATH = "group-evacuate"
14200   HTYPE = constants.HTYPE_GROUP
14201   REQ_BGL = False
14202
14203   def ExpandNames(self):
14204     # This raises errors.OpPrereqError on its own:
14205     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14206
14207     if self.op.target_groups:
14208       self.req_target_uuids = map(self.cfg.LookupNodeGroup,
14209                                   self.op.target_groups)
14210     else:
14211       self.req_target_uuids = []
14212
14213     if self.group_uuid in self.req_target_uuids:
14214       raise errors.OpPrereqError("Group to be evacuated (%s) can not be used"
14215                                  " as a target group (targets are %s)" %
14216                                  (self.group_uuid,
14217                                   utils.CommaJoin(self.req_target_uuids)),
14218                                  errors.ECODE_INVAL)
14219
14220     self.op.iallocator = _GetDefaultIAllocator(self.cfg, self.op.iallocator)
14221
14222     self.share_locks = _ShareAll()
14223     self.needed_locks = {
14224       locking.LEVEL_INSTANCE: [],
14225       locking.LEVEL_NODEGROUP: [],
14226       locking.LEVEL_NODE: [],
14227       }
14228
14229   def DeclareLocks(self, level):
14230     if level == locking.LEVEL_INSTANCE:
14231       assert not self.needed_locks[locking.LEVEL_INSTANCE]
14232
14233       # Lock instances optimistically, needs verification once node and group
14234       # locks have been acquired
14235       self.needed_locks[locking.LEVEL_INSTANCE] = \
14236         self.cfg.GetNodeGroupInstances(self.group_uuid)
14237
14238     elif level == locking.LEVEL_NODEGROUP:
14239       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
14240
14241       if self.req_target_uuids:
14242         lock_groups = set([self.group_uuid] + self.req_target_uuids)
14243
14244         # Lock all groups used by instances optimistically; this requires going
14245         # via the node before it's locked, requiring verification later on
14246         lock_groups.update(group_uuid
14247                            for instance_name in
14248                              self.owned_locks(locking.LEVEL_INSTANCE)
14249                            for group_uuid in
14250                              self.cfg.GetInstanceNodeGroups(instance_name))
14251       else:
14252         # No target groups, need to lock all of them
14253         lock_groups = locking.ALL_SET
14254
14255       self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
14256
14257     elif level == locking.LEVEL_NODE:
14258       # This will only lock the nodes in the group to be evacuated which
14259       # contain actual instances
14260       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
14261       self._LockInstancesNodes()
14262
14263       # Lock all nodes in group to be evacuated and target groups
14264       owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
14265       assert self.group_uuid in owned_groups
14266       member_nodes = [node_name
14267                       for group in owned_groups
14268                       for node_name in self.cfg.GetNodeGroup(group).members]
14269       self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
14270
14271   def CheckPrereq(self):
14272     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
14273     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
14274     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
14275
14276     assert owned_groups.issuperset(self.req_target_uuids)
14277     assert self.group_uuid in owned_groups
14278
14279     # Check if locked instances are still correct
14280     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
14281
14282     # Get instance information
14283     self.instances = dict(self.cfg.GetMultiInstanceInfo(owned_instances))
14284
14285     # Check if node groups for locked instances are still correct
14286     _CheckInstancesNodeGroups(self.cfg, self.instances,
14287                               owned_groups, owned_nodes, self.group_uuid)
14288
14289     if self.req_target_uuids:
14290       # User requested specific target groups
14291       self.target_uuids = self.req_target_uuids
14292     else:
14293       # All groups except the one to be evacuated are potential targets
14294       self.target_uuids = [group_uuid for group_uuid in owned_groups
14295                            if group_uuid != self.group_uuid]
14296
14297       if not self.target_uuids:
14298         raise errors.OpPrereqError("There are no possible target groups",
14299                                    errors.ECODE_INVAL)
14300
14301   def BuildHooksEnv(self):
14302     """Build hooks env.
14303
14304     """
14305     return {
14306       "GROUP_NAME": self.op.group_name,
14307       "TARGET_GROUPS": " ".join(self.target_uuids),
14308       }
14309
14310   def BuildHooksNodes(self):
14311     """Build hooks nodes.
14312
14313     """
14314     mn = self.cfg.GetMasterNode()
14315
14316     assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
14317
14318     run_nodes = [mn] + self.cfg.GetNodeGroup(self.group_uuid).members
14319
14320     return (run_nodes, run_nodes)
14321
14322   def Exec(self, feedback_fn):
14323     instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
14324
14325     assert self.group_uuid not in self.target_uuids
14326
14327     ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_CHG_GROUP,
14328                      instances=instances, target_groups=self.target_uuids)
14329
14330     ial.Run(self.op.iallocator)
14331
14332     if not ial.success:
14333       raise errors.OpPrereqError("Can't compute group evacuation using"
14334                                  " iallocator '%s': %s" %
14335                                  (self.op.iallocator, ial.info),
14336                                  errors.ECODE_NORES)
14337
14338     jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
14339
14340     self.LogInfo("Iallocator returned %s job(s) for evacuating node group %s",
14341                  len(jobs), self.op.group_name)
14342
14343     return ResultWithJobs(jobs)
14344
14345
14346 class TagsLU(NoHooksLU): # pylint: disable=W0223
14347   """Generic tags LU.
14348
14349   This is an abstract class which is the parent of all the other tags LUs.
14350
14351   """
14352   def ExpandNames(self):
14353     self.group_uuid = None
14354     self.needed_locks = {}
14355
14356     if self.op.kind == constants.TAG_NODE:
14357       self.op.name = _ExpandNodeName(self.cfg, self.op.name)
14358       lock_level = locking.LEVEL_NODE
14359       lock_name = self.op.name
14360     elif self.op.kind == constants.TAG_INSTANCE:
14361       self.op.name = _ExpandInstanceName(self.cfg, self.op.name)
14362       lock_level = locking.LEVEL_INSTANCE
14363       lock_name = self.op.name
14364     elif self.op.kind == constants.TAG_NODEGROUP:
14365       self.group_uuid = self.cfg.LookupNodeGroup(self.op.name)
14366       lock_level = locking.LEVEL_NODEGROUP
14367       lock_name = self.group_uuid
14368     else:
14369       lock_level = None
14370       lock_name = None
14371
14372     if lock_level and getattr(self.op, "use_locking", True):
14373       self.needed_locks[lock_level] = lock_name
14374
14375     # FIXME: Acquire BGL for cluster tag operations (as of this writing it's
14376     # not possible to acquire the BGL based on opcode parameters)
14377
14378   def CheckPrereq(self):
14379     """Check prerequisites.
14380
14381     """
14382     if self.op.kind == constants.TAG_CLUSTER:
14383       self.target = self.cfg.GetClusterInfo()
14384     elif self.op.kind == constants.TAG_NODE:
14385       self.target = self.cfg.GetNodeInfo(self.op.name)
14386     elif self.op.kind == constants.TAG_INSTANCE:
14387       self.target = self.cfg.GetInstanceInfo(self.op.name)
14388     elif self.op.kind == constants.TAG_NODEGROUP:
14389       self.target = self.cfg.GetNodeGroup(self.group_uuid)
14390     else:
14391       raise errors.OpPrereqError("Wrong tag type requested (%s)" %
14392                                  str(self.op.kind), errors.ECODE_INVAL)
14393
14394
14395 class LUTagsGet(TagsLU):
14396   """Returns the tags of a given object.
14397
14398   """
14399   REQ_BGL = False
14400
14401   def ExpandNames(self):
14402     TagsLU.ExpandNames(self)
14403
14404     # Share locks as this is only a read operation
14405     self.share_locks = _ShareAll()
14406
14407   def Exec(self, feedback_fn):
14408     """Returns the tag list.
14409
14410     """
14411     return list(self.target.GetTags())
14412
14413
14414 class LUTagsSearch(NoHooksLU):
14415   """Searches the tags for a given pattern.
14416
14417   """
14418   REQ_BGL = False
14419
14420   def ExpandNames(self):
14421     self.needed_locks = {}
14422
14423   def CheckPrereq(self):
14424     """Check prerequisites.
14425
14426     This checks the pattern passed for validity by compiling it.
14427
14428     """
14429     try:
14430       self.re = re.compile(self.op.pattern)
14431     except re.error, err:
14432       raise errors.OpPrereqError("Invalid search pattern '%s': %s" %
14433                                  (self.op.pattern, err), errors.ECODE_INVAL)
14434
14435   def Exec(self, feedback_fn):
14436     """Returns the tag list.
14437
14438     """
14439     cfg = self.cfg
14440     tgts = [("/cluster", cfg.GetClusterInfo())]
14441     ilist = cfg.GetAllInstancesInfo().values()
14442     tgts.extend([("/instances/%s" % i.name, i) for i in ilist])
14443     nlist = cfg.GetAllNodesInfo().values()
14444     tgts.extend([("/nodes/%s" % n.name, n) for n in nlist])
14445     tgts.extend(("/nodegroup/%s" % n.name, n)
14446                 for n in cfg.GetAllNodeGroupsInfo().values())
14447     results = []
14448     for path, target in tgts:
14449       for tag in target.GetTags():
14450         if self.re.search(tag):
14451           results.append((path, tag))
14452     return results
14453
14454
14455 class LUTagsSet(TagsLU):
14456   """Sets a tag on a given object.
14457
14458   """
14459   REQ_BGL = False
14460
14461   def CheckPrereq(self):
14462     """Check prerequisites.
14463
14464     This checks the type and length of the tag name and value.
14465
14466     """
14467     TagsLU.CheckPrereq(self)
14468     for tag in self.op.tags:
14469       objects.TaggableObject.ValidateTag(tag)
14470
14471   def Exec(self, feedback_fn):
14472     """Sets the tag.
14473
14474     """
14475     try:
14476       for tag in self.op.tags:
14477         self.target.AddTag(tag)
14478     except errors.TagError, err:
14479       raise errors.OpExecError("Error while setting tag: %s" % str(err))
14480     self.cfg.Update(self.target, feedback_fn)
14481
14482
14483 class LUTagsDel(TagsLU):
14484   """Delete a list of tags from a given object.
14485
14486   """
14487   REQ_BGL = False
14488
14489   def CheckPrereq(self):
14490     """Check prerequisites.
14491
14492     This checks that we have the given tag.
14493
14494     """
14495     TagsLU.CheckPrereq(self)
14496     for tag in self.op.tags:
14497       objects.TaggableObject.ValidateTag(tag)
14498     del_tags = frozenset(self.op.tags)
14499     cur_tags = self.target.GetTags()
14500
14501     diff_tags = del_tags - cur_tags
14502     if diff_tags:
14503       diff_names = ("'%s'" % i for i in sorted(diff_tags))
14504       raise errors.OpPrereqError("Tag(s) %s not found" %
14505                                  (utils.CommaJoin(diff_names), ),
14506                                  errors.ECODE_NOENT)
14507
14508   def Exec(self, feedback_fn):
14509     """Remove the tag from the object.
14510
14511     """
14512     for tag in self.op.tags:
14513       self.target.RemoveTag(tag)
14514     self.cfg.Update(self.target, feedback_fn)
14515
14516
14517 class LUTestDelay(NoHooksLU):
14518   """Sleep for a specified amount of time.
14519
14520   This LU sleeps on the master and/or nodes for a specified amount of
14521   time.
14522
14523   """
14524   REQ_BGL = False
14525
14526   def ExpandNames(self):
14527     """Expand names and set required locks.
14528
14529     This expands the node list, if any.
14530
14531     """
14532     self.needed_locks = {}
14533     if self.op.on_nodes:
14534       # _GetWantedNodes can be used here, but is not always appropriate to use
14535       # this way in ExpandNames. Check LogicalUnit.ExpandNames docstring for
14536       # more information.
14537       self.op.on_nodes = _GetWantedNodes(self, self.op.on_nodes)
14538       self.needed_locks[locking.LEVEL_NODE] = self.op.on_nodes
14539
14540   def _TestDelay(self):
14541     """Do the actual sleep.
14542
14543     """
14544     if self.op.on_master:
14545       if not utils.TestDelay(self.op.duration):
14546         raise errors.OpExecError("Error during master delay test")
14547     if self.op.on_nodes:
14548       result = self.rpc.call_test_delay(self.op.on_nodes, self.op.duration)
14549       for node, node_result in result.items():
14550         node_result.Raise("Failure during rpc call to node %s" % node)
14551
14552   def Exec(self, feedback_fn):
14553     """Execute the test delay opcode, with the wanted repetitions.
14554
14555     """
14556     if self.op.repeat == 0:
14557       self._TestDelay()
14558     else:
14559       top_value = self.op.repeat - 1
14560       for i in range(self.op.repeat):
14561         self.LogInfo("Test delay iteration %d/%d" % (i, top_value))
14562         self._TestDelay()
14563
14564
14565 class LUTestJqueue(NoHooksLU):
14566   """Utility LU to test some aspects of the job queue.
14567
14568   """
14569   REQ_BGL = False
14570
14571   # Must be lower than default timeout for WaitForJobChange to see whether it
14572   # notices changed jobs
14573   _CLIENT_CONNECT_TIMEOUT = 20.0
14574   _CLIENT_CONFIRM_TIMEOUT = 60.0
14575
14576   @classmethod
14577   def _NotifyUsingSocket(cls, cb, errcls):
14578     """Opens a Unix socket and waits for another program to connect.
14579
14580     @type cb: callable
14581     @param cb: Callback to send socket name to client
14582     @type errcls: class
14583     @param errcls: Exception class to use for errors
14584
14585     """
14586     # Using a temporary directory as there's no easy way to create temporary
14587     # sockets without writing a custom loop around tempfile.mktemp and
14588     # socket.bind
14589     tmpdir = tempfile.mkdtemp()
14590     try:
14591       tmpsock = utils.PathJoin(tmpdir, "sock")
14592
14593       logging.debug("Creating temporary socket at %s", tmpsock)
14594       sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
14595       try:
14596         sock.bind(tmpsock)
14597         sock.listen(1)
14598
14599         # Send details to client
14600         cb(tmpsock)
14601
14602         # Wait for client to connect before continuing
14603         sock.settimeout(cls._CLIENT_CONNECT_TIMEOUT)
14604         try:
14605           (conn, _) = sock.accept()
14606         except socket.error, err:
14607           raise errcls("Client didn't connect in time (%s)" % err)
14608       finally:
14609         sock.close()
14610     finally:
14611       # Remove as soon as client is connected
14612       shutil.rmtree(tmpdir)
14613
14614     # Wait for client to close
14615     try:
14616       try:
14617         # pylint: disable=E1101
14618         # Instance of '_socketobject' has no ... member
14619         conn.settimeout(cls._CLIENT_CONFIRM_TIMEOUT)
14620         conn.recv(1)
14621       except socket.error, err:
14622         raise errcls("Client failed to confirm notification (%s)" % err)
14623     finally:
14624       conn.close()
14625
14626   def _SendNotification(self, test, arg, sockname):
14627     """Sends a notification to the client.
14628
14629     @type test: string
14630     @param test: Test name
14631     @param arg: Test argument (depends on test)
14632     @type sockname: string
14633     @param sockname: Socket path
14634
14635     """
14636     self.Log(constants.ELOG_JQUEUE_TEST, (sockname, test, arg))
14637
14638   def _Notify(self, prereq, test, arg):
14639     """Notifies the client of a test.
14640
14641     @type prereq: bool
14642     @param prereq: Whether this is a prereq-phase test
14643     @type test: string
14644     @param test: Test name
14645     @param arg: Test argument (depends on test)
14646
14647     """
14648     if prereq:
14649       errcls = errors.OpPrereqError
14650     else:
14651       errcls = errors.OpExecError
14652
14653     return self._NotifyUsingSocket(compat.partial(self._SendNotification,
14654                                                   test, arg),
14655                                    errcls)
14656
14657   def CheckArguments(self):
14658     self.checkargs_calls = getattr(self, "checkargs_calls", 0) + 1
14659     self.expandnames_calls = 0
14660
14661   def ExpandNames(self):
14662     checkargs_calls = getattr(self, "checkargs_calls", 0)
14663     if checkargs_calls < 1:
14664       raise errors.ProgrammerError("CheckArguments was not called")
14665
14666     self.expandnames_calls += 1
14667
14668     if self.op.notify_waitlock:
14669       self._Notify(True, constants.JQT_EXPANDNAMES, None)
14670
14671     self.LogInfo("Expanding names")
14672
14673     # Get lock on master node (just to get a lock, not for a particular reason)
14674     self.needed_locks = {
14675       locking.LEVEL_NODE: self.cfg.GetMasterNode(),
14676       }
14677
14678   def Exec(self, feedback_fn):
14679     if self.expandnames_calls < 1:
14680       raise errors.ProgrammerError("ExpandNames was not called")
14681
14682     if self.op.notify_exec:
14683       self._Notify(False, constants.JQT_EXEC, None)
14684
14685     self.LogInfo("Executing")
14686
14687     if self.op.log_messages:
14688       self._Notify(False, constants.JQT_STARTMSG, len(self.op.log_messages))
14689       for idx, msg in enumerate(self.op.log_messages):
14690         self.LogInfo("Sending log message %s", idx + 1)
14691         feedback_fn(constants.JQT_MSGPREFIX + msg)
14692         # Report how many test messages have been sent
14693         self._Notify(False, constants.JQT_LOGMSG, idx + 1)
14694
14695     if self.op.fail:
14696       raise errors.OpExecError("Opcode failure was requested")
14697
14698     return True
14699
14700
14701 class IAllocator(object):
14702   """IAllocator framework.
14703
14704   An IAllocator instance has three sets of attributes:
14705     - cfg that is needed to query the cluster
14706     - input data (all members of the _KEYS class attribute are required)
14707     - four buffer attributes (in|out_data|text), that represent the
14708       input (to the external script) in text and data structure format,
14709       and the output from it, again in two formats
14710     - the result variables from the script (success, info, nodes) for
14711       easy usage
14712
14713   """
14714   # pylint: disable=R0902
14715   # lots of instance attributes
14716
14717   def __init__(self, cfg, rpc_runner, mode, **kwargs):
14718     self.cfg = cfg
14719     self.rpc = rpc_runner
14720     # init buffer variables
14721     self.in_text = self.out_text = self.in_data = self.out_data = None
14722     # init all input fields so that pylint is happy
14723     self.mode = mode
14724     self.memory = self.disks = self.disk_template = self.spindle_use = None
14725     self.os = self.tags = self.nics = self.vcpus = None
14726     self.hypervisor = None
14727     self.relocate_from = None
14728     self.name = None
14729     self.instances = None
14730     self.evac_mode = None
14731     self.target_groups = []
14732     # computed fields
14733     self.required_nodes = None
14734     # init result fields
14735     self.success = self.info = self.result = None
14736
14737     try:
14738       (fn, keydata, self._result_check) = self._MODE_DATA[self.mode]
14739     except KeyError:
14740       raise errors.ProgrammerError("Unknown mode '%s' passed to the"
14741                                    " IAllocator" % self.mode)
14742
14743     keyset = [n for (n, _) in keydata]
14744
14745     for key in kwargs:
14746       if key not in keyset:
14747         raise errors.ProgrammerError("Invalid input parameter '%s' to"
14748                                      " IAllocator" % key)
14749       setattr(self, key, kwargs[key])
14750
14751     for key in keyset:
14752       if key not in kwargs:
14753         raise errors.ProgrammerError("Missing input parameter '%s' to"
14754                                      " IAllocator" % key)
14755     self._BuildInputData(compat.partial(fn, self), keydata)
14756
14757   def _ComputeClusterData(self):
14758     """Compute the generic allocator input data.
14759
14760     This is the data that is independent of the actual operation.
14761
14762     """
14763     cfg = self.cfg
14764     cluster_info = cfg.GetClusterInfo()
14765     # cluster data
14766     data = {
14767       "version": constants.IALLOCATOR_VERSION,
14768       "cluster_name": cfg.GetClusterName(),
14769       "cluster_tags": list(cluster_info.GetTags()),
14770       "enabled_hypervisors": list(cluster_info.enabled_hypervisors),
14771       "ipolicy": cluster_info.ipolicy,
14772       }
14773     ninfo = cfg.GetAllNodesInfo()
14774     iinfo = cfg.GetAllInstancesInfo().values()
14775     i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo]
14776
14777     # node data
14778     node_list = [n.name for n in ninfo.values() if n.vm_capable]
14779
14780     if self.mode == constants.IALLOCATOR_MODE_ALLOC:
14781       hypervisor_name = self.hypervisor
14782     elif self.mode == constants.IALLOCATOR_MODE_RELOC:
14783       hypervisor_name = cfg.GetInstanceInfo(self.name).hypervisor
14784     else:
14785       hypervisor_name = cluster_info.primary_hypervisor
14786
14787     node_data = self.rpc.call_node_info(node_list, [cfg.GetVGName()],
14788                                         [hypervisor_name])
14789     node_iinfo = \
14790       self.rpc.call_all_instances_info(node_list,
14791                                        cluster_info.enabled_hypervisors)
14792
14793     data["nodegroups"] = self._ComputeNodeGroupData(cfg)
14794
14795     config_ndata = self._ComputeBasicNodeData(cfg, ninfo)
14796     data["nodes"] = self._ComputeDynamicNodeData(ninfo, node_data, node_iinfo,
14797                                                  i_list, config_ndata)
14798     assert len(data["nodes"]) == len(ninfo), \
14799         "Incomplete node data computed"
14800
14801     data["instances"] = self._ComputeInstanceData(cluster_info, i_list)
14802
14803     self.in_data = data
14804
14805   @staticmethod
14806   def _ComputeNodeGroupData(cfg):
14807     """Compute node groups data.
14808
14809     """
14810     cluster = cfg.GetClusterInfo()
14811     ng = dict((guuid, {
14812       "name": gdata.name,
14813       "alloc_policy": gdata.alloc_policy,
14814       "ipolicy": _CalculateGroupIPolicy(cluster, gdata),
14815       })
14816       for guuid, gdata in cfg.GetAllNodeGroupsInfo().items())
14817
14818     return ng
14819
14820   @staticmethod
14821   def _ComputeBasicNodeData(cfg, node_cfg):
14822     """Compute global node data.
14823
14824     @rtype: dict
14825     @returns: a dict of name: (node dict, node config)
14826
14827     """
14828     # fill in static (config-based) values
14829     node_results = dict((ninfo.name, {
14830       "tags": list(ninfo.GetTags()),
14831       "primary_ip": ninfo.primary_ip,
14832       "secondary_ip": ninfo.secondary_ip,
14833       "offline": ninfo.offline,
14834       "drained": ninfo.drained,
14835       "master_candidate": ninfo.master_candidate,
14836       "group": ninfo.group,
14837       "master_capable": ninfo.master_capable,
14838       "vm_capable": ninfo.vm_capable,
14839       "ndparams": cfg.GetNdParams(ninfo),
14840       })
14841       for ninfo in node_cfg.values())
14842
14843     return node_results
14844
14845   @staticmethod
14846   def _ComputeDynamicNodeData(node_cfg, node_data, node_iinfo, i_list,
14847                               node_results):
14848     """Compute global node data.
14849
14850     @param node_results: the basic node structures as filled from the config
14851
14852     """
14853     #TODO(dynmem): compute the right data on MAX and MIN memory
14854     # make a copy of the current dict
14855     node_results = dict(node_results)
14856     for nname, nresult in node_data.items():
14857       assert nname in node_results, "Missing basic data for node %s" % nname
14858       ninfo = node_cfg[nname]
14859
14860       if not (ninfo.offline or ninfo.drained):
14861         nresult.Raise("Can't get data for node %s" % nname)
14862         node_iinfo[nname].Raise("Can't get node instance info from node %s" %
14863                                 nname)
14864         remote_info = _MakeLegacyNodeInfo(nresult.payload)
14865
14866         for attr in ["memory_total", "memory_free", "memory_dom0",
14867                      "vg_size", "vg_free", "cpu_total"]:
14868           if attr not in remote_info:
14869             raise errors.OpExecError("Node '%s' didn't return attribute"
14870                                      " '%s'" % (nname, attr))
14871           if not isinstance(remote_info[attr], int):
14872             raise errors.OpExecError("Node '%s' returned invalid value"
14873                                      " for '%s': %s" %
14874                                      (nname, attr, remote_info[attr]))
14875         # compute memory used by primary instances
14876         i_p_mem = i_p_up_mem = 0
14877         for iinfo, beinfo in i_list:
14878           if iinfo.primary_node == nname:
14879             i_p_mem += beinfo[constants.BE_MAXMEM]
14880             if iinfo.name not in node_iinfo[nname].payload:
14881               i_used_mem = 0
14882             else:
14883               i_used_mem = int(node_iinfo[nname].payload[iinfo.name]["memory"])
14884             i_mem_diff = beinfo[constants.BE_MAXMEM] - i_used_mem
14885             remote_info["memory_free"] -= max(0, i_mem_diff)
14886
14887             if iinfo.admin_state == constants.ADMINST_UP:
14888               i_p_up_mem += beinfo[constants.BE_MAXMEM]
14889
14890         # compute memory used by instances
14891         pnr_dyn = {
14892           "total_memory": remote_info["memory_total"],
14893           "reserved_memory": remote_info["memory_dom0"],
14894           "free_memory": remote_info["memory_free"],
14895           "total_disk": remote_info["vg_size"],
14896           "free_disk": remote_info["vg_free"],
14897           "total_cpus": remote_info["cpu_total"],
14898           "i_pri_memory": i_p_mem,
14899           "i_pri_up_memory": i_p_up_mem,
14900           }
14901         pnr_dyn.update(node_results[nname])
14902         node_results[nname] = pnr_dyn
14903
14904     return node_results
14905
14906   @staticmethod
14907   def _ComputeInstanceData(cluster_info, i_list):
14908     """Compute global instance data.
14909
14910     """
14911     instance_data = {}
14912     for iinfo, beinfo in i_list:
14913       nic_data = []
14914       for nic in iinfo.nics:
14915         filled_params = cluster_info.SimpleFillNIC(nic.nicparams)
14916         nic_dict = {
14917           "mac": nic.mac,
14918           "ip": nic.ip,
14919           "mode": filled_params[constants.NIC_MODE],
14920           "link": filled_params[constants.NIC_LINK],
14921           }
14922         if filled_params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
14923           nic_dict["bridge"] = filled_params[constants.NIC_LINK]
14924         nic_data.append(nic_dict)
14925       pir = {
14926         "tags": list(iinfo.GetTags()),
14927         "admin_state": iinfo.admin_state,
14928         "vcpus": beinfo[constants.BE_VCPUS],
14929         "memory": beinfo[constants.BE_MAXMEM],
14930         "spindle_use": beinfo[constants.BE_SPINDLE_USE],
14931         "os": iinfo.os,
14932         "nodes": [iinfo.primary_node] + list(iinfo.secondary_nodes),
14933         "nics": nic_data,
14934         "disks": [{constants.IDISK_SIZE: dsk.size,
14935                    constants.IDISK_MODE: dsk.mode}
14936                   for dsk in iinfo.disks],
14937         "disk_template": iinfo.disk_template,
14938         "hypervisor": iinfo.hypervisor,
14939         }
14940       pir["disk_space_total"] = _ComputeDiskSize(iinfo.disk_template,
14941                                                  pir["disks"])
14942       instance_data[iinfo.name] = pir
14943
14944     return instance_data
14945
14946   def _AddNewInstance(self):
14947     """Add new instance data to allocator structure.
14948
14949     This in combination with _AllocatorGetClusterData will create the
14950     correct structure needed as input for the allocator.
14951
14952     The checks for the completeness of the opcode must have already been
14953     done.
14954
14955     """
14956     disk_space = _ComputeDiskSize(self.disk_template, self.disks)
14957
14958     if self.disk_template in constants.DTS_INT_MIRROR:
14959       self.required_nodes = 2
14960     else:
14961       self.required_nodes = 1
14962
14963     request = {
14964       "name": self.name,
14965       "disk_template": self.disk_template,
14966       "tags": self.tags,
14967       "os": self.os,
14968       "vcpus": self.vcpus,
14969       "memory": self.memory,
14970       "spindle_use": self.spindle_use,
14971       "disks": self.disks,
14972       "disk_space_total": disk_space,
14973       "nics": self.nics,
14974       "required_nodes": self.required_nodes,
14975       "hypervisor": self.hypervisor,
14976       }
14977
14978     return request
14979
14980   def _AddRelocateInstance(self):
14981     """Add relocate instance data to allocator structure.
14982
14983     This in combination with _IAllocatorGetClusterData will create the
14984     correct structure needed as input for the allocator.
14985
14986     The checks for the completeness of the opcode must have already been
14987     done.
14988
14989     """
14990     instance = self.cfg.GetInstanceInfo(self.name)
14991     if instance is None:
14992       raise errors.ProgrammerError("Unknown instance '%s' passed to"
14993                                    " IAllocator" % self.name)
14994
14995     if instance.disk_template not in constants.DTS_MIRRORED:
14996       raise errors.OpPrereqError("Can't relocate non-mirrored instances",
14997                                  errors.ECODE_INVAL)
14998
14999     if instance.disk_template in constants.DTS_INT_MIRROR and \
15000         len(instance.secondary_nodes) != 1:
15001       raise errors.OpPrereqError("Instance has not exactly one secondary node",
15002                                  errors.ECODE_STATE)
15003
15004     self.required_nodes = 1
15005     disk_sizes = [{constants.IDISK_SIZE: disk.size} for disk in instance.disks]
15006     disk_space = _ComputeDiskSize(instance.disk_template, disk_sizes)
15007
15008     request = {
15009       "name": self.name,
15010       "disk_space_total": disk_space,
15011       "required_nodes": self.required_nodes,
15012       "relocate_from": self.relocate_from,
15013       }
15014     return request
15015
15016   def _AddNodeEvacuate(self):
15017     """Get data for node-evacuate requests.
15018
15019     """
15020     return {
15021       "instances": self.instances,
15022       "evac_mode": self.evac_mode,
15023       }
15024
15025   def _AddChangeGroup(self):
15026     """Get data for node-evacuate requests.
15027
15028     """
15029     return {
15030       "instances": self.instances,
15031       "target_groups": self.target_groups,
15032       }
15033
15034   def _BuildInputData(self, fn, keydata):
15035     """Build input data structures.
15036
15037     """
15038     self._ComputeClusterData()
15039
15040     request = fn()
15041     request["type"] = self.mode
15042     for keyname, keytype in keydata:
15043       if keyname not in request:
15044         raise errors.ProgrammerError("Request parameter %s is missing" %
15045                                      keyname)
15046       val = request[keyname]
15047       if not keytype(val):
15048         raise errors.ProgrammerError("Request parameter %s doesn't pass"
15049                                      " validation, value %s, expected"
15050                                      " type %s" % (keyname, val, keytype))
15051     self.in_data["request"] = request
15052
15053     self.in_text = serializer.Dump(self.in_data)
15054
15055   _STRING_LIST = ht.TListOf(ht.TString)
15056   _JOB_LIST = ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, {
15057      # pylint: disable=E1101
15058      # Class '...' has no 'OP_ID' member
15059      "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID,
15060                           opcodes.OpInstanceMigrate.OP_ID,
15061                           opcodes.OpInstanceReplaceDisks.OP_ID])
15062      })))
15063
15064   _NEVAC_MOVED = \
15065     ht.TListOf(ht.TAnd(ht.TIsLength(3),
15066                        ht.TItems([ht.TNonEmptyString,
15067                                   ht.TNonEmptyString,
15068                                   ht.TListOf(ht.TNonEmptyString),
15069                                  ])))
15070   _NEVAC_FAILED = \
15071     ht.TListOf(ht.TAnd(ht.TIsLength(2),
15072                        ht.TItems([ht.TNonEmptyString,
15073                                   ht.TMaybeString,
15074                                  ])))
15075   _NEVAC_RESULT = ht.TAnd(ht.TIsLength(3),
15076                           ht.TItems([_NEVAC_MOVED, _NEVAC_FAILED, _JOB_LIST]))
15077
15078   _MODE_DATA = {
15079     constants.IALLOCATOR_MODE_ALLOC:
15080       (_AddNewInstance,
15081        [
15082         ("name", ht.TString),
15083         ("memory", ht.TInt),
15084         ("spindle_use", ht.TInt),
15085         ("disks", ht.TListOf(ht.TDict)),
15086         ("disk_template", ht.TString),
15087         ("os", ht.TString),
15088         ("tags", _STRING_LIST),
15089         ("nics", ht.TListOf(ht.TDict)),
15090         ("vcpus", ht.TInt),
15091         ("hypervisor", ht.TString),
15092         ], ht.TList),
15093     constants.IALLOCATOR_MODE_RELOC:
15094       (_AddRelocateInstance,
15095        [("name", ht.TString), ("relocate_from", _STRING_LIST)],
15096        ht.TList),
15097      constants.IALLOCATOR_MODE_NODE_EVAC:
15098       (_AddNodeEvacuate, [
15099         ("instances", _STRING_LIST),
15100         ("evac_mode", ht.TElemOf(constants.IALLOCATOR_NEVAC_MODES)),
15101         ], _NEVAC_RESULT),
15102      constants.IALLOCATOR_MODE_CHG_GROUP:
15103       (_AddChangeGroup, [
15104         ("instances", _STRING_LIST),
15105         ("target_groups", _STRING_LIST),
15106         ], _NEVAC_RESULT),
15107     }
15108
15109   def Run(self, name, validate=True, call_fn=None):
15110     """Run an instance allocator and return the results.
15111
15112     """
15113     if call_fn is None:
15114       call_fn = self.rpc.call_iallocator_runner
15115
15116     result = call_fn(self.cfg.GetMasterNode(), name, self.in_text)
15117     result.Raise("Failure while running the iallocator script")
15118
15119     self.out_text = result.payload
15120     if validate:
15121       self._ValidateResult()
15122
15123   def _ValidateResult(self):
15124     """Process the allocator results.
15125
15126     This will process and if successful save the result in
15127     self.out_data and the other parameters.
15128
15129     """
15130     try:
15131       rdict = serializer.Load(self.out_text)
15132     except Exception, err:
15133       raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
15134
15135     if not isinstance(rdict, dict):
15136       raise errors.OpExecError("Can't parse iallocator results: not a dict")
15137
15138     # TODO: remove backwards compatiblity in later versions
15139     if "nodes" in rdict and "result" not in rdict:
15140       rdict["result"] = rdict["nodes"]
15141       del rdict["nodes"]
15142
15143     for key in "success", "info", "result":
15144       if key not in rdict:
15145         raise errors.OpExecError("Can't parse iallocator results:"
15146                                  " missing key '%s'" % key)
15147       setattr(self, key, rdict[key])
15148
15149     if not self._result_check(self.result):
15150       raise errors.OpExecError("Iallocator returned invalid result,"
15151                                " expected %s, got %s" %
15152                                (self._result_check, self.result),
15153                                errors.ECODE_INVAL)
15154
15155     if self.mode == constants.IALLOCATOR_MODE_RELOC:
15156       assert self.relocate_from is not None
15157       assert self.required_nodes == 1
15158
15159       node2group = dict((name, ndata["group"])
15160                         for (name, ndata) in self.in_data["nodes"].items())
15161
15162       fn = compat.partial(self._NodesToGroups, node2group,
15163                           self.in_data["nodegroups"])
15164
15165       instance = self.cfg.GetInstanceInfo(self.name)
15166       request_groups = fn(self.relocate_from + [instance.primary_node])
15167       result_groups = fn(rdict["result"] + [instance.primary_node])
15168
15169       if self.success and not set(result_groups).issubset(request_groups):
15170         raise errors.OpExecError("Groups of nodes returned by iallocator (%s)"
15171                                  " differ from original groups (%s)" %
15172                                  (utils.CommaJoin(result_groups),
15173                                   utils.CommaJoin(request_groups)))
15174
15175     elif self.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
15176       assert self.evac_mode in constants.IALLOCATOR_NEVAC_MODES
15177
15178     self.out_data = rdict
15179
15180   @staticmethod
15181   def _NodesToGroups(node2group, groups, nodes):
15182     """Returns a list of unique group names for a list of nodes.
15183
15184     @type node2group: dict
15185     @param node2group: Map from node name to group UUID
15186     @type groups: dict
15187     @param groups: Group information
15188     @type nodes: list
15189     @param nodes: Node names
15190
15191     """
15192     result = set()
15193
15194     for node in nodes:
15195       try:
15196         group_uuid = node2group[node]
15197       except KeyError:
15198         # Ignore unknown node
15199         pass
15200       else:
15201         try:
15202           group = groups[group_uuid]
15203         except KeyError:
15204           # Can't find group, let's use UUID
15205           group_name = group_uuid
15206         else:
15207           group_name = group["name"]
15208
15209         result.add(group_name)
15210
15211     return sorted(result)
15212
15213
15214 class LUTestAllocator(NoHooksLU):
15215   """Run allocator tests.
15216
15217   This LU runs the allocator tests
15218
15219   """
15220   def CheckPrereq(self):
15221     """Check prerequisites.
15222
15223     This checks the opcode parameters depending on the director and mode test.
15224
15225     """
15226     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
15227       for attr in ["memory", "disks", "disk_template",
15228                    "os", "tags", "nics", "vcpus"]:
15229         if not hasattr(self.op, attr):
15230           raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
15231                                      attr, errors.ECODE_INVAL)
15232       iname = self.cfg.ExpandInstanceName(self.op.name)
15233       if iname is not None:
15234         raise errors.OpPrereqError("Instance '%s' already in the cluster" %
15235                                    iname, errors.ECODE_EXISTS)
15236       if not isinstance(self.op.nics, list):
15237         raise errors.OpPrereqError("Invalid parameter 'nics'",
15238                                    errors.ECODE_INVAL)
15239       if not isinstance(self.op.disks, list):
15240         raise errors.OpPrereqError("Invalid parameter 'disks'",
15241                                    errors.ECODE_INVAL)
15242       for row in self.op.disks:
15243         if (not isinstance(row, dict) or
15244             constants.IDISK_SIZE not in row or
15245             not isinstance(row[constants.IDISK_SIZE], int) or
15246             constants.IDISK_MODE not in row or
15247             row[constants.IDISK_MODE] not in constants.DISK_ACCESS_SET):
15248           raise errors.OpPrereqError("Invalid contents of the 'disks'"
15249                                      " parameter", errors.ECODE_INVAL)
15250       if self.op.hypervisor is None:
15251         self.op.hypervisor = self.cfg.GetHypervisorType()
15252     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
15253       fname = _ExpandInstanceName(self.cfg, self.op.name)
15254       self.op.name = fname
15255       self.relocate_from = \
15256           list(self.cfg.GetInstanceInfo(fname).secondary_nodes)
15257     elif self.op.mode in (constants.IALLOCATOR_MODE_CHG_GROUP,
15258                           constants.IALLOCATOR_MODE_NODE_EVAC):
15259       if not self.op.instances:
15260         raise errors.OpPrereqError("Missing instances", errors.ECODE_INVAL)
15261       self.op.instances = _GetWantedInstances(self, self.op.instances)
15262     else:
15263       raise errors.OpPrereqError("Invalid test allocator mode '%s'" %
15264                                  self.op.mode, errors.ECODE_INVAL)
15265
15266     if self.op.direction == constants.IALLOCATOR_DIR_OUT:
15267       if self.op.allocator is None:
15268         raise errors.OpPrereqError("Missing allocator name",
15269                                    errors.ECODE_INVAL)
15270     elif self.op.direction != constants.IALLOCATOR_DIR_IN:
15271       raise errors.OpPrereqError("Wrong allocator test '%s'" %
15272                                  self.op.direction, errors.ECODE_INVAL)
15273
15274   def Exec(self, feedback_fn):
15275     """Run the allocator test.
15276
15277     """
15278     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
15279       ial = IAllocator(self.cfg, self.rpc,
15280                        mode=self.op.mode,
15281                        name=self.op.name,
15282                        memory=self.op.memory,
15283                        disks=self.op.disks,
15284                        disk_template=self.op.disk_template,
15285                        os=self.op.os,
15286                        tags=self.op.tags,
15287                        nics=self.op.nics,
15288                        vcpus=self.op.vcpus,
15289                        hypervisor=self.op.hypervisor,
15290                        )
15291     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
15292       ial = IAllocator(self.cfg, self.rpc,
15293                        mode=self.op.mode,
15294                        name=self.op.name,
15295                        relocate_from=list(self.relocate_from),
15296                        )
15297     elif self.op.mode == constants.IALLOCATOR_MODE_CHG_GROUP:
15298       ial = IAllocator(self.cfg, self.rpc,
15299                        mode=self.op.mode,
15300                        instances=self.op.instances,
15301                        target_groups=self.op.target_groups)
15302     elif self.op.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
15303       ial = IAllocator(self.cfg, self.rpc,
15304                        mode=self.op.mode,
15305                        instances=self.op.instances,
15306                        evac_mode=self.op.evac_mode)
15307     else:
15308       raise errors.ProgrammerError("Uncatched mode %s in"
15309                                    " LUTestAllocator.Exec", self.op.mode)
15310
15311     if self.op.direction == constants.IALLOCATOR_DIR_IN:
15312       result = ial.in_text
15313     else:
15314       ial.Run(self.op.allocator, validate=False)
15315       result = ial.out_text
15316     return result
15317
15318
15319 #: Query type implementations
15320 _QUERY_IMPL = {
15321   constants.QR_CLUSTER: _ClusterQuery,
15322   constants.QR_INSTANCE: _InstanceQuery,
15323   constants.QR_NODE: _NodeQuery,
15324   constants.QR_GROUP: _GroupQuery,
15325   constants.QR_OS: _OsQuery,
15326   constants.QR_EXPORT: _ExportQuery,
15327   }
15328
15329 assert set(_QUERY_IMPL.keys()) == constants.QR_VIA_OP
15330
15331
15332 def _GetQueryImplementation(name):
15333   """Returns the implemtnation for a query type.
15334
15335   @param name: Query type, must be one of L{constants.QR_VIA_OP}
15336
15337   """
15338   try:
15339     return _QUERY_IMPL[name]
15340   except KeyError:
15341     raise errors.OpPrereqError("Unknown query resource '%s'" % name,
15342                                errors.ECODE_INVAL)