Merge branch 'stable-2.6-hotplug' into stable-2.6-ippool-hotplug-esi
[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 import ipaddr
44
45 from ganeti import ssh
46 from ganeti import utils
47 from ganeti import errors
48 from ganeti import hypervisor
49 from ganeti import locking
50 from ganeti import constants
51 from ganeti import objects
52 from ganeti import serializer
53 from ganeti import ssconf
54 from ganeti import uidpool
55 from ganeti import compat
56 from ganeti import masterd
57 from ganeti import netutils
58 from ganeti import query
59 from ganeti import qlang
60 from ganeti import opcodes
61 from ganeti import ht
62 from ganeti import rpc
63 from ganeti import runtime
64 from ganeti import network
65
66 import ganeti.masterd.instance # pylint: disable=W0611
67
68
69 #: Size of DRBD meta block device
70 DRBD_META_SIZE = 128
71
72 # States of instance
73 INSTANCE_DOWN = [constants.ADMINST_DOWN]
74 INSTANCE_ONLINE = [constants.ADMINST_DOWN, constants.ADMINST_UP]
75 INSTANCE_NOT_RUNNING = [constants.ADMINST_DOWN, constants.ADMINST_OFFLINE]
76
77 #: Instance status in which an instance can be marked as offline/online
78 CAN_CHANGE_INSTANCE_OFFLINE = (frozenset(INSTANCE_DOWN) | frozenset([
79   constants.ADMINST_OFFLINE,
80   ]))
81
82
83 class ResultWithJobs:
84   """Data container for LU results with jobs.
85
86   Instances of this class returned from L{LogicalUnit.Exec} will be recognized
87   by L{mcpu._ProcessResult}. The latter will then submit the jobs
88   contained in the C{jobs} attribute and include the job IDs in the opcode
89   result.
90
91   """
92   def __init__(self, jobs, **kwargs):
93     """Initializes this class.
94
95     Additional return values can be specified as keyword arguments.
96
97     @type jobs: list of lists of L{opcode.OpCode}
98     @param jobs: A list of lists of opcode objects
99
100     """
101     self.jobs = jobs
102     self.other = kwargs
103
104
105 class LogicalUnit(object):
106   """Logical Unit base class.
107
108   Subclasses must follow these rules:
109     - implement ExpandNames
110     - implement CheckPrereq (except when tasklets are used)
111     - implement Exec (except when tasklets are used)
112     - implement BuildHooksEnv
113     - implement BuildHooksNodes
114     - redefine HPATH and HTYPE
115     - optionally redefine their run requirements:
116         REQ_BGL: the LU needs to hold the Big Ganeti Lock exclusively
117
118   Note that all commands require root permissions.
119
120   @ivar dry_run_result: the value (if any) that will be returned to the caller
121       in dry-run mode (signalled by opcode dry_run parameter)
122
123   """
124   HPATH = None
125   HTYPE = None
126   REQ_BGL = True
127
128   def __init__(self, processor, op, context, rpc_runner):
129     """Constructor for LogicalUnit.
130
131     This needs to be overridden in derived classes in order to check op
132     validity.
133
134     """
135     self.proc = processor
136     self.op = op
137     self.cfg = context.cfg
138     self.glm = context.glm
139     # readability alias
140     self.owned_locks = context.glm.list_owned
141     self.context = context
142     self.rpc = rpc_runner
143     # Dicts used to declare locking needs to mcpu
144     self.needed_locks = None
145     self.share_locks = dict.fromkeys(locking.LEVELS, 0)
146     self.add_locks = {}
147     self.remove_locks = {}
148     # Used to force good behavior when calling helper functions
149     self.recalculate_locks = {}
150     # logging
151     self.Log = processor.Log # pylint: disable=C0103
152     self.LogWarning = processor.LogWarning # pylint: disable=C0103
153     self.LogInfo = processor.LogInfo # pylint: disable=C0103
154     self.LogStep = processor.LogStep # pylint: disable=C0103
155     # support for dry-run
156     self.dry_run_result = None
157     # support for generic debug attribute
158     if (not hasattr(self.op, "debug_level") or
159         not isinstance(self.op.debug_level, int)):
160       self.op.debug_level = 0
161
162     # Tasklets
163     self.tasklets = None
164
165     # Validate opcode parameters and set defaults
166     self.op.Validate(True)
167
168     self.CheckArguments()
169
170   def CheckArguments(self):
171     """Check syntactic validity for the opcode arguments.
172
173     This method is for doing a simple syntactic check and ensure
174     validity of opcode parameters, without any cluster-related
175     checks. While the same can be accomplished in ExpandNames and/or
176     CheckPrereq, doing these separate is better because:
177
178       - ExpandNames is left as as purely a lock-related function
179       - CheckPrereq is run after we have acquired locks (and possible
180         waited for them)
181
182     The function is allowed to change the self.op attribute so that
183     later methods can no longer worry about missing parameters.
184
185     """
186     pass
187
188   def ExpandNames(self):
189     """Expand names for this LU.
190
191     This method is called before starting to execute the opcode, and it should
192     update all the parameters of the opcode to their canonical form (e.g. a
193     short node name must be fully expanded after this method has successfully
194     completed). This way locking, hooks, logging, etc. can work correctly.
195
196     LUs which implement this method must also populate the self.needed_locks
197     member, as a dict with lock levels as keys, and a list of needed lock names
198     as values. Rules:
199
200       - use an empty dict if you don't need any lock
201       - if you don't need any lock at a particular level omit that
202         level (note that in this case C{DeclareLocks} won't be called
203         at all for that level)
204       - if you need locks at a level, but you can't calculate it in
205         this function, initialise that level with an empty list and do
206         further processing in L{LogicalUnit.DeclareLocks} (see that
207         function's docstring)
208       - don't put anything for the BGL level
209       - if you want all locks at a level use L{locking.ALL_SET} as a value
210
211     If you need to share locks (rather than acquire them exclusively) at one
212     level you can modify self.share_locks, setting a true value (usually 1) for
213     that level. By default locks are not shared.
214
215     This function can also define a list of tasklets, which then will be
216     executed in order instead of the usual LU-level CheckPrereq and Exec
217     functions, if those are not defined by the LU.
218
219     Examples::
220
221       # Acquire all nodes and one instance
222       self.needed_locks = {
223         locking.LEVEL_NODE: locking.ALL_SET,
224         locking.LEVEL_INSTANCE: ['instance1.example.com'],
225       }
226       # Acquire just two nodes
227       self.needed_locks = {
228         locking.LEVEL_NODE: ['node1.example.com', 'node2.example.com'],
229       }
230       # Acquire no locks
231       self.needed_locks = {} # No, you can't leave it to the default value None
232
233     """
234     # The implementation of this method is mandatory only if the new LU is
235     # concurrent, so that old LUs don't need to be changed all at the same
236     # time.
237     if self.REQ_BGL:
238       self.needed_locks = {} # Exclusive LUs don't need locks.
239     else:
240       raise NotImplementedError
241
242   def DeclareLocks(self, level):
243     """Declare LU locking needs for a level
244
245     While most LUs can just declare their locking needs at ExpandNames time,
246     sometimes there's the need to calculate some locks after having acquired
247     the ones before. This function is called just before acquiring locks at a
248     particular level, but after acquiring the ones at lower levels, and permits
249     such calculations. It can be used to modify self.needed_locks, and by
250     default it does nothing.
251
252     This function is only called if you have something already set in
253     self.needed_locks for the level.
254
255     @param level: Locking level which is going to be locked
256     @type level: member of L{ganeti.locking.LEVELS}
257
258     """
259
260   def CheckPrereq(self):
261     """Check prerequisites for this LU.
262
263     This method should check that the prerequisites for the execution
264     of this LU are fulfilled. It can do internode communication, but
265     it should be idempotent - no cluster or system changes are
266     allowed.
267
268     The method should raise errors.OpPrereqError in case something is
269     not fulfilled. Its return value is ignored.
270
271     This method should also update all the parameters of the opcode to
272     their canonical form if it hasn't been done by ExpandNames before.
273
274     """
275     if self.tasklets is not None:
276       for (idx, tl) in enumerate(self.tasklets):
277         logging.debug("Checking prerequisites for tasklet %s/%s",
278                       idx + 1, len(self.tasklets))
279         tl.CheckPrereq()
280     else:
281       pass
282
283   def Exec(self, feedback_fn):
284     """Execute the LU.
285
286     This method should implement the actual work. It should raise
287     errors.OpExecError for failures that are somewhat dealt with in
288     code, or expected.
289
290     """
291     if self.tasklets is not None:
292       for (idx, tl) in enumerate(self.tasklets):
293         logging.debug("Executing tasklet %s/%s", idx + 1, len(self.tasklets))
294         tl.Exec(feedback_fn)
295     else:
296       raise NotImplementedError
297
298   def BuildHooksEnv(self):
299     """Build hooks environment for this LU.
300
301     @rtype: dict
302     @return: Dictionary containing the environment that will be used for
303       running the hooks for this LU. The keys of the dict must not be prefixed
304       with "GANETI_"--that'll be added by the hooks runner. The hooks runner
305       will extend the environment with additional variables. If no environment
306       should be defined, an empty dictionary should be returned (not C{None}).
307     @note: If the C{HPATH} attribute of the LU class is C{None}, this function
308       will not be called.
309
310     """
311     raise NotImplementedError
312
313   def BuildHooksNodes(self):
314     """Build list of nodes to run LU's hooks.
315
316     @rtype: tuple; (list, list)
317     @return: Tuple containing a list of node names on which the hook
318       should run before the execution and a list of node names on which the
319       hook should run after the execution. No nodes should be returned as an
320       empty list (and not None).
321     @note: If the C{HPATH} attribute of the LU class is C{None}, this function
322       will not be called.
323
324     """
325     raise NotImplementedError
326
327   def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result):
328     """Notify the LU about the results of its hooks.
329
330     This method is called every time a hooks phase is executed, and notifies
331     the Logical Unit about the hooks' result. The LU can then use it to alter
332     its result based on the hooks.  By default the method does nothing and the
333     previous result is passed back unchanged but any LU can define it if it
334     wants to use the local cluster hook-scripts somehow.
335
336     @param phase: one of L{constants.HOOKS_PHASE_POST} or
337         L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
338     @param hook_results: the results of the multi-node hooks rpc call
339     @param feedback_fn: function used send feedback back to the caller
340     @param lu_result: the previous Exec result this LU had, or None
341         in the PRE phase
342     @return: the new Exec result, based on the previous result
343         and hook results
344
345     """
346     # API must be kept, thus we ignore the unused argument and could
347     # be a function warnings
348     # pylint: disable=W0613,R0201
349     return lu_result
350
351   def _ExpandAndLockInstance(self):
352     """Helper function to expand and lock an instance.
353
354     Many LUs that work on an instance take its name in self.op.instance_name
355     and need to expand it and then declare the expanded name for locking. This
356     function does it, and then updates self.op.instance_name to the expanded
357     name. It also initializes needed_locks as a dict, if this hasn't been done
358     before.
359
360     """
361     if self.needed_locks is None:
362       self.needed_locks = {}
363     else:
364       assert locking.LEVEL_INSTANCE not in self.needed_locks, \
365         "_ExpandAndLockInstance called with instance-level locks set"
366     self.op.instance_name = _ExpandInstanceName(self.cfg,
367                                                 self.op.instance_name)
368     self.needed_locks[locking.LEVEL_INSTANCE] = self.op.instance_name
369
370   def _LockInstancesNodes(self, primary_only=False,
371                           level=locking.LEVEL_NODE):
372     """Helper function to declare instances' nodes for locking.
373
374     This function should be called after locking one or more instances to lock
375     their nodes. Its effect is populating self.needed_locks[locking.LEVEL_NODE]
376     with all primary or secondary nodes for instances already locked and
377     present in self.needed_locks[locking.LEVEL_INSTANCE].
378
379     It should be called from DeclareLocks, and for safety only works if
380     self.recalculate_locks[locking.LEVEL_NODE] is set.
381
382     In the future it may grow parameters to just lock some instance's nodes, or
383     to just lock primaries or secondary nodes, if needed.
384
385     If should be called in DeclareLocks in a way similar to::
386
387       if level == locking.LEVEL_NODE:
388         self._LockInstancesNodes()
389
390     @type primary_only: boolean
391     @param primary_only: only lock primary nodes of locked instances
392     @param level: Which lock level to use for locking nodes
393
394     """
395     assert level in self.recalculate_locks, \
396       "_LockInstancesNodes helper function called with no nodes to recalculate"
397
398     # TODO: check if we're really been called with the instance locks held
399
400     # For now we'll replace self.needed_locks[locking.LEVEL_NODE], but in the
401     # future we might want to have different behaviors depending on the value
402     # of self.recalculate_locks[locking.LEVEL_NODE]
403     wanted_nodes = []
404     locked_i = self.owned_locks(locking.LEVEL_INSTANCE)
405     for _, instance in self.cfg.GetMultiInstanceInfo(locked_i):
406       wanted_nodes.append(instance.primary_node)
407       if not primary_only:
408         wanted_nodes.extend(instance.secondary_nodes)
409
410     if self.recalculate_locks[level] == constants.LOCKS_REPLACE:
411       self.needed_locks[level] = wanted_nodes
412     elif self.recalculate_locks[level] == constants.LOCKS_APPEND:
413       self.needed_locks[level].extend(wanted_nodes)
414     else:
415       raise errors.ProgrammerError("Unknown recalculation mode")
416
417     del self.recalculate_locks[level]
418
419
420 class NoHooksLU(LogicalUnit): # pylint: disable=W0223
421   """Simple LU which runs no hooks.
422
423   This LU is intended as a parent for other LogicalUnits which will
424   run no hooks, in order to reduce duplicate code.
425
426   """
427   HPATH = None
428   HTYPE = None
429
430   def BuildHooksEnv(self):
431     """Empty BuildHooksEnv for NoHooksLu.
432
433     This just raises an error.
434
435     """
436     raise AssertionError("BuildHooksEnv called for NoHooksLUs")
437
438   def BuildHooksNodes(self):
439     """Empty BuildHooksNodes for NoHooksLU.
440
441     """
442     raise AssertionError("BuildHooksNodes called for NoHooksLU")
443
444
445 class Tasklet:
446   """Tasklet base class.
447
448   Tasklets are subcomponents for LUs. LUs can consist entirely of tasklets or
449   they can mix legacy code with tasklets. Locking needs to be done in the LU,
450   tasklets know nothing about locks.
451
452   Subclasses must follow these rules:
453     - Implement CheckPrereq
454     - Implement Exec
455
456   """
457   def __init__(self, lu):
458     self.lu = lu
459
460     # Shortcuts
461     self.cfg = lu.cfg
462     self.rpc = lu.rpc
463
464   def CheckPrereq(self):
465     """Check prerequisites for this tasklets.
466
467     This method should check whether the prerequisites for the execution of
468     this tasklet are fulfilled. It can do internode communication, but it
469     should be idempotent - no cluster or system changes are allowed.
470
471     The method should raise errors.OpPrereqError in case something is not
472     fulfilled. Its return value is ignored.
473
474     This method should also update all parameters to their canonical form if it
475     hasn't been done before.
476
477     """
478     pass
479
480   def Exec(self, feedback_fn):
481     """Execute the tasklet.
482
483     This method should implement the actual work. It should raise
484     errors.OpExecError for failures that are somewhat dealt with in code, or
485     expected.
486
487     """
488     raise NotImplementedError
489
490
491 class _QueryBase:
492   """Base for query utility classes.
493
494   """
495   #: Attribute holding field definitions
496   FIELDS = None
497
498   #: Field to sort by
499   SORT_FIELD = "name"
500
501   def __init__(self, qfilter, fields, use_locking):
502     """Initializes this class.
503
504     """
505     self.use_locking = use_locking
506
507     self.query = query.Query(self.FIELDS, fields, qfilter=qfilter,
508                              namefield=self.SORT_FIELD)
509     self.requested_data = self.query.RequestedData()
510     self.names = self.query.RequestedNames()
511
512     # Sort only if no names were requested
513     self.sort_by_name = not self.names
514
515     self.do_locking = None
516     self.wanted = None
517
518   def _GetNames(self, lu, all_names, lock_level):
519     """Helper function to determine names asked for in the query.
520
521     """
522     if self.do_locking:
523       names = lu.owned_locks(lock_level)
524     else:
525       names = all_names
526
527     if self.wanted == locking.ALL_SET:
528       assert not self.names
529       # caller didn't specify names, so ordering is not important
530       return utils.NiceSort(names)
531
532     # caller specified names and we must keep the same order
533     assert self.names
534     assert not self.do_locking or lu.glm.is_owned(lock_level)
535
536     missing = set(self.wanted).difference(names)
537     if missing:
538       raise errors.OpExecError("Some items were removed before retrieving"
539                                " their data: %s" % missing)
540
541     # Return expanded names
542     return self.wanted
543
544   def ExpandNames(self, lu):
545     """Expand names for this query.
546
547     See L{LogicalUnit.ExpandNames}.
548
549     """
550     raise NotImplementedError()
551
552   def DeclareLocks(self, lu, level):
553     """Declare locks for this query.
554
555     See L{LogicalUnit.DeclareLocks}.
556
557     """
558     raise NotImplementedError()
559
560   def _GetQueryData(self, lu):
561     """Collects all data for this query.
562
563     @return: Query data object
564
565     """
566     raise NotImplementedError()
567
568   def NewStyleQuery(self, lu):
569     """Collect data and execute query.
570
571     """
572     return query.GetQueryResponse(self.query, self._GetQueryData(lu),
573                                   sort_by_name=self.sort_by_name)
574
575   def OldStyleQuery(self, lu):
576     """Collect data and execute query.
577
578     """
579     return self.query.OldStyleQuery(self._GetQueryData(lu),
580                                     sort_by_name=self.sort_by_name)
581
582
583 def _ShareAll():
584   """Returns a dict declaring all lock levels shared.
585
586   """
587   return dict.fromkeys(locking.LEVELS, 1)
588
589
590 def _MakeLegacyNodeInfo(data):
591   """Formats the data returned by L{rpc.RpcRunner.call_node_info}.
592
593   Converts the data into a single dictionary. This is fine for most use cases,
594   but some require information from more than one volume group or hypervisor.
595
596   """
597   (bootid, (vg_info, ), (hv_info, )) = data
598
599   return utils.JoinDisjointDicts(utils.JoinDisjointDicts(vg_info, hv_info), {
600     "bootid": bootid,
601     })
602
603
604 def _AnnotateDiskParams(instance, devs, cfg):
605   """Little helper wrapper to the rpc annotation method.
606
607   @param instance: The instance object
608   @type devs: List of L{objects.Disk}
609   @param devs: The root devices (not any of its children!)
610   @param cfg: The config object
611   @returns The annotated disk copies
612   @see L{rpc.AnnotateDiskParams}
613
614   """
615   return rpc.AnnotateDiskParams(instance.disk_template, devs,
616                                 cfg.GetInstanceDiskParams(instance))
617
618
619 def _CheckInstancesNodeGroups(cfg, instances, owned_groups, owned_nodes,
620                               cur_group_uuid):
621   """Checks if node groups for locked instances are still correct.
622
623   @type cfg: L{config.ConfigWriter}
624   @param cfg: Cluster configuration
625   @type instances: dict; string as key, L{objects.Instance} as value
626   @param instances: Dictionary, instance name as key, instance object as value
627   @type owned_groups: iterable of string
628   @param owned_groups: List of owned groups
629   @type owned_nodes: iterable of string
630   @param owned_nodes: List of owned nodes
631   @type cur_group_uuid: string or None
632   @param cur_group_uuid: Optional group UUID to check against instance's groups
633
634   """
635   for (name, inst) in instances.items():
636     assert owned_nodes.issuperset(inst.all_nodes), \
637       "Instance %s's nodes changed while we kept the lock" % name
638
639     inst_groups = _CheckInstanceNodeGroups(cfg, name, owned_groups)
640
641     assert cur_group_uuid is None or cur_group_uuid in inst_groups, \
642       "Instance %s has no node in group %s" % (name, cur_group_uuid)
643
644
645 def _CheckInstanceNodeGroups(cfg, instance_name, owned_groups):
646   """Checks if the owned node groups are still correct for an instance.
647
648   @type cfg: L{config.ConfigWriter}
649   @param cfg: The cluster configuration
650   @type instance_name: string
651   @param instance_name: Instance name
652   @type owned_groups: set or frozenset
653   @param owned_groups: List of currently owned node groups
654
655   """
656   inst_groups = cfg.GetInstanceNodeGroups(instance_name)
657
658   if not owned_groups.issuperset(inst_groups):
659     raise errors.OpPrereqError("Instance %s's node groups changed since"
660                                " locks were acquired, current groups are"
661                                " are '%s', owning groups '%s'; retry the"
662                                " operation" %
663                                (instance_name,
664                                 utils.CommaJoin(inst_groups),
665                                 utils.CommaJoin(owned_groups)),
666                                errors.ECODE_STATE)
667
668   return inst_groups
669
670
671 def _CheckNodeGroupInstances(cfg, group_uuid, owned_instances):
672   """Checks if the instances in a node group are still correct.
673
674   @type cfg: L{config.ConfigWriter}
675   @param cfg: The cluster configuration
676   @type group_uuid: string
677   @param group_uuid: Node group UUID
678   @type owned_instances: set or frozenset
679   @param owned_instances: List of currently owned instances
680
681   """
682   wanted_instances = cfg.GetNodeGroupInstances(group_uuid)
683   if owned_instances != wanted_instances:
684     raise errors.OpPrereqError("Instances in node group '%s' changed since"
685                                " locks were acquired, wanted '%s', have '%s';"
686                                " retry the operation" %
687                                (group_uuid,
688                                 utils.CommaJoin(wanted_instances),
689                                 utils.CommaJoin(owned_instances)),
690                                errors.ECODE_STATE)
691
692   return wanted_instances
693
694
695 def _SupportsOob(cfg, node):
696   """Tells if node supports OOB.
697
698   @type cfg: L{config.ConfigWriter}
699   @param cfg: The cluster configuration
700   @type node: L{objects.Node}
701   @param node: The node
702   @return: The OOB script if supported or an empty string otherwise
703
704   """
705   return cfg.GetNdParams(node)[constants.ND_OOB_PROGRAM]
706
707
708 def _CopyLockList(names):
709   """Makes a copy of a list of lock names.
710
711   Handles L{locking.ALL_SET} correctly.
712
713   """
714   if names == locking.ALL_SET:
715     return locking.ALL_SET
716   else:
717     return names[:]
718
719
720 def _GetWantedNodes(lu, nodes):
721   """Returns list of checked and expanded node names.
722
723   @type lu: L{LogicalUnit}
724   @param lu: the logical unit on whose behalf we execute
725   @type nodes: list
726   @param nodes: list of node names or None for all nodes
727   @rtype: list
728   @return: the list of nodes, sorted
729   @raise errors.ProgrammerError: if the nodes parameter is wrong type
730
731   """
732   if nodes:
733     return [_ExpandNodeName(lu.cfg, name) for name in nodes]
734
735   return utils.NiceSort(lu.cfg.GetNodeList())
736
737
738 def _GetWantedInstances(lu, instances):
739   """Returns list of checked and expanded instance names.
740
741   @type lu: L{LogicalUnit}
742   @param lu: the logical unit on whose behalf we execute
743   @type instances: list
744   @param instances: list of instance names or None for all instances
745   @rtype: list
746   @return: the list of instances, sorted
747   @raise errors.OpPrereqError: if the instances parameter is wrong type
748   @raise errors.OpPrereqError: if any of the passed instances is not found
749
750   """
751   if instances:
752     wanted = [_ExpandInstanceName(lu.cfg, name) for name in instances]
753   else:
754     wanted = utils.NiceSort(lu.cfg.GetInstanceList())
755   return wanted
756
757
758 def _GetUpdatedParams(old_params, update_dict,
759                       use_default=True, use_none=False):
760   """Return the new version of a parameter dictionary.
761
762   @type old_params: dict
763   @param old_params: old parameters
764   @type update_dict: dict
765   @param update_dict: dict containing new parameter values, or
766       constants.VALUE_DEFAULT to reset the parameter to its default
767       value
768   @param use_default: boolean
769   @type use_default: whether to recognise L{constants.VALUE_DEFAULT}
770       values as 'to be deleted' values
771   @param use_none: boolean
772   @type use_none: whether to recognise C{None} values as 'to be
773       deleted' values
774   @rtype: dict
775   @return: the new parameter dictionary
776
777   """
778   params_copy = copy.deepcopy(old_params)
779   for key, val in update_dict.iteritems():
780     if ((use_default and val == constants.VALUE_DEFAULT) or
781         (use_none and val is None)):
782       try:
783         del params_copy[key]
784       except KeyError:
785         pass
786     else:
787       params_copy[key] = val
788   return params_copy
789
790
791 def _GetUpdatedIPolicy(old_ipolicy, new_ipolicy, group_policy=False):
792   """Return the new version of a instance policy.
793
794   @param group_policy: whether this policy applies to a group and thus
795     we should support removal of policy entries
796
797   """
798   use_none = use_default = group_policy
799   ipolicy = copy.deepcopy(old_ipolicy)
800   for key, value in new_ipolicy.items():
801     if key not in constants.IPOLICY_ALL_KEYS:
802       raise errors.OpPrereqError("Invalid key in new ipolicy: %s" % key,
803                                  errors.ECODE_INVAL)
804     if key in constants.IPOLICY_ISPECS:
805       utils.ForceDictType(value, constants.ISPECS_PARAMETER_TYPES)
806       ipolicy[key] = _GetUpdatedParams(old_ipolicy.get(key, {}), value,
807                                        use_none=use_none,
808                                        use_default=use_default)
809     else:
810       if (not value or value == [constants.VALUE_DEFAULT] or
811           value == constants.VALUE_DEFAULT):
812         if group_policy:
813           del ipolicy[key]
814         else:
815           raise errors.OpPrereqError("Can't unset ipolicy attribute '%s'"
816                                      " on the cluster'" % key,
817                                      errors.ECODE_INVAL)
818       else:
819         if key in constants.IPOLICY_PARAMETERS:
820           # FIXME: we assume all such values are float
821           try:
822             ipolicy[key] = float(value)
823           except (TypeError, ValueError), err:
824             raise errors.OpPrereqError("Invalid value for attribute"
825                                        " '%s': '%s', error: %s" %
826                                        (key, value, err), errors.ECODE_INVAL)
827         else:
828           # FIXME: we assume all others are lists; this should be redone
829           # in a nicer way
830           ipolicy[key] = list(value)
831   try:
832     objects.InstancePolicy.CheckParameterSyntax(ipolicy, not group_policy)
833   except errors.ConfigurationError, err:
834     raise errors.OpPrereqError("Invalid instance policy: %s" % err,
835                                errors.ECODE_INVAL)
836   return ipolicy
837
838
839 def _UpdateAndVerifySubDict(base, updates, type_check):
840   """Updates and verifies a dict with sub dicts of the same type.
841
842   @param base: The dict with the old data
843   @param updates: The dict with the new data
844   @param type_check: Dict suitable to ForceDictType to verify correct types
845   @returns: A new dict with updated and verified values
846
847   """
848   def fn(old, value):
849     new = _GetUpdatedParams(old, value)
850     utils.ForceDictType(new, type_check)
851     return new
852
853   ret = copy.deepcopy(base)
854   ret.update(dict((key, fn(base.get(key, {}), value))
855                   for key, value in updates.items()))
856   return ret
857
858
859 def _MergeAndVerifyHvState(op_input, obj_input):
860   """Combines the hv state from an opcode with the one of the object
861
862   @param op_input: The input dict from the opcode
863   @param obj_input: The input dict from the objects
864   @return: The verified and updated dict
865
866   """
867   if op_input:
868     invalid_hvs = set(op_input) - constants.HYPER_TYPES
869     if invalid_hvs:
870       raise errors.OpPrereqError("Invalid hypervisor(s) in hypervisor state:"
871                                  " %s" % utils.CommaJoin(invalid_hvs),
872                                  errors.ECODE_INVAL)
873     if obj_input is None:
874       obj_input = {}
875     type_check = constants.HVSTS_PARAMETER_TYPES
876     return _UpdateAndVerifySubDict(obj_input, op_input, type_check)
877
878   return None
879
880
881 def _MergeAndVerifyDiskState(op_input, obj_input):
882   """Combines the disk state from an opcode with the one of the object
883
884   @param op_input: The input dict from the opcode
885   @param obj_input: The input dict from the objects
886   @return: The verified and updated dict
887   """
888   if op_input:
889     invalid_dst = set(op_input) - constants.DS_VALID_TYPES
890     if invalid_dst:
891       raise errors.OpPrereqError("Invalid storage type(s) in disk state: %s" %
892                                  utils.CommaJoin(invalid_dst),
893                                  errors.ECODE_INVAL)
894     type_check = constants.DSS_PARAMETER_TYPES
895     if obj_input is None:
896       obj_input = {}
897     return dict((key, _UpdateAndVerifySubDict(obj_input.get(key, {}), value,
898                                               type_check))
899                 for key, value in op_input.items())
900
901   return None
902
903
904 def _ReleaseLocks(lu, level, names=None, keep=None):
905   """Releases locks owned by an LU.
906
907   @type lu: L{LogicalUnit}
908   @param level: Lock level
909   @type names: list or None
910   @param names: Names of locks to release
911   @type keep: list or None
912   @param keep: Names of locks to retain
913
914   """
915   assert not (keep is not None and names is not None), \
916          "Only one of the 'names' and the 'keep' parameters can be given"
917
918   if names is not None:
919     should_release = names.__contains__
920   elif keep:
921     should_release = lambda name: name not in keep
922   else:
923     should_release = None
924
925   owned = lu.owned_locks(level)
926   if not owned:
927     # Not owning any lock at this level, do nothing
928     pass
929
930   elif should_release:
931     retain = []
932     release = []
933
934     # Determine which locks to release
935     for name in owned:
936       if should_release(name):
937         release.append(name)
938       else:
939         retain.append(name)
940
941     assert len(lu.owned_locks(level)) == (len(retain) + len(release))
942
943     # Release just some locks
944     lu.glm.release(level, names=release)
945
946     assert frozenset(lu.owned_locks(level)) == frozenset(retain)
947   else:
948     # Release everything
949     lu.glm.release(level)
950
951     assert not lu.glm.is_owned(level), "No locks should be owned"
952
953
954 def _MapInstanceDisksToNodes(instances):
955   """Creates a map from (node, volume) to instance name.
956
957   @type instances: list of L{objects.Instance}
958   @rtype: dict; tuple of (node name, volume name) as key, instance name as value
959
960   """
961   return dict(((node, vol), inst.name)
962               for inst in instances
963               for (node, vols) in inst.MapLVsByNode().items()
964               for vol in vols)
965
966
967 def _RunPostHook(lu, node_name):
968   """Runs the post-hook for an opcode on a single node.
969
970   """
971   hm = lu.proc.BuildHooksManager(lu)
972   try:
973     hm.RunPhase(constants.HOOKS_PHASE_POST, nodes=[node_name])
974   except Exception, err: # pylint: disable=W0703
975     lu.LogWarning("Errors occurred running hooks on %s: %s" % (node_name, err))
976
977
978 def _CheckOutputFields(static, dynamic, selected):
979   """Checks whether all selected fields are valid.
980
981   @type static: L{utils.FieldSet}
982   @param static: static fields set
983   @type dynamic: L{utils.FieldSet}
984   @param dynamic: dynamic fields set
985
986   """
987   f = utils.FieldSet()
988   f.Extend(static)
989   f.Extend(dynamic)
990
991   delta = f.NonMatching(selected)
992   if delta:
993     raise errors.OpPrereqError("Unknown output fields selected: %s"
994                                % ",".join(delta), errors.ECODE_INVAL)
995
996
997 def _CheckGlobalHvParams(params):
998   """Validates that given hypervisor params are not global ones.
999
1000   This will ensure that instances don't get customised versions of
1001   global params.
1002
1003   """
1004   used_globals = constants.HVC_GLOBALS.intersection(params)
1005   if used_globals:
1006     msg = ("The following hypervisor parameters are global and cannot"
1007            " be customized at instance level, please modify them at"
1008            " cluster level: %s" % utils.CommaJoin(used_globals))
1009     raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
1010
1011
1012 def _CheckNodeOnline(lu, node, msg=None):
1013   """Ensure that a given node is online.
1014
1015   @param lu: the LU on behalf of which we make the check
1016   @param node: the node to check
1017   @param msg: if passed, should be a message to replace the default one
1018   @raise errors.OpPrereqError: if the node is offline
1019
1020   """
1021   if msg is None:
1022     msg = "Can't use offline node"
1023   if lu.cfg.GetNodeInfo(node).offline:
1024     raise errors.OpPrereqError("%s: %s" % (msg, node), errors.ECODE_STATE)
1025
1026
1027 def _CheckNodeNotDrained(lu, node):
1028   """Ensure that a given node is not drained.
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 drained
1033
1034   """
1035   if lu.cfg.GetNodeInfo(node).drained:
1036     raise errors.OpPrereqError("Can't use drained node %s" % node,
1037                                errors.ECODE_STATE)
1038
1039
1040 def _CheckNodeVmCapable(lu, node):
1041   """Ensure that a given node is vm capable.
1042
1043   @param lu: the LU on behalf of which we make the check
1044   @param node: the node to check
1045   @raise errors.OpPrereqError: if the node is not vm capable
1046
1047   """
1048   if not lu.cfg.GetNodeInfo(node).vm_capable:
1049     raise errors.OpPrereqError("Can't use non-vm_capable node %s" % node,
1050                                errors.ECODE_STATE)
1051
1052
1053 def _CheckNodeHasOS(lu, node, os_name, force_variant):
1054   """Ensure that a node supports a given OS.
1055
1056   @param lu: the LU on behalf of which we make the check
1057   @param node: the node to check
1058   @param os_name: the OS to query about
1059   @param force_variant: whether to ignore variant errors
1060   @raise errors.OpPrereqError: if the node is not supporting the OS
1061
1062   """
1063   result = lu.rpc.call_os_get(node, os_name)
1064   result.Raise("OS '%s' not in supported OS list for node %s" %
1065                (os_name, node),
1066                prereq=True, ecode=errors.ECODE_INVAL)
1067   if not force_variant:
1068     _CheckOSVariant(result.payload, os_name)
1069
1070
1071 def _CheckNodeHasSecondaryIP(lu, node, secondary_ip, prereq):
1072   """Ensure that a node has the given secondary ip.
1073
1074   @type lu: L{LogicalUnit}
1075   @param lu: the LU on behalf of which we make the check
1076   @type node: string
1077   @param node: the node to check
1078   @type secondary_ip: string
1079   @param secondary_ip: the ip to check
1080   @type prereq: boolean
1081   @param prereq: whether to throw a prerequisite or an execute error
1082   @raise errors.OpPrereqError: if the node doesn't have the ip, and prereq=True
1083   @raise errors.OpExecError: if the node doesn't have the ip, and prereq=False
1084
1085   """
1086   result = lu.rpc.call_node_has_ip_address(node, secondary_ip)
1087   result.Raise("Failure checking secondary ip on node %s" % node,
1088                prereq=prereq, ecode=errors.ECODE_ENVIRON)
1089   if not result.payload:
1090     msg = ("Node claims it doesn't have the secondary ip you gave (%s),"
1091            " please fix and re-run this command" % secondary_ip)
1092     if prereq:
1093       raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
1094     else:
1095       raise errors.OpExecError(msg)
1096
1097
1098 def _GetClusterDomainSecret():
1099   """Reads the cluster domain secret.
1100
1101   """
1102   return utils.ReadOneLineFile(constants.CLUSTER_DOMAIN_SECRET_FILE,
1103                                strict=True)
1104
1105
1106 def _CheckInstanceState(lu, instance, req_states, msg=None):
1107   """Ensure that an instance is in one of the required states.
1108
1109   @param lu: the LU on behalf of which we make the check
1110   @param instance: the instance to check
1111   @param msg: if passed, should be a message to replace the default one
1112   @raise errors.OpPrereqError: if the instance is not in the required state
1113
1114   """
1115   if msg is None:
1116     msg = "can't use instance from outside %s states" % ", ".join(req_states)
1117   if instance.admin_state not in req_states:
1118     raise errors.OpPrereqError("Instance '%s' is marked to be %s, %s" %
1119                                (instance.name, instance.admin_state, msg),
1120                                errors.ECODE_STATE)
1121
1122   if constants.ADMINST_UP not in req_states:
1123     pnode = instance.primary_node
1124     ins_l = lu.rpc.call_instance_list([pnode], [instance.hypervisor])[pnode]
1125     ins_l.Raise("Can't contact node %s for instance information" % pnode,
1126                 prereq=True, ecode=errors.ECODE_ENVIRON)
1127
1128     if instance.name in ins_l.payload:
1129       raise errors.OpPrereqError("Instance %s is running, %s" %
1130                                  (instance.name, msg), errors.ECODE_STATE)
1131
1132
1133 def _ComputeMinMaxSpec(name, qualifier, ipolicy, value):
1134   """Computes if value is in the desired range.
1135
1136   @param name: name of the parameter for which we perform the check
1137   @param qualifier: a qualifier used in the error message (e.g. 'disk/1',
1138       not just 'disk')
1139   @param ipolicy: dictionary containing min, max and std values
1140   @param value: actual value that we want to use
1141   @return: None or element not meeting the criteria
1142
1143
1144   """
1145   if value in [None, constants.VALUE_AUTO]:
1146     return None
1147   max_v = ipolicy[constants.ISPECS_MAX].get(name, value)
1148   min_v = ipolicy[constants.ISPECS_MIN].get(name, value)
1149   if value > max_v or min_v > value:
1150     if qualifier:
1151       fqn = "%s/%s" % (name, qualifier)
1152     else:
1153       fqn = name
1154     return ("%s value %s is not in range [%s, %s]" %
1155             (fqn, value, min_v, max_v))
1156   return None
1157
1158
1159 def _ComputeIPolicySpecViolation(ipolicy, mem_size, cpu_count, disk_count,
1160                                  nic_count, disk_sizes, spindle_use,
1161                                  _compute_fn=_ComputeMinMaxSpec):
1162   """Verifies ipolicy against provided specs.
1163
1164   @type ipolicy: dict
1165   @param ipolicy: The ipolicy
1166   @type mem_size: int
1167   @param mem_size: The memory size
1168   @type cpu_count: int
1169   @param cpu_count: Used cpu cores
1170   @type disk_count: int
1171   @param disk_count: Number of disks used
1172   @type nic_count: int
1173   @param nic_count: Number of nics used
1174   @type disk_sizes: list of ints
1175   @param disk_sizes: Disk sizes of used disk (len must match C{disk_count})
1176   @type spindle_use: int
1177   @param spindle_use: The number of spindles this instance uses
1178   @param _compute_fn: The compute function (unittest only)
1179   @return: A list of violations, or an empty list of no violations are found
1180
1181   """
1182   assert disk_count == len(disk_sizes)
1183
1184   test_settings = [
1185     (constants.ISPEC_MEM_SIZE, "", mem_size),
1186     (constants.ISPEC_CPU_COUNT, "", cpu_count),
1187     (constants.ISPEC_DISK_COUNT, "", disk_count),
1188     (constants.ISPEC_NIC_COUNT, "", nic_count),
1189     (constants.ISPEC_SPINDLE_USE, "", spindle_use),
1190     ] + [(constants.ISPEC_DISK_SIZE, str(idx), d)
1191          for idx, d in enumerate(disk_sizes)]
1192
1193   return filter(None,
1194                 (_compute_fn(name, qualifier, ipolicy, value)
1195                  for (name, qualifier, value) in test_settings))
1196
1197
1198 def _ComputeIPolicyInstanceViolation(ipolicy, instance,
1199                                      _compute_fn=_ComputeIPolicySpecViolation):
1200   """Compute if instance meets the specs of ipolicy.
1201
1202   @type ipolicy: dict
1203   @param ipolicy: The ipolicy to verify against
1204   @type instance: L{objects.Instance}
1205   @param instance: The instance to verify
1206   @param _compute_fn: The function to verify ipolicy (unittest only)
1207   @see: L{_ComputeIPolicySpecViolation}
1208
1209   """
1210   mem_size = instance.beparams.get(constants.BE_MAXMEM, None)
1211   cpu_count = instance.beparams.get(constants.BE_VCPUS, None)
1212   spindle_use = instance.beparams.get(constants.BE_SPINDLE_USE, None)
1213   disk_count = len(instance.disks)
1214   disk_sizes = [disk.size for disk in instance.disks]
1215   nic_count = len(instance.nics)
1216
1217   return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
1218                      disk_sizes, spindle_use)
1219
1220
1221 def _ComputeIPolicyInstanceSpecViolation(ipolicy, instance_spec,
1222     _compute_fn=_ComputeIPolicySpecViolation):
1223   """Compute if instance specs meets the specs of ipolicy.
1224
1225   @type ipolicy: dict
1226   @param ipolicy: The ipolicy to verify against
1227   @param instance_spec: dict
1228   @param instance_spec: The instance spec to verify
1229   @param _compute_fn: The function to verify ipolicy (unittest only)
1230   @see: L{_ComputeIPolicySpecViolation}
1231
1232   """
1233   mem_size = instance_spec.get(constants.ISPEC_MEM_SIZE, None)
1234   cpu_count = instance_spec.get(constants.ISPEC_CPU_COUNT, None)
1235   disk_count = instance_spec.get(constants.ISPEC_DISK_COUNT, 0)
1236   disk_sizes = instance_spec.get(constants.ISPEC_DISK_SIZE, [])
1237   nic_count = instance_spec.get(constants.ISPEC_NIC_COUNT, 0)
1238   spindle_use = instance_spec.get(constants.ISPEC_SPINDLE_USE, None)
1239
1240   return _compute_fn(ipolicy, mem_size, cpu_count, disk_count, nic_count,
1241                      disk_sizes, spindle_use)
1242
1243
1244 def _ComputeIPolicyNodeViolation(ipolicy, instance, current_group,
1245                                  target_group,
1246                                  _compute_fn=_ComputeIPolicyInstanceViolation):
1247   """Compute if instance meets the specs of the new target group.
1248
1249   @param ipolicy: The ipolicy to verify
1250   @param instance: The instance object to verify
1251   @param current_group: The current group of the instance
1252   @param target_group: The new group of the instance
1253   @param _compute_fn: The function to verify ipolicy (unittest only)
1254   @see: L{_ComputeIPolicySpecViolation}
1255
1256   """
1257   if current_group == target_group:
1258     return []
1259   else:
1260     return _compute_fn(ipolicy, instance)
1261
1262
1263 def _CheckTargetNodeIPolicy(lu, ipolicy, instance, node, ignore=False,
1264                             _compute_fn=_ComputeIPolicyNodeViolation):
1265   """Checks that the target node is correct in terms of instance policy.
1266
1267   @param ipolicy: The ipolicy to verify
1268   @param instance: The instance object to verify
1269   @param node: The new node to relocate
1270   @param ignore: Ignore violations of the ipolicy
1271   @param _compute_fn: The function to verify ipolicy (unittest only)
1272   @see: L{_ComputeIPolicySpecViolation}
1273
1274   """
1275   primary_node = lu.cfg.GetNodeInfo(instance.primary_node)
1276   res = _compute_fn(ipolicy, instance, primary_node.group, node.group)
1277
1278   if res:
1279     msg = ("Instance does not meet target node group's (%s) instance"
1280            " policy: %s") % (node.group, utils.CommaJoin(res))
1281     if ignore:
1282       lu.LogWarning(msg)
1283     else:
1284       raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
1285
1286
1287 def _ComputeNewInstanceViolations(old_ipolicy, new_ipolicy, instances):
1288   """Computes a set of any instances that would violate the new ipolicy.
1289
1290   @param old_ipolicy: The current (still in-place) ipolicy
1291   @param new_ipolicy: The new (to become) ipolicy
1292   @param instances: List of instances to verify
1293   @return: A list of instances which violates the new ipolicy but
1294       did not before
1295
1296   """
1297   return (_ComputeViolatingInstances(new_ipolicy, instances) -
1298           _ComputeViolatingInstances(old_ipolicy, instances))
1299
1300
1301 def _ExpandItemName(fn, name, kind):
1302   """Expand an item name.
1303
1304   @param fn: the function to use for expansion
1305   @param name: requested item name
1306   @param kind: text description ('Node' or 'Instance')
1307   @return: the resolved (full) name
1308   @raise errors.OpPrereqError: if the item is not found
1309
1310   """
1311   full_name = fn(name)
1312   if full_name is None:
1313     raise errors.OpPrereqError("%s '%s' not known" % (kind, name),
1314                                errors.ECODE_NOENT)
1315   return full_name
1316
1317
1318 def _ExpandNodeName(cfg, name):
1319   """Wrapper over L{_ExpandItemName} for nodes."""
1320   return _ExpandItemName(cfg.ExpandNodeName, name, "Node")
1321
1322
1323 def _ExpandInstanceName(cfg, name):
1324   """Wrapper over L{_ExpandItemName} for instance."""
1325   return _ExpandItemName(cfg.ExpandInstanceName, name, "Instance")
1326
1327 def _BuildNetworkHookEnv(name, network, gateway, network6, gateway6,
1328                          network_type, mac_prefix, tags, serial_no):
1329   env = dict()
1330   if name:
1331     env["NETWORK_NAME"] = name
1332   if network:
1333     env["NETWORK_SUBNET"] = network
1334   if gateway:
1335     env["NETWORK_GATEWAY"] = gateway
1336   if network6:
1337     env["NETWORK_SUBNET6"] = network6
1338   if gateway6:
1339     env["NETWORK_GATEWAY6"] = gateway6
1340   if mac_prefix:
1341     env["NETWORK_MAC_PREFIX"] = mac_prefix
1342   if network_type:
1343     env["NETWORK_TYPE"] = network_type
1344   if tags:
1345     env["NETWORK_TAGS"] = " ".join(tags)
1346   if serial_no:
1347     env["NETWORK_SERIAL_NO"] = serial_no
1348
1349   return env
1350
1351
1352 def _BuildNetworkHookEnvByObject(lu, network):
1353   args = {
1354     "name": network.name,
1355     "network": network.network,
1356     "gateway": network.gateway,
1357     "network6": network.network6,
1358     "gateway6": network.gateway6,
1359     "network_type": network.network_type,
1360     "mac_prefix": network.mac_prefix,
1361     "tags": network.tags,
1362     "serial_no": network.serial_no,
1363   }
1364   return _BuildNetworkHookEnv(**args)
1365
1366
1367 def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
1368                           minmem, maxmem, vcpus, nics, disk_template, disks,
1369                           bep, hvp, hypervisor_name, tags, serial_no):
1370   """Builds instance related env variables for hooks
1371
1372   This builds the hook environment from individual variables.
1373
1374   @type name: string
1375   @param name: the name of the instance
1376   @type primary_node: string
1377   @param primary_node: the name of the instance's primary node
1378   @type secondary_nodes: list
1379   @param secondary_nodes: list of secondary nodes as strings
1380   @type os_type: string
1381   @param os_type: the name of the instance's OS
1382   @type status: string
1383   @param status: the desired status of the instance
1384   @type minmem: string
1385   @param minmem: the minimum memory size of the instance
1386   @type maxmem: string
1387   @param maxmem: the maximum memory size of the instance
1388   @type vcpus: string
1389   @param vcpus: the count of VCPUs the instance has
1390   @type nics: list
1391   @param nics: list of tuples (ip, mac, mode, link, network) representing
1392       the NICs the instance has
1393   @type disk_template: string
1394   @param disk_template: the disk template of the instance
1395   @type disks: list
1396   @param disks: the list of (size, mode) pairs
1397   @type bep: dict
1398   @param bep: the backend parameters for the instance
1399   @type hvp: dict
1400   @param hvp: the hypervisor parameters for the instance
1401   @type hypervisor_name: string
1402   @param hypervisor_name: the hypervisor for the instance
1403   @type tags: list
1404   @param tags: list of instance tags as strings
1405   @rtype: dict
1406   @return: the hook environment for this instance
1407
1408   """
1409   env = {
1410     "OP_TARGET": name,
1411     "INSTANCE_NAME": name,
1412     "INSTANCE_PRIMARY": primary_node,
1413     "INSTANCE_SECONDARIES": " ".join(secondary_nodes),
1414     "INSTANCE_OS_TYPE": os_type,
1415     "INSTANCE_STATUS": status,
1416     "INSTANCE_MINMEM": minmem,
1417     "INSTANCE_MAXMEM": maxmem,
1418     # TODO(2.7) remove deprecated "memory" value
1419     "INSTANCE_MEMORY": maxmem,
1420     "INSTANCE_VCPUS": vcpus,
1421     "INSTANCE_DISK_TEMPLATE": disk_template,
1422     "INSTANCE_HYPERVISOR": hypervisor_name,
1423     "INSTANCE_SERIAL_NO": serial_no,
1424   }
1425   if nics:
1426     nic_count = len(nics)
1427     for idx, (ip, mac, mode, link, network, netinfo) in enumerate(nics):
1428       if ip is None:
1429         ip = ""
1430       env["INSTANCE_NIC%d_IP" % idx] = ip
1431       env["INSTANCE_NIC%d_MAC" % idx] = mac
1432       env["INSTANCE_NIC%d_MODE" % idx] = mode
1433       env["INSTANCE_NIC%d_LINK" % idx] = link
1434       if network:
1435         env["INSTANCE_NIC%d_NETWORK" % idx] = network
1436         if netinfo:
1437           nobj = objects.Network.FromDict(netinfo)
1438           if nobj.network:
1439             env["INSTANCE_NIC%d_NETWORK_SUBNET" % idx] = nobj.network
1440           if nobj.gateway:
1441             env["INSTANCE_NIC%d_NETWORK_GATEWAY" % idx] = nobj.gateway
1442           if nobj.network6:
1443             env["INSTANCE_NIC%d_NETWORK_SUBNET6" % idx] = nobj.network6
1444           if nobj.gateway6:
1445             env["INSTANCE_NIC%d_NETWORK_GATEWAY6" % idx] = nobj.gateway6
1446           if nobj.mac_prefix:
1447             env["INSTANCE_NIC%d_NETWORK_MAC_PREFIX" % idx] = nobj.mac_prefix
1448           if nobj.network_type:
1449             env["INSTANCE_NIC%d_NETWORK_TYPE" % idx] = nobj.network_type
1450           if nobj.tags:
1451             env["INSTANCE_NIC%d_NETWORK_TAGS" % idx] = " ".join(nobj.tags)
1452       if mode == constants.NIC_MODE_BRIDGED:
1453         env["INSTANCE_NIC%d_BRIDGE" % idx] = link
1454   else:
1455     nic_count = 0
1456
1457   env["INSTANCE_NIC_COUNT"] = nic_count
1458
1459   if disks:
1460     disk_count = len(disks)
1461     for idx, (size, mode) in enumerate(disks):
1462       env["INSTANCE_DISK%d_SIZE" % idx] = size
1463       env["INSTANCE_DISK%d_MODE" % idx] = mode
1464   else:
1465     disk_count = 0
1466
1467   env["INSTANCE_DISK_COUNT"] = disk_count
1468
1469   if not tags:
1470     tags = []
1471
1472   env["INSTANCE_TAGS"] = " ".join(tags)
1473
1474   for source, kind in [(bep, "BE"), (hvp, "HV")]:
1475     for key, value in source.items():
1476       env["INSTANCE_%s_%s" % (kind, key)] = value
1477
1478   return env
1479
1480 def _NICToTuple(lu, nic):
1481   """Build a tupple of nic information.
1482
1483   @type lu:  L{LogicalUnit}
1484   @param lu: the logical unit on whose behalf we execute
1485   @type nic: L{objects.NIC}
1486   @param nic: nic to convert to hooks tuple
1487
1488   """
1489   cluster = lu.cfg.GetClusterInfo()
1490   ip = nic.ip
1491   mac = nic.mac
1492   filled_params = cluster.SimpleFillNIC(nic.nicparams)
1493   mode = filled_params[constants.NIC_MODE]
1494   link = filled_params[constants.NIC_LINK]
1495   network = nic.network
1496   netinfo = None
1497   if network:
1498     net_uuid = lu.cfg.LookupNetwork(network)
1499     if net_uuid:
1500       nobj = lu.cfg.GetNetwork(net_uuid)
1501       netinfo = objects.Network.ToDict(nobj)
1502   return (ip, mac, mode, link, network, netinfo)
1503
1504 def _NICListToTuple(lu, nics):
1505   """Build a list of nic information tuples.
1506
1507   This list is suitable to be passed to _BuildInstanceHookEnv or as a return
1508   value in LUInstanceQueryData.
1509
1510   @type lu:  L{LogicalUnit}
1511   @param lu: the logical unit on whose behalf we execute
1512   @type nics: list of L{objects.NIC}
1513   @param nics: list of nics to convert to hooks tuples
1514
1515   """
1516   hooks_nics = []
1517   cluster = lu.cfg.GetClusterInfo()
1518   for nic in nics:
1519     hooks_nics.append(_NICToTuple(lu, nic))
1520   return hooks_nics
1521
1522 def _BuildInstanceHookEnvByObject(lu, instance, override=None):
1523   """Builds instance related env variables for hooks from an object.
1524
1525   @type lu: L{LogicalUnit}
1526   @param lu: the logical unit on whose behalf we execute
1527   @type instance: L{objects.Instance}
1528   @param instance: the instance for which we should build the
1529       environment
1530   @type override: dict
1531   @param override: dictionary with key/values that will override
1532       our values
1533   @rtype: dict
1534   @return: the hook environment dictionary
1535
1536   """
1537   cluster = lu.cfg.GetClusterInfo()
1538   bep = cluster.FillBE(instance)
1539   hvp = cluster.FillHV(instance)
1540   args = {
1541     "name": instance.name,
1542     "primary_node": instance.primary_node,
1543     "secondary_nodes": instance.secondary_nodes,
1544     "os_type": instance.os,
1545     "status": instance.admin_state,
1546     "maxmem": bep[constants.BE_MAXMEM],
1547     "minmem": bep[constants.BE_MINMEM],
1548     "vcpus": bep[constants.BE_VCPUS],
1549     "nics": _NICListToTuple(lu, instance.nics),
1550     "disk_template": instance.disk_template,
1551     "disks": [(disk.size, disk.mode) for disk in instance.disks],
1552     "bep": bep,
1553     "hvp": hvp,
1554     "hypervisor_name": instance.hypervisor,
1555     "tags": instance.tags,
1556     "serial_no": instance.serial_no,
1557   }
1558   if override:
1559     args.update(override)
1560   return _BuildInstanceHookEnv(**args) # pylint: disable=W0142
1561
1562
1563 def _AdjustCandidatePool(lu, exceptions):
1564   """Adjust the candidate pool after node operations.
1565
1566   """
1567   mod_list = lu.cfg.MaintainCandidatePool(exceptions)
1568   if mod_list:
1569     lu.LogInfo("Promoted nodes to master candidate role: %s",
1570                utils.CommaJoin(node.name for node in mod_list))
1571     for name in mod_list:
1572       lu.context.ReaddNode(name)
1573   mc_now, mc_max, _ = lu.cfg.GetMasterCandidateStats(exceptions)
1574   if mc_now > mc_max:
1575     lu.LogInfo("Note: more nodes are candidates (%d) than desired (%d)" %
1576                (mc_now, mc_max))
1577
1578
1579 def _DecideSelfPromotion(lu, exceptions=None):
1580   """Decide whether I should promote myself as a master candidate.
1581
1582   """
1583   cp_size = lu.cfg.GetClusterInfo().candidate_pool_size
1584   mc_now, mc_should, _ = lu.cfg.GetMasterCandidateStats(exceptions)
1585   # the new node will increase mc_max with one, so:
1586   mc_should = min(mc_should + 1, cp_size)
1587   return mc_now < mc_should
1588
1589
1590 def _CalculateGroupIPolicy(cluster, group):
1591   """Calculate instance policy for group.
1592
1593   """
1594   return cluster.SimpleFillIPolicy(group.ipolicy)
1595
1596
1597 def _ComputeViolatingInstances(ipolicy, instances):
1598   """Computes a set of instances who violates given ipolicy.
1599
1600   @param ipolicy: The ipolicy to verify
1601   @type instances: object.Instance
1602   @param instances: List of instances to verify
1603   @return: A frozenset of instance names violating the ipolicy
1604
1605   """
1606   return frozenset([inst.name for inst in instances
1607                     if _ComputeIPolicyInstanceViolation(ipolicy, inst)])
1608
1609
1610 def _CheckNicsBridgesExist(lu, target_nics, target_node):
1611   """Check that the brigdes needed by a list of nics exist.
1612
1613   """
1614   cluster = lu.cfg.GetClusterInfo()
1615   paramslist = [cluster.SimpleFillNIC(nic.nicparams) for nic in target_nics]
1616   brlist = [params[constants.NIC_LINK] for params in paramslist
1617             if params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED]
1618   if brlist:
1619     result = lu.rpc.call_bridges_exist(target_node, brlist)
1620     result.Raise("Error checking bridges on destination node '%s'" %
1621                  target_node, prereq=True, ecode=errors.ECODE_ENVIRON)
1622
1623
1624 def _CheckInstanceBridgesExist(lu, instance, node=None):
1625   """Check that the brigdes needed by an instance exist.
1626
1627   """
1628   if node is None:
1629     node = instance.primary_node
1630   _CheckNicsBridgesExist(lu, instance.nics, node)
1631
1632
1633 def _CheckOSVariant(os_obj, name):
1634   """Check whether an OS name conforms to the os variants specification.
1635
1636   @type os_obj: L{objects.OS}
1637   @param os_obj: OS object to check
1638   @type name: string
1639   @param name: OS name passed by the user, to check for validity
1640
1641   """
1642   variant = objects.OS.GetVariant(name)
1643   if not os_obj.supported_variants:
1644     if variant:
1645       raise errors.OpPrereqError("OS '%s' doesn't support variants ('%s'"
1646                                  " passed)" % (os_obj.name, variant),
1647                                  errors.ECODE_INVAL)
1648     return
1649   if not variant:
1650     raise errors.OpPrereqError("OS name must include a variant",
1651                                errors.ECODE_INVAL)
1652
1653   if variant not in os_obj.supported_variants:
1654     raise errors.OpPrereqError("Unsupported OS variant", errors.ECODE_INVAL)
1655
1656
1657 def _GetNodeInstancesInner(cfg, fn):
1658   return [i for i in cfg.GetAllInstancesInfo().values() if fn(i)]
1659
1660
1661 def _GetNodeInstances(cfg, node_name):
1662   """Returns a list of all primary and secondary instances on a node.
1663
1664   """
1665
1666   return _GetNodeInstancesInner(cfg, lambda inst: node_name in inst.all_nodes)
1667
1668
1669 def _GetNodePrimaryInstances(cfg, node_name):
1670   """Returns primary instances on a node.
1671
1672   """
1673   return _GetNodeInstancesInner(cfg,
1674                                 lambda inst: node_name == inst.primary_node)
1675
1676
1677 def _GetNodeSecondaryInstances(cfg, node_name):
1678   """Returns secondary instances on a node.
1679
1680   """
1681   return _GetNodeInstancesInner(cfg,
1682                                 lambda inst: node_name in inst.secondary_nodes)
1683
1684
1685 def _GetStorageTypeArgs(cfg, storage_type):
1686   """Returns the arguments for a storage type.
1687
1688   """
1689   # Special case for file storage
1690   if storage_type == constants.ST_FILE:
1691     # storage.FileStorage wants a list of storage directories
1692     return [[cfg.GetFileStorageDir(), cfg.GetSharedFileStorageDir()]]
1693
1694   return []
1695
1696
1697 def _FindFaultyInstanceDisks(cfg, rpc_runner, instance, node_name, prereq):
1698   faulty = []
1699
1700   for dev in instance.disks:
1701     cfg.SetDiskID(dev, node_name)
1702
1703   result = rpc_runner.call_blockdev_getmirrorstatus(node_name, (instance.disks,
1704                                                                 instance))
1705   result.Raise("Failed to get disk status from node %s" % node_name,
1706                prereq=prereq, ecode=errors.ECODE_ENVIRON)
1707
1708   for idx, bdev_status in enumerate(result.payload):
1709     if bdev_status and bdev_status.ldisk_status == constants.LDS_FAULTY:
1710       faulty.append(idx)
1711
1712   return faulty
1713
1714
1715 def _CheckIAllocatorOrNode(lu, iallocator_slot, node_slot):
1716   """Check the sanity of iallocator and node arguments and use the
1717   cluster-wide iallocator if appropriate.
1718
1719   Check that at most one of (iallocator, node) is specified. If none is
1720   specified, then the LU's opcode's iallocator slot is filled with the
1721   cluster-wide default iallocator.
1722
1723   @type iallocator_slot: string
1724   @param iallocator_slot: the name of the opcode iallocator slot
1725   @type node_slot: string
1726   @param node_slot: the name of the opcode target node slot
1727
1728   """
1729   node = getattr(lu.op, node_slot, None)
1730   iallocator = getattr(lu.op, iallocator_slot, None)
1731
1732   if node is not None and iallocator is not None:
1733     raise errors.OpPrereqError("Do not specify both, iallocator and node",
1734                                errors.ECODE_INVAL)
1735   elif node is None and iallocator is None:
1736     default_iallocator = lu.cfg.GetDefaultIAllocator()
1737     if default_iallocator:
1738       setattr(lu.op, iallocator_slot, default_iallocator)
1739     else:
1740       raise errors.OpPrereqError("No iallocator or node given and no"
1741                                  " cluster-wide default iallocator found;"
1742                                  " please specify either an iallocator or a"
1743                                  " node, or set a cluster-wide default"
1744                                  " iallocator")
1745
1746
1747 def _GetDefaultIAllocator(cfg, iallocator):
1748   """Decides on which iallocator to use.
1749
1750   @type cfg: L{config.ConfigWriter}
1751   @param cfg: Cluster configuration object
1752   @type iallocator: string or None
1753   @param iallocator: Iallocator specified in opcode
1754   @rtype: string
1755   @return: Iallocator name
1756
1757   """
1758   if not iallocator:
1759     # Use default iallocator
1760     iallocator = cfg.GetDefaultIAllocator()
1761
1762   if not iallocator:
1763     raise errors.OpPrereqError("No iallocator was specified, neither in the"
1764                                " opcode nor as a cluster-wide default",
1765                                errors.ECODE_INVAL)
1766
1767   return iallocator
1768
1769
1770 def _InstanceRunning(lu, instance):
1771   """Return True if instance is running else False."""
1772
1773   remote_info = lu.rpc.call_instance_info(instance.primary_node,
1774                                           instance.name,
1775                                           instance.hypervisor)
1776   remote_info.Raise("Error checking node %s" % instance.primary_node)
1777   instance_running = bool(remote_info.payload)
1778   return instance_running
1779
1780
1781 def _CheckHostnameSane(lu, name):
1782   """Ensures that a given hostname resolves to a 'sane' name.
1783
1784   The given name is required to be a prefix of the resolved hostname,
1785   to prevent accidental mismatches.
1786
1787   @param lu: the logical unit on behalf of which we're checking
1788   @param name: the name we should resolve and check
1789   @return: the resolved hostname object
1790
1791   """
1792   hostname = netutils.GetHostname(name=name)
1793   if hostname.name != name:
1794     lu.LogInfo("Resolved given name '%s' to '%s'", name, hostname.name)
1795   if not utils.MatchNameComponent(name, [hostname.name]):
1796     raise errors.OpPrereqError(("Resolved hostname '%s' does not look the"
1797                                 " same as given hostname '%s'") %
1798                                 (hostname.name, name), errors.ECODE_INVAL)
1799   return hostname
1800
1801
1802 class LUClusterPostInit(LogicalUnit):
1803   """Logical unit for running hooks after cluster initialization.
1804
1805   """
1806   HPATH = "cluster-init"
1807   HTYPE = constants.HTYPE_CLUSTER
1808
1809   def BuildHooksEnv(self):
1810     """Build hooks env.
1811
1812     """
1813     return {
1814       "OP_TARGET": self.cfg.GetClusterName(),
1815       }
1816
1817   def BuildHooksNodes(self):
1818     """Build hooks nodes.
1819
1820     """
1821     return ([], [self.cfg.GetMasterNode()])
1822
1823   def Exec(self, feedback_fn):
1824     """Nothing to do.
1825
1826     """
1827     return True
1828
1829
1830 class LUClusterDestroy(LogicalUnit):
1831   """Logical unit for destroying the cluster.
1832
1833   """
1834   HPATH = "cluster-destroy"
1835   HTYPE = constants.HTYPE_CLUSTER
1836
1837   def BuildHooksEnv(self):
1838     """Build hooks env.
1839
1840     """
1841     return {
1842       "OP_TARGET": self.cfg.GetClusterName(),
1843       }
1844
1845   def BuildHooksNodes(self):
1846     """Build hooks nodes.
1847
1848     """
1849     return ([], [])
1850
1851   def CheckPrereq(self):
1852     """Check prerequisites.
1853
1854     This checks whether the cluster is empty.
1855
1856     Any errors are signaled by raising errors.OpPrereqError.
1857
1858     """
1859     master = self.cfg.GetMasterNode()
1860
1861     nodelist = self.cfg.GetNodeList()
1862     if len(nodelist) != 1 or nodelist[0] != master:
1863       raise errors.OpPrereqError("There are still %d node(s) in"
1864                                  " this cluster." % (len(nodelist) - 1),
1865                                  errors.ECODE_INVAL)
1866     instancelist = self.cfg.GetInstanceList()
1867     if instancelist:
1868       raise errors.OpPrereqError("There are still %d instance(s) in"
1869                                  " this cluster." % len(instancelist),
1870                                  errors.ECODE_INVAL)
1871
1872   def Exec(self, feedback_fn):
1873     """Destroys the cluster.
1874
1875     """
1876     master_params = self.cfg.GetMasterNetworkParameters()
1877
1878     # Run post hooks on master node before it's removed
1879     _RunPostHook(self, master_params.name)
1880
1881     ems = self.cfg.GetUseExternalMipScript()
1882     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
1883                                                      master_params, ems)
1884     if result.fail_msg:
1885       self.LogWarning("Error disabling the master IP address: %s",
1886                       result.fail_msg)
1887
1888     return master_params.name
1889
1890
1891 def _VerifyCertificate(filename):
1892   """Verifies a certificate for L{LUClusterVerifyConfig}.
1893
1894   @type filename: string
1895   @param filename: Path to PEM file
1896
1897   """
1898   try:
1899     cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
1900                                            utils.ReadFile(filename))
1901   except Exception, err: # pylint: disable=W0703
1902     return (LUClusterVerifyConfig.ETYPE_ERROR,
1903             "Failed to load X509 certificate %s: %s" % (filename, err))
1904
1905   (errcode, msg) = \
1906     utils.VerifyX509Certificate(cert, constants.SSL_CERT_EXPIRATION_WARN,
1907                                 constants.SSL_CERT_EXPIRATION_ERROR)
1908
1909   if msg:
1910     fnamemsg = "While verifying %s: %s" % (filename, msg)
1911   else:
1912     fnamemsg = None
1913
1914   if errcode is None:
1915     return (None, fnamemsg)
1916   elif errcode == utils.CERT_WARNING:
1917     return (LUClusterVerifyConfig.ETYPE_WARNING, fnamemsg)
1918   elif errcode == utils.CERT_ERROR:
1919     return (LUClusterVerifyConfig.ETYPE_ERROR, fnamemsg)
1920
1921   raise errors.ProgrammerError("Unhandled certificate error code %r" % errcode)
1922
1923
1924 def _GetAllHypervisorParameters(cluster, instances):
1925   """Compute the set of all hypervisor parameters.
1926
1927   @type cluster: L{objects.Cluster}
1928   @param cluster: the cluster object
1929   @param instances: list of L{objects.Instance}
1930   @param instances: additional instances from which to obtain parameters
1931   @rtype: list of (origin, hypervisor, parameters)
1932   @return: a list with all parameters found, indicating the hypervisor they
1933        apply to, and the origin (can be "cluster", "os X", or "instance Y")
1934
1935   """
1936   hvp_data = []
1937
1938   for hv_name in cluster.enabled_hypervisors:
1939     hvp_data.append(("cluster", hv_name, cluster.GetHVDefaults(hv_name)))
1940
1941   for os_name, os_hvp in cluster.os_hvp.items():
1942     for hv_name, hv_params in os_hvp.items():
1943       if hv_params:
1944         full_params = cluster.GetHVDefaults(hv_name, os_name=os_name)
1945         hvp_data.append(("os %s" % os_name, hv_name, full_params))
1946
1947   # TODO: collapse identical parameter values in a single one
1948   for instance in instances:
1949     if instance.hvparams:
1950       hvp_data.append(("instance %s" % instance.name, instance.hypervisor,
1951                        cluster.FillHV(instance)))
1952
1953   return hvp_data
1954
1955
1956 class _VerifyErrors(object):
1957   """Mix-in for cluster/group verify LUs.
1958
1959   It provides _Error and _ErrorIf, and updates the self.bad boolean. (Expects
1960   self.op and self._feedback_fn to be available.)
1961
1962   """
1963
1964   ETYPE_FIELD = "code"
1965   ETYPE_ERROR = "ERROR"
1966   ETYPE_WARNING = "WARNING"
1967
1968   def _Error(self, ecode, item, msg, *args, **kwargs):
1969     """Format an error message.
1970
1971     Based on the opcode's error_codes parameter, either format a
1972     parseable error code, or a simpler error string.
1973
1974     This must be called only from Exec and functions called from Exec.
1975
1976     """
1977     ltype = kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR)
1978     itype, etxt, _ = ecode
1979     # first complete the msg
1980     if args:
1981       msg = msg % args
1982     # then format the whole message
1983     if self.op.error_codes: # This is a mix-in. pylint: disable=E1101
1984       msg = "%s:%s:%s:%s:%s" % (ltype, etxt, itype, item, msg)
1985     else:
1986       if item:
1987         item = " " + item
1988       else:
1989         item = ""
1990       msg = "%s: %s%s: %s" % (ltype, itype, item, msg)
1991     # and finally report it via the feedback_fn
1992     self._feedback_fn("  - %s" % msg) # Mix-in. pylint: disable=E1101
1993
1994   def _ErrorIf(self, cond, ecode, *args, **kwargs):
1995     """Log an error message if the passed condition is True.
1996
1997     """
1998     cond = (bool(cond)
1999             or self.op.debug_simulate_errors) # pylint: disable=E1101
2000
2001     # If the error code is in the list of ignored errors, demote the error to a
2002     # warning
2003     (_, etxt, _) = ecode
2004     if etxt in self.op.ignore_errors:     # pylint: disable=E1101
2005       kwargs[self.ETYPE_FIELD] = self.ETYPE_WARNING
2006
2007     if cond:
2008       self._Error(ecode, *args, **kwargs)
2009
2010     # do not mark the operation as failed for WARN cases only
2011     if kwargs.get(self.ETYPE_FIELD, self.ETYPE_ERROR) == self.ETYPE_ERROR:
2012       self.bad = self.bad or cond
2013
2014
2015 class LUClusterVerify(NoHooksLU):
2016   """Submits all jobs necessary to verify the cluster.
2017
2018   """
2019   REQ_BGL = False
2020
2021   def ExpandNames(self):
2022     self.needed_locks = {}
2023
2024   def Exec(self, feedback_fn):
2025     jobs = []
2026
2027     if self.op.group_name:
2028       groups = [self.op.group_name]
2029       depends_fn = lambda: None
2030     else:
2031       groups = self.cfg.GetNodeGroupList()
2032
2033       # Verify global configuration
2034       jobs.append([
2035         opcodes.OpClusterVerifyConfig(ignore_errors=self.op.ignore_errors)
2036         ])
2037
2038       # Always depend on global verification
2039       depends_fn = lambda: [(-len(jobs), [])]
2040
2041     jobs.extend([opcodes.OpClusterVerifyGroup(group_name=group,
2042                                             ignore_errors=self.op.ignore_errors,
2043                                             depends=depends_fn())]
2044                 for group in groups)
2045
2046     # Fix up all parameters
2047     for op in itertools.chain(*jobs): # pylint: disable=W0142
2048       op.debug_simulate_errors = self.op.debug_simulate_errors
2049       op.verbose = self.op.verbose
2050       op.error_codes = self.op.error_codes
2051       try:
2052         op.skip_checks = self.op.skip_checks
2053       except AttributeError:
2054         assert not isinstance(op, opcodes.OpClusterVerifyGroup)
2055
2056     return ResultWithJobs(jobs)
2057
2058
2059 class LUClusterVerifyConfig(NoHooksLU, _VerifyErrors):
2060   """Verifies the cluster config.
2061
2062   """
2063   REQ_BGL = False
2064
2065   def _VerifyHVP(self, hvp_data):
2066     """Verifies locally the syntax of the hypervisor parameters.
2067
2068     """
2069     for item, hv_name, hv_params in hvp_data:
2070       msg = ("hypervisor %s parameters syntax check (source %s): %%s" %
2071              (item, hv_name))
2072       try:
2073         hv_class = hypervisor.GetHypervisor(hv_name)
2074         utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
2075         hv_class.CheckParameterSyntax(hv_params)
2076       except errors.GenericError, err:
2077         self._ErrorIf(True, constants.CV_ECLUSTERCFG, None, msg % str(err))
2078
2079   def ExpandNames(self):
2080     self.needed_locks = dict.fromkeys(locking.LEVELS, locking.ALL_SET)
2081     self.share_locks = _ShareAll()
2082
2083   def CheckPrereq(self):
2084     """Check prerequisites.
2085
2086     """
2087     # Retrieve all information
2088     self.all_group_info = self.cfg.GetAllNodeGroupsInfo()
2089     self.all_node_info = self.cfg.GetAllNodesInfo()
2090     self.all_inst_info = self.cfg.GetAllInstancesInfo()
2091
2092   def Exec(self, feedback_fn):
2093     """Verify integrity of cluster, performing various test on nodes.
2094
2095     """
2096     self.bad = False
2097     self._feedback_fn = feedback_fn
2098
2099     feedback_fn("* Verifying cluster config")
2100
2101     for msg in self.cfg.VerifyConfig():
2102       self._ErrorIf(True, constants.CV_ECLUSTERCFG, None, msg)
2103
2104     feedback_fn("* Verifying cluster certificate files")
2105
2106     for cert_filename in constants.ALL_CERT_FILES:
2107       (errcode, msg) = _VerifyCertificate(cert_filename)
2108       self._ErrorIf(errcode, constants.CV_ECLUSTERCERT, None, msg, code=errcode)
2109
2110     feedback_fn("* Verifying hypervisor parameters")
2111
2112     self._VerifyHVP(_GetAllHypervisorParameters(self.cfg.GetClusterInfo(),
2113                                                 self.all_inst_info.values()))
2114
2115     feedback_fn("* Verifying all nodes belong to an existing group")
2116
2117     # We do this verification here because, should this bogus circumstance
2118     # occur, it would never be caught by VerifyGroup, which only acts on
2119     # nodes/instances reachable from existing node groups.
2120
2121     dangling_nodes = set(node.name for node in self.all_node_info.values()
2122                          if node.group not in self.all_group_info)
2123
2124     dangling_instances = {}
2125     no_node_instances = []
2126
2127     for inst in self.all_inst_info.values():
2128       if inst.primary_node in dangling_nodes:
2129         dangling_instances.setdefault(inst.primary_node, []).append(inst.name)
2130       elif inst.primary_node not in self.all_node_info:
2131         no_node_instances.append(inst.name)
2132
2133     pretty_dangling = [
2134         "%s (%s)" %
2135         (node.name,
2136          utils.CommaJoin(dangling_instances.get(node.name,
2137                                                 ["no instances"])))
2138         for node in dangling_nodes]
2139
2140     self._ErrorIf(bool(dangling_nodes), constants.CV_ECLUSTERDANGLINGNODES,
2141                   None,
2142                   "the following nodes (and their instances) belong to a non"
2143                   " existing group: %s", utils.CommaJoin(pretty_dangling))
2144
2145     self._ErrorIf(bool(no_node_instances), constants.CV_ECLUSTERDANGLINGINST,
2146                   None,
2147                   "the following instances have a non-existing primary-node:"
2148                   " %s", utils.CommaJoin(no_node_instances))
2149
2150     return not self.bad
2151
2152
2153 class LUClusterVerifyGroup(LogicalUnit, _VerifyErrors):
2154   """Verifies the status of a node group.
2155
2156   """
2157   HPATH = "cluster-verify"
2158   HTYPE = constants.HTYPE_CLUSTER
2159   REQ_BGL = False
2160
2161   _HOOKS_INDENT_RE = re.compile("^", re.M)
2162
2163   class NodeImage(object):
2164     """A class representing the logical and physical status of a node.
2165
2166     @type name: string
2167     @ivar name: the node name to which this object refers
2168     @ivar volumes: a structure as returned from
2169         L{ganeti.backend.GetVolumeList} (runtime)
2170     @ivar instances: a list of running instances (runtime)
2171     @ivar pinst: list of configured primary instances (config)
2172     @ivar sinst: list of configured secondary instances (config)
2173     @ivar sbp: dictionary of {primary-node: list of instances} for all
2174         instances for which this node is secondary (config)
2175     @ivar mfree: free memory, as reported by hypervisor (runtime)
2176     @ivar dfree: free disk, as reported by the node (runtime)
2177     @ivar offline: the offline status (config)
2178     @type rpc_fail: boolean
2179     @ivar rpc_fail: whether the RPC verify call was successfull (overall,
2180         not whether the individual keys were correct) (runtime)
2181     @type lvm_fail: boolean
2182     @ivar lvm_fail: whether the RPC call didn't return valid LVM data
2183     @type hyp_fail: boolean
2184     @ivar hyp_fail: whether the RPC call didn't return the instance list
2185     @type ghost: boolean
2186     @ivar ghost: whether this is a known node or not (config)
2187     @type os_fail: boolean
2188     @ivar os_fail: whether the RPC call didn't return valid OS data
2189     @type oslist: list
2190     @ivar oslist: list of OSes as diagnosed by DiagnoseOS
2191     @type vm_capable: boolean
2192     @ivar vm_capable: whether the node can host instances
2193
2194     """
2195     def __init__(self, offline=False, name=None, vm_capable=True):
2196       self.name = name
2197       self.volumes = {}
2198       self.instances = []
2199       self.pinst = []
2200       self.sinst = []
2201       self.sbp = {}
2202       self.mfree = 0
2203       self.dfree = 0
2204       self.offline = offline
2205       self.vm_capable = vm_capable
2206       self.rpc_fail = False
2207       self.lvm_fail = False
2208       self.hyp_fail = False
2209       self.ghost = False
2210       self.os_fail = False
2211       self.oslist = {}
2212
2213   def ExpandNames(self):
2214     # This raises errors.OpPrereqError on its own:
2215     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
2216
2217     # Get instances in node group; this is unsafe and needs verification later
2218     inst_names = \
2219       self.cfg.GetNodeGroupInstances(self.group_uuid, primary_only=True)
2220
2221     self.needed_locks = {
2222       locking.LEVEL_INSTANCE: inst_names,
2223       locking.LEVEL_NODEGROUP: [self.group_uuid],
2224       locking.LEVEL_NODE: [],
2225       }
2226
2227     self.share_locks = _ShareAll()
2228
2229   def DeclareLocks(self, level):
2230     if level == locking.LEVEL_NODE:
2231       # Get members of node group; this is unsafe and needs verification later
2232       nodes = set(self.cfg.GetNodeGroup(self.group_uuid).members)
2233
2234       all_inst_info = self.cfg.GetAllInstancesInfo()
2235
2236       # In Exec(), we warn about mirrored instances that have primary and
2237       # secondary living in separate node groups. To fully verify that
2238       # volumes for these instances are healthy, we will need to do an
2239       # extra call to their secondaries. We ensure here those nodes will
2240       # be locked.
2241       for inst in self.owned_locks(locking.LEVEL_INSTANCE):
2242         # Important: access only the instances whose lock is owned
2243         if all_inst_info[inst].disk_template in constants.DTS_INT_MIRROR:
2244           nodes.update(all_inst_info[inst].secondary_nodes)
2245
2246       self.needed_locks[locking.LEVEL_NODE] = nodes
2247
2248   def CheckPrereq(self):
2249     assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
2250     self.group_info = self.cfg.GetNodeGroup(self.group_uuid)
2251
2252     group_nodes = set(self.group_info.members)
2253     group_instances = \
2254       self.cfg.GetNodeGroupInstances(self.group_uuid, primary_only=True)
2255
2256     unlocked_nodes = \
2257         group_nodes.difference(self.owned_locks(locking.LEVEL_NODE))
2258
2259     unlocked_instances = \
2260         group_instances.difference(self.owned_locks(locking.LEVEL_INSTANCE))
2261
2262     if unlocked_nodes:
2263       raise errors.OpPrereqError("Missing lock for nodes: %s" %
2264                                  utils.CommaJoin(unlocked_nodes),
2265                                  errors.ECODE_STATE)
2266
2267     if unlocked_instances:
2268       raise errors.OpPrereqError("Missing lock for instances: %s" %
2269                                  utils.CommaJoin(unlocked_instances),
2270                                  errors.ECODE_STATE)
2271
2272     self.all_node_info = self.cfg.GetAllNodesInfo()
2273     self.all_inst_info = self.cfg.GetAllInstancesInfo()
2274
2275     self.my_node_names = utils.NiceSort(group_nodes)
2276     self.my_inst_names = utils.NiceSort(group_instances)
2277
2278     self.my_node_info = dict((name, self.all_node_info[name])
2279                              for name in self.my_node_names)
2280
2281     self.my_inst_info = dict((name, self.all_inst_info[name])
2282                              for name in self.my_inst_names)
2283
2284     # We detect here the nodes that will need the extra RPC calls for verifying
2285     # split LV volumes; they should be locked.
2286     extra_lv_nodes = set()
2287
2288     for inst in self.my_inst_info.values():
2289       if inst.disk_template in constants.DTS_INT_MIRROR:
2290         for nname in inst.all_nodes:
2291           if self.all_node_info[nname].group != self.group_uuid:
2292             extra_lv_nodes.add(nname)
2293
2294     unlocked_lv_nodes = \
2295         extra_lv_nodes.difference(self.owned_locks(locking.LEVEL_NODE))
2296
2297     if unlocked_lv_nodes:
2298       raise errors.OpPrereqError("Missing node locks for LV check: %s" %
2299                                  utils.CommaJoin(unlocked_lv_nodes),
2300                                  errors.ECODE_STATE)
2301     self.extra_lv_nodes = list(extra_lv_nodes)
2302
2303   def _VerifyNode(self, ninfo, nresult):
2304     """Perform some basic validation on data returned from a node.
2305
2306       - check the result data structure is well formed and has all the
2307         mandatory fields
2308       - check ganeti version
2309
2310     @type ninfo: L{objects.Node}
2311     @param ninfo: the node to check
2312     @param nresult: the results from the node
2313     @rtype: boolean
2314     @return: whether overall this call was successful (and we can expect
2315          reasonable values in the respose)
2316
2317     """
2318     node = ninfo.name
2319     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2320
2321     # main result, nresult should be a non-empty dict
2322     test = not nresult or not isinstance(nresult, dict)
2323     _ErrorIf(test, constants.CV_ENODERPC, node,
2324                   "unable to verify node: no data returned")
2325     if test:
2326       return False
2327
2328     # compares ganeti version
2329     local_version = constants.PROTOCOL_VERSION
2330     remote_version = nresult.get("version", None)
2331     test = not (remote_version and
2332                 isinstance(remote_version, (list, tuple)) and
2333                 len(remote_version) == 2)
2334     _ErrorIf(test, constants.CV_ENODERPC, node,
2335              "connection to node returned invalid data")
2336     if test:
2337       return False
2338
2339     test = local_version != remote_version[0]
2340     _ErrorIf(test, constants.CV_ENODEVERSION, node,
2341              "incompatible protocol versions: master %s,"
2342              " node %s", local_version, remote_version[0])
2343     if test:
2344       return False
2345
2346     # node seems compatible, we can actually try to look into its results
2347
2348     # full package version
2349     self._ErrorIf(constants.RELEASE_VERSION != remote_version[1],
2350                   constants.CV_ENODEVERSION, node,
2351                   "software version mismatch: master %s, node %s",
2352                   constants.RELEASE_VERSION, remote_version[1],
2353                   code=self.ETYPE_WARNING)
2354
2355     hyp_result = nresult.get(constants.NV_HYPERVISOR, None)
2356     if ninfo.vm_capable and isinstance(hyp_result, dict):
2357       for hv_name, hv_result in hyp_result.iteritems():
2358         test = hv_result is not None
2359         _ErrorIf(test, constants.CV_ENODEHV, node,
2360                  "hypervisor %s verify failure: '%s'", hv_name, hv_result)
2361
2362     hvp_result = nresult.get(constants.NV_HVPARAMS, None)
2363     if ninfo.vm_capable and isinstance(hvp_result, list):
2364       for item, hv_name, hv_result in hvp_result:
2365         _ErrorIf(True, constants.CV_ENODEHV, node,
2366                  "hypervisor %s parameter verify failure (source %s): %s",
2367                  hv_name, item, hv_result)
2368
2369     test = nresult.get(constants.NV_NODESETUP,
2370                        ["Missing NODESETUP results"])
2371     _ErrorIf(test, constants.CV_ENODESETUP, node, "node setup error: %s",
2372              "; ".join(test))
2373
2374     return True
2375
2376   def _VerifyNodeTime(self, ninfo, nresult,
2377                       nvinfo_starttime, nvinfo_endtime):
2378     """Check the node time.
2379
2380     @type ninfo: L{objects.Node}
2381     @param ninfo: the node to check
2382     @param nresult: the remote results for the node
2383     @param nvinfo_starttime: the start time of the RPC call
2384     @param nvinfo_endtime: the end time of the RPC call
2385
2386     """
2387     node = ninfo.name
2388     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2389
2390     ntime = nresult.get(constants.NV_TIME, None)
2391     try:
2392       ntime_merged = utils.MergeTime(ntime)
2393     except (ValueError, TypeError):
2394       _ErrorIf(True, constants.CV_ENODETIME, node, "Node returned invalid time")
2395       return
2396
2397     if ntime_merged < (nvinfo_starttime - constants.NODE_MAX_CLOCK_SKEW):
2398       ntime_diff = "%.01fs" % abs(nvinfo_starttime - ntime_merged)
2399     elif ntime_merged > (nvinfo_endtime + constants.NODE_MAX_CLOCK_SKEW):
2400       ntime_diff = "%.01fs" % abs(ntime_merged - nvinfo_endtime)
2401     else:
2402       ntime_diff = None
2403
2404     _ErrorIf(ntime_diff is not None, constants.CV_ENODETIME, node,
2405              "Node time diverges by at least %s from master node time",
2406              ntime_diff)
2407
2408   def _VerifyNodeLVM(self, ninfo, nresult, vg_name):
2409     """Check the node LVM results.
2410
2411     @type ninfo: L{objects.Node}
2412     @param ninfo: the node to check
2413     @param nresult: the remote results for the node
2414     @param vg_name: the configured VG name
2415
2416     """
2417     if vg_name is None:
2418       return
2419
2420     node = ninfo.name
2421     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2422
2423     # checks vg existence and size > 20G
2424     vglist = nresult.get(constants.NV_VGLIST, None)
2425     test = not vglist
2426     _ErrorIf(test, constants.CV_ENODELVM, node, "unable to check volume groups")
2427     if not test:
2428       vgstatus = utils.CheckVolumeGroupSize(vglist, vg_name,
2429                                             constants.MIN_VG_SIZE)
2430       _ErrorIf(vgstatus, constants.CV_ENODELVM, node, vgstatus)
2431
2432     # check pv names
2433     pvlist = nresult.get(constants.NV_PVLIST, None)
2434     test = pvlist is None
2435     _ErrorIf(test, constants.CV_ENODELVM, node, "Can't get PV list from node")
2436     if not test:
2437       # check that ':' is not present in PV names, since it's a
2438       # special character for lvcreate (denotes the range of PEs to
2439       # use on the PV)
2440       for _, pvname, owner_vg in pvlist:
2441         test = ":" in pvname
2442         _ErrorIf(test, constants.CV_ENODELVM, node,
2443                  "Invalid character ':' in PV '%s' of VG '%s'",
2444                  pvname, owner_vg)
2445
2446   def _VerifyNodeBridges(self, ninfo, nresult, bridges):
2447     """Check the node bridges.
2448
2449     @type ninfo: L{objects.Node}
2450     @param ninfo: the node to check
2451     @param nresult: the remote results for the node
2452     @param bridges: the expected list of bridges
2453
2454     """
2455     if not bridges:
2456       return
2457
2458     node = ninfo.name
2459     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2460
2461     missing = nresult.get(constants.NV_BRIDGES, None)
2462     test = not isinstance(missing, list)
2463     _ErrorIf(test, constants.CV_ENODENET, node,
2464              "did not return valid bridge information")
2465     if not test:
2466       _ErrorIf(bool(missing), constants.CV_ENODENET, node,
2467                "missing bridges: %s" % utils.CommaJoin(sorted(missing)))
2468
2469   def _VerifyNodeUserScripts(self, ninfo, nresult):
2470     """Check the results of user scripts presence and executability on the node
2471
2472     @type ninfo: L{objects.Node}
2473     @param ninfo: the node to check
2474     @param nresult: the remote results for the node
2475
2476     """
2477     node = ninfo.name
2478
2479     test = not constants.NV_USERSCRIPTS in nresult
2480     self._ErrorIf(test, constants.CV_ENODEUSERSCRIPTS, node,
2481                   "did not return user scripts information")
2482
2483     broken_scripts = nresult.get(constants.NV_USERSCRIPTS, None)
2484     if not test:
2485       self._ErrorIf(broken_scripts, constants.CV_ENODEUSERSCRIPTS, node,
2486                     "user scripts not present or not executable: %s" %
2487                     utils.CommaJoin(sorted(broken_scripts)))
2488
2489   def _VerifyNodeNetwork(self, ninfo, nresult):
2490     """Check the node network connectivity results.
2491
2492     @type ninfo: L{objects.Node}
2493     @param ninfo: the node to check
2494     @param nresult: the remote results for the node
2495
2496     """
2497     node = ninfo.name
2498     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2499
2500     test = constants.NV_NODELIST not in nresult
2501     _ErrorIf(test, constants.CV_ENODESSH, node,
2502              "node hasn't returned node ssh connectivity data")
2503     if not test:
2504       if nresult[constants.NV_NODELIST]:
2505         for a_node, a_msg in nresult[constants.NV_NODELIST].items():
2506           _ErrorIf(True, constants.CV_ENODESSH, node,
2507                    "ssh communication with node '%s': %s", a_node, a_msg)
2508
2509     test = constants.NV_NODENETTEST not in nresult
2510     _ErrorIf(test, constants.CV_ENODENET, node,
2511              "node hasn't returned node tcp connectivity data")
2512     if not test:
2513       if nresult[constants.NV_NODENETTEST]:
2514         nlist = utils.NiceSort(nresult[constants.NV_NODENETTEST].keys())
2515         for anode in nlist:
2516           _ErrorIf(True, constants.CV_ENODENET, node,
2517                    "tcp communication with node '%s': %s",
2518                    anode, nresult[constants.NV_NODENETTEST][anode])
2519
2520     test = constants.NV_MASTERIP not in nresult
2521     _ErrorIf(test, constants.CV_ENODENET, node,
2522              "node hasn't returned node master IP reachability data")
2523     if not test:
2524       if not nresult[constants.NV_MASTERIP]:
2525         if node == self.master_node:
2526           msg = "the master node cannot reach the master IP (not configured?)"
2527         else:
2528           msg = "cannot reach the master IP"
2529         _ErrorIf(True, constants.CV_ENODENET, node, msg)
2530
2531   def _VerifyInstance(self, instance, instanceconfig, node_image,
2532                       diskstatus):
2533     """Verify an instance.
2534
2535     This function checks to see if the required block devices are
2536     available on the instance's node.
2537
2538     """
2539     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2540     node_current = instanceconfig.primary_node
2541
2542     node_vol_should = {}
2543     instanceconfig.MapLVsByNode(node_vol_should)
2544
2545     ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(), self.group_info)
2546     err = _ComputeIPolicyInstanceViolation(ipolicy, instanceconfig)
2547     _ErrorIf(err, constants.CV_EINSTANCEPOLICY, instance, utils.CommaJoin(err))
2548
2549     for node in node_vol_should:
2550       n_img = node_image[node]
2551       if n_img.offline or n_img.rpc_fail or n_img.lvm_fail:
2552         # ignore missing volumes on offline or broken nodes
2553         continue
2554       for volume in node_vol_should[node]:
2555         test = volume not in n_img.volumes
2556         _ErrorIf(test, constants.CV_EINSTANCEMISSINGDISK, instance,
2557                  "volume %s missing on node %s", volume, node)
2558
2559     if instanceconfig.admin_state == constants.ADMINST_UP:
2560       pri_img = node_image[node_current]
2561       test = instance not in pri_img.instances and not pri_img.offline
2562       _ErrorIf(test, constants.CV_EINSTANCEDOWN, instance,
2563                "instance not running on its primary node %s",
2564                node_current)
2565
2566     diskdata = [(nname, success, status, idx)
2567                 for (nname, disks) in diskstatus.items()
2568                 for idx, (success, status) in enumerate(disks)]
2569
2570     for nname, success, bdev_status, idx in diskdata:
2571       # the 'ghost node' construction in Exec() ensures that we have a
2572       # node here
2573       snode = node_image[nname]
2574       bad_snode = snode.ghost or snode.offline
2575       _ErrorIf(instanceconfig.admin_state == constants.ADMINST_UP and
2576                not success and not bad_snode,
2577                constants.CV_EINSTANCEFAULTYDISK, instance,
2578                "couldn't retrieve status for disk/%s on %s: %s",
2579                idx, nname, bdev_status)
2580       _ErrorIf((instanceconfig.admin_state == constants.ADMINST_UP and
2581                 success and bdev_status.ldisk_status == constants.LDS_FAULTY),
2582                constants.CV_EINSTANCEFAULTYDISK, instance,
2583                "disk/%s on %s is faulty", idx, nname)
2584
2585   def _VerifyOrphanVolumes(self, node_vol_should, node_image, reserved):
2586     """Verify if there are any unknown volumes in the cluster.
2587
2588     The .os, .swap and backup volumes are ignored. All other volumes are
2589     reported as unknown.
2590
2591     @type reserved: L{ganeti.utils.FieldSet}
2592     @param reserved: a FieldSet of reserved volume names
2593
2594     """
2595     for node, n_img in node_image.items():
2596       if (n_img.offline or n_img.rpc_fail or n_img.lvm_fail or
2597           self.all_node_info[node].group != self.group_uuid):
2598         # skip non-healthy nodes
2599         continue
2600       for volume in n_img.volumes:
2601         test = ((node not in node_vol_should or
2602                 volume not in node_vol_should[node]) and
2603                 not reserved.Matches(volume))
2604         self._ErrorIf(test, constants.CV_ENODEORPHANLV, node,
2605                       "volume %s is unknown", volume)
2606
2607   def _VerifyNPlusOneMemory(self, node_image, instance_cfg):
2608     """Verify N+1 Memory Resilience.
2609
2610     Check that if one single node dies we can still start all the
2611     instances it was primary for.
2612
2613     """
2614     cluster_info = self.cfg.GetClusterInfo()
2615     for node, n_img in node_image.items():
2616       # This code checks that every node which is now listed as
2617       # secondary has enough memory to host all instances it is
2618       # supposed to should a single other node in the cluster fail.
2619       # FIXME: not ready for failover to an arbitrary node
2620       # FIXME: does not support file-backed instances
2621       # WARNING: we currently take into account down instances as well
2622       # as up ones, considering that even if they're down someone
2623       # might want to start them even in the event of a node failure.
2624       if n_img.offline or self.all_node_info[node].group != self.group_uuid:
2625         # we're skipping nodes marked offline and nodes in other groups from
2626         # the N+1 warning, since most likely we don't have good memory
2627         # infromation from them; we already list instances living on such
2628         # nodes, and that's enough warning
2629         continue
2630       #TODO(dynmem): also consider ballooning out other instances
2631       for prinode, instances in n_img.sbp.items():
2632         needed_mem = 0
2633         for instance in instances:
2634           bep = cluster_info.FillBE(instance_cfg[instance])
2635           if bep[constants.BE_AUTO_BALANCE]:
2636             needed_mem += bep[constants.BE_MINMEM]
2637         test = n_img.mfree < needed_mem
2638         self._ErrorIf(test, constants.CV_ENODEN1, node,
2639                       "not enough memory to accomodate instance failovers"
2640                       " should node %s fail (%dMiB needed, %dMiB available)",
2641                       prinode, needed_mem, n_img.mfree)
2642
2643   @classmethod
2644   def _VerifyFiles(cls, errorif, nodeinfo, master_node, all_nvinfo,
2645                    (files_all, files_opt, files_mc, files_vm)):
2646     """Verifies file checksums collected from all nodes.
2647
2648     @param errorif: Callback for reporting errors
2649     @param nodeinfo: List of L{objects.Node} objects
2650     @param master_node: Name of master node
2651     @param all_nvinfo: RPC results
2652
2653     """
2654     # Define functions determining which nodes to consider for a file
2655     files2nodefn = [
2656       (files_all, None),
2657       (files_mc, lambda node: (node.master_candidate or
2658                                node.name == master_node)),
2659       (files_vm, lambda node: node.vm_capable),
2660       ]
2661
2662     # Build mapping from filename to list of nodes which should have the file
2663     nodefiles = {}
2664     for (files, fn) in files2nodefn:
2665       if fn is None:
2666         filenodes = nodeinfo
2667       else:
2668         filenodes = filter(fn, nodeinfo)
2669       nodefiles.update((filename,
2670                         frozenset(map(operator.attrgetter("name"), filenodes)))
2671                        for filename in files)
2672
2673     assert set(nodefiles) == (files_all | files_mc | files_vm)
2674
2675     fileinfo = dict((filename, {}) for filename in nodefiles)
2676     ignore_nodes = set()
2677
2678     for node in nodeinfo:
2679       if node.offline:
2680         ignore_nodes.add(node.name)
2681         continue
2682
2683       nresult = all_nvinfo[node.name]
2684
2685       if nresult.fail_msg or not nresult.payload:
2686         node_files = None
2687       else:
2688         node_files = nresult.payload.get(constants.NV_FILELIST, None)
2689
2690       test = not (node_files and isinstance(node_files, dict))
2691       errorif(test, constants.CV_ENODEFILECHECK, node.name,
2692               "Node did not return file checksum data")
2693       if test:
2694         ignore_nodes.add(node.name)
2695         continue
2696
2697       # Build per-checksum mapping from filename to nodes having it
2698       for (filename, checksum) in node_files.items():
2699         assert filename in nodefiles
2700         fileinfo[filename].setdefault(checksum, set()).add(node.name)
2701
2702     for (filename, checksums) in fileinfo.items():
2703       assert compat.all(len(i) > 10 for i in checksums), "Invalid checksum"
2704
2705       # Nodes having the file
2706       with_file = frozenset(node_name
2707                             for nodes in fileinfo[filename].values()
2708                             for node_name in nodes) - ignore_nodes
2709
2710       expected_nodes = nodefiles[filename] - ignore_nodes
2711
2712       # Nodes missing file
2713       missing_file = expected_nodes - with_file
2714
2715       if filename in files_opt:
2716         # All or no nodes
2717         errorif(missing_file and missing_file != expected_nodes,
2718                 constants.CV_ECLUSTERFILECHECK, None,
2719                 "File %s is optional, but it must exist on all or no"
2720                 " nodes (not found on %s)",
2721                 filename, utils.CommaJoin(utils.NiceSort(missing_file)))
2722       else:
2723         errorif(missing_file, constants.CV_ECLUSTERFILECHECK, None,
2724                 "File %s is missing from node(s) %s", filename,
2725                 utils.CommaJoin(utils.NiceSort(missing_file)))
2726
2727         # Warn if a node has a file it shouldn't
2728         unexpected = with_file - expected_nodes
2729         errorif(unexpected,
2730                 constants.CV_ECLUSTERFILECHECK, None,
2731                 "File %s should not exist on node(s) %s",
2732                 filename, utils.CommaJoin(utils.NiceSort(unexpected)))
2733
2734       # See if there are multiple versions of the file
2735       test = len(checksums) > 1
2736       if test:
2737         variants = ["variant %s on %s" %
2738                     (idx + 1, utils.CommaJoin(utils.NiceSort(nodes)))
2739                     for (idx, (checksum, nodes)) in
2740                       enumerate(sorted(checksums.items()))]
2741       else:
2742         variants = []
2743
2744       errorif(test, constants.CV_ECLUSTERFILECHECK, None,
2745               "File %s found with %s different checksums (%s)",
2746               filename, len(checksums), "; ".join(variants))
2747
2748   def _VerifyNodeDrbd(self, ninfo, nresult, instanceinfo, drbd_helper,
2749                       drbd_map):
2750     """Verifies and the node DRBD status.
2751
2752     @type ninfo: L{objects.Node}
2753     @param ninfo: the node to check
2754     @param nresult: the remote results for the node
2755     @param instanceinfo: the dict of instances
2756     @param drbd_helper: the configured DRBD usermode helper
2757     @param drbd_map: the DRBD map as returned by
2758         L{ganeti.config.ConfigWriter.ComputeDRBDMap}
2759
2760     """
2761     node = ninfo.name
2762     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2763
2764     if drbd_helper:
2765       helper_result = nresult.get(constants.NV_DRBDHELPER, None)
2766       test = (helper_result == None)
2767       _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2768                "no drbd usermode helper returned")
2769       if helper_result:
2770         status, payload = helper_result
2771         test = not status
2772         _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2773                  "drbd usermode helper check unsuccessful: %s", payload)
2774         test = status and (payload != drbd_helper)
2775         _ErrorIf(test, constants.CV_ENODEDRBDHELPER, node,
2776                  "wrong drbd usermode helper: %s", payload)
2777
2778     # compute the DRBD minors
2779     node_drbd = {}
2780     for minor, instance in drbd_map[node].items():
2781       test = instance not in instanceinfo
2782       _ErrorIf(test, constants.CV_ECLUSTERCFG, None,
2783                "ghost instance '%s' in temporary DRBD map", instance)
2784         # ghost instance should not be running, but otherwise we
2785         # don't give double warnings (both ghost instance and
2786         # unallocated minor in use)
2787       if test:
2788         node_drbd[minor] = (instance, False)
2789       else:
2790         instance = instanceinfo[instance]
2791         node_drbd[minor] = (instance.name,
2792                             instance.admin_state == constants.ADMINST_UP)
2793
2794     # and now check them
2795     used_minors = nresult.get(constants.NV_DRBDLIST, [])
2796     test = not isinstance(used_minors, (tuple, list))
2797     _ErrorIf(test, constants.CV_ENODEDRBD, node,
2798              "cannot parse drbd status file: %s", str(used_minors))
2799     if test:
2800       # we cannot check drbd status
2801       return
2802
2803     for minor, (iname, must_exist) in node_drbd.items():
2804       test = minor not in used_minors and must_exist
2805       _ErrorIf(test, constants.CV_ENODEDRBD, node,
2806                "drbd minor %d of instance %s is not active", minor, iname)
2807     for minor in used_minors:
2808       test = minor not in node_drbd
2809       _ErrorIf(test, constants.CV_ENODEDRBD, node,
2810                "unallocated drbd minor %d is in use", minor)
2811
2812   def _UpdateNodeOS(self, ninfo, nresult, nimg):
2813     """Builds the node OS structures.
2814
2815     @type ninfo: L{objects.Node}
2816     @param ninfo: the node to check
2817     @param nresult: the remote results for the node
2818     @param nimg: the node image object
2819
2820     """
2821     node = ninfo.name
2822     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2823
2824     remote_os = nresult.get(constants.NV_OSLIST, None)
2825     test = (not isinstance(remote_os, list) or
2826             not compat.all(isinstance(v, list) and len(v) == 7
2827                            for v in remote_os))
2828
2829     _ErrorIf(test, constants.CV_ENODEOS, node,
2830              "node hasn't returned valid OS data")
2831
2832     nimg.os_fail = test
2833
2834     if test:
2835       return
2836
2837     os_dict = {}
2838
2839     for (name, os_path, status, diagnose,
2840          variants, parameters, api_ver) in nresult[constants.NV_OSLIST]:
2841
2842       if name not in os_dict:
2843         os_dict[name] = []
2844
2845       # parameters is a list of lists instead of list of tuples due to
2846       # JSON lacking a real tuple type, fix it:
2847       parameters = [tuple(v) for v in parameters]
2848       os_dict[name].append((os_path, status, diagnose,
2849                             set(variants), set(parameters), set(api_ver)))
2850
2851     nimg.oslist = os_dict
2852
2853   def _VerifyNodeOS(self, ninfo, nimg, base):
2854     """Verifies the node OS list.
2855
2856     @type ninfo: L{objects.Node}
2857     @param ninfo: the node to check
2858     @param nimg: the node image object
2859     @param base: the 'template' node we match against (e.g. from the master)
2860
2861     """
2862     node = ninfo.name
2863     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2864
2865     assert not nimg.os_fail, "Entered _VerifyNodeOS with failed OS rpc?"
2866
2867     beautify_params = lambda l: ["%s: %s" % (k, v) for (k, v) in l]
2868     for os_name, os_data in nimg.oslist.items():
2869       assert os_data, "Empty OS status for OS %s?!" % os_name
2870       f_path, f_status, f_diag, f_var, f_param, f_api = os_data[0]
2871       _ErrorIf(not f_status, constants.CV_ENODEOS, node,
2872                "Invalid OS %s (located at %s): %s", os_name, f_path, f_diag)
2873       _ErrorIf(len(os_data) > 1, constants.CV_ENODEOS, node,
2874                "OS '%s' has multiple entries (first one shadows the rest): %s",
2875                os_name, utils.CommaJoin([v[0] for v in os_data]))
2876       # comparisons with the 'base' image
2877       test = os_name not in base.oslist
2878       _ErrorIf(test, constants.CV_ENODEOS, node,
2879                "Extra OS %s not present on reference node (%s)",
2880                os_name, base.name)
2881       if test:
2882         continue
2883       assert base.oslist[os_name], "Base node has empty OS status?"
2884       _, b_status, _, b_var, b_param, b_api = base.oslist[os_name][0]
2885       if not b_status:
2886         # base OS is invalid, skipping
2887         continue
2888       for kind, a, b in [("API version", f_api, b_api),
2889                          ("variants list", f_var, b_var),
2890                          ("parameters", beautify_params(f_param),
2891                           beautify_params(b_param))]:
2892         _ErrorIf(a != b, constants.CV_ENODEOS, node,
2893                  "OS %s for %s differs from reference node %s: [%s] vs. [%s]",
2894                  kind, os_name, base.name,
2895                  utils.CommaJoin(sorted(a)), utils.CommaJoin(sorted(b)))
2896
2897     # check any missing OSes
2898     missing = set(base.oslist.keys()).difference(nimg.oslist.keys())
2899     _ErrorIf(missing, constants.CV_ENODEOS, node,
2900              "OSes present on reference node %s but missing on this node: %s",
2901              base.name, utils.CommaJoin(missing))
2902
2903   def _VerifyOob(self, ninfo, nresult):
2904     """Verifies out of band functionality of a node.
2905
2906     @type ninfo: L{objects.Node}
2907     @param ninfo: the node to check
2908     @param nresult: the remote results for the node
2909
2910     """
2911     node = ninfo.name
2912     # We just have to verify the paths on master and/or master candidates
2913     # as the oob helper is invoked on the master
2914     if ((ninfo.master_candidate or ninfo.master_capable) and
2915         constants.NV_OOB_PATHS in nresult):
2916       for path_result in nresult[constants.NV_OOB_PATHS]:
2917         self._ErrorIf(path_result, constants.CV_ENODEOOBPATH, node, path_result)
2918
2919   def _UpdateNodeVolumes(self, ninfo, nresult, nimg, vg_name):
2920     """Verifies and updates the node volume data.
2921
2922     This function will update a L{NodeImage}'s internal structures
2923     with data from the remote call.
2924
2925     @type ninfo: L{objects.Node}
2926     @param ninfo: the node to check
2927     @param nresult: the remote results for the node
2928     @param nimg: the node image object
2929     @param vg_name: the configured VG name
2930
2931     """
2932     node = ninfo.name
2933     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2934
2935     nimg.lvm_fail = True
2936     lvdata = nresult.get(constants.NV_LVLIST, "Missing LV data")
2937     if vg_name is None:
2938       pass
2939     elif isinstance(lvdata, basestring):
2940       _ErrorIf(True, constants.CV_ENODELVM, node, "LVM problem on node: %s",
2941                utils.SafeEncode(lvdata))
2942     elif not isinstance(lvdata, dict):
2943       _ErrorIf(True, constants.CV_ENODELVM, node,
2944                "rpc call to node failed (lvlist)")
2945     else:
2946       nimg.volumes = lvdata
2947       nimg.lvm_fail = False
2948
2949   def _UpdateNodeInstances(self, ninfo, nresult, nimg):
2950     """Verifies and updates the node instance list.
2951
2952     If the listing was successful, then updates this node's instance
2953     list. Otherwise, it marks the RPC call as failed for the instance
2954     list key.
2955
2956     @type ninfo: L{objects.Node}
2957     @param ninfo: the node to check
2958     @param nresult: the remote results for the node
2959     @param nimg: the node image object
2960
2961     """
2962     idata = nresult.get(constants.NV_INSTANCELIST, None)
2963     test = not isinstance(idata, list)
2964     self._ErrorIf(test, constants.CV_ENODEHV, ninfo.name,
2965                   "rpc call to node failed (instancelist): %s",
2966                   utils.SafeEncode(str(idata)))
2967     if test:
2968       nimg.hyp_fail = True
2969     else:
2970       nimg.instances = idata
2971
2972   def _UpdateNodeInfo(self, ninfo, nresult, nimg, vg_name):
2973     """Verifies and computes a node information map
2974
2975     @type ninfo: L{objects.Node}
2976     @param ninfo: the node to check
2977     @param nresult: the remote results for the node
2978     @param nimg: the node image object
2979     @param vg_name: the configured VG name
2980
2981     """
2982     node = ninfo.name
2983     _ErrorIf = self._ErrorIf # pylint: disable=C0103
2984
2985     # try to read free memory (from the hypervisor)
2986     hv_info = nresult.get(constants.NV_HVINFO, None)
2987     test = not isinstance(hv_info, dict) or "memory_free" not in hv_info
2988     _ErrorIf(test, constants.CV_ENODEHV, node,
2989              "rpc call to node failed (hvinfo)")
2990     if not test:
2991       try:
2992         nimg.mfree = int(hv_info["memory_free"])
2993       except (ValueError, TypeError):
2994         _ErrorIf(True, constants.CV_ENODERPC, node,
2995                  "node returned invalid nodeinfo, check hypervisor")
2996
2997     # FIXME: devise a free space model for file based instances as well
2998     if vg_name is not None:
2999       test = (constants.NV_VGLIST not in nresult or
3000               vg_name not in nresult[constants.NV_VGLIST])
3001       _ErrorIf(test, constants.CV_ENODELVM, node,
3002                "node didn't return data for the volume group '%s'"
3003                " - it is either missing or broken", vg_name)
3004       if not test:
3005         try:
3006           nimg.dfree = int(nresult[constants.NV_VGLIST][vg_name])
3007         except (ValueError, TypeError):
3008           _ErrorIf(True, constants.CV_ENODERPC, node,
3009                    "node returned invalid LVM info, check LVM status")
3010
3011   def _CollectDiskInfo(self, nodelist, node_image, instanceinfo):
3012     """Gets per-disk status information for all instances.
3013
3014     @type nodelist: list of strings
3015     @param nodelist: Node names
3016     @type node_image: dict of (name, L{objects.Node})
3017     @param node_image: Node objects
3018     @type instanceinfo: dict of (name, L{objects.Instance})
3019     @param instanceinfo: Instance objects
3020     @rtype: {instance: {node: [(succes, payload)]}}
3021     @return: a dictionary of per-instance dictionaries with nodes as
3022         keys and disk information as values; the disk information is a
3023         list of tuples (success, payload)
3024
3025     """
3026     _ErrorIf = self._ErrorIf # pylint: disable=C0103
3027
3028     node_disks = {}
3029     node_disks_devonly = {}
3030     diskless_instances = set()
3031     diskless = constants.DT_DISKLESS
3032
3033     for nname in nodelist:
3034       node_instances = list(itertools.chain(node_image[nname].pinst,
3035                                             node_image[nname].sinst))
3036       diskless_instances.update(inst for inst in node_instances
3037                                 if instanceinfo[inst].disk_template == diskless)
3038       disks = [(inst, disk)
3039                for inst in node_instances
3040                for disk in instanceinfo[inst].disks]
3041
3042       if not disks:
3043         # No need to collect data
3044         continue
3045
3046       node_disks[nname] = disks
3047
3048       # _AnnotateDiskParams makes already copies of the disks
3049       devonly = []
3050       for (inst, dev) in disks:
3051         (anno_disk,) = _AnnotateDiskParams(instanceinfo[inst], [dev], self.cfg)
3052         self.cfg.SetDiskID(anno_disk, nname)
3053         devonly.append(anno_disk)
3054
3055       node_disks_devonly[nname] = devonly
3056
3057     assert len(node_disks) == len(node_disks_devonly)
3058
3059     # Collect data from all nodes with disks
3060     result = self.rpc.call_blockdev_getmirrorstatus_multi(node_disks.keys(),
3061                                                           node_disks_devonly)
3062
3063     assert len(result) == len(node_disks)
3064
3065     instdisk = {}
3066
3067     for (nname, nres) in result.items():
3068       disks = node_disks[nname]
3069
3070       if nres.offline:
3071         # No data from this node
3072         data = len(disks) * [(False, "node offline")]
3073       else:
3074         msg = nres.fail_msg
3075         _ErrorIf(msg, constants.CV_ENODERPC, nname,
3076                  "while getting disk information: %s", msg)
3077         if msg:
3078           # No data from this node
3079           data = len(disks) * [(False, msg)]
3080         else:
3081           data = []
3082           for idx, i in enumerate(nres.payload):
3083             if isinstance(i, (tuple, list)) and len(i) == 2:
3084               data.append(i)
3085             else:
3086               logging.warning("Invalid result from node %s, entry %d: %s",
3087                               nname, idx, i)
3088               data.append((False, "Invalid result from the remote node"))
3089
3090       for ((inst, _), status) in zip(disks, data):
3091         instdisk.setdefault(inst, {}).setdefault(nname, []).append(status)
3092
3093     # Add empty entries for diskless instances.
3094     for inst in diskless_instances:
3095       assert inst not in instdisk
3096       instdisk[inst] = {}
3097
3098     assert compat.all(len(statuses) == len(instanceinfo[inst].disks) and
3099                       len(nnames) <= len(instanceinfo[inst].all_nodes) and
3100                       compat.all(isinstance(s, (tuple, list)) and
3101                                  len(s) == 2 for s in statuses)
3102                       for inst, nnames in instdisk.items()
3103                       for nname, statuses in nnames.items())
3104     assert set(instdisk) == set(instanceinfo), "instdisk consistency failure"
3105
3106     return instdisk
3107
3108   @staticmethod
3109   def _SshNodeSelector(group_uuid, all_nodes):
3110     """Create endless iterators for all potential SSH check hosts.
3111
3112     """
3113     nodes = [node for node in all_nodes
3114              if (node.group != group_uuid and
3115                  not node.offline)]
3116     keyfunc = operator.attrgetter("group")
3117
3118     return map(itertools.cycle,
3119                [sorted(map(operator.attrgetter("name"), names))
3120                 for _, names in itertools.groupby(sorted(nodes, key=keyfunc),
3121                                                   keyfunc)])
3122
3123   @classmethod
3124   def _SelectSshCheckNodes(cls, group_nodes, group_uuid, all_nodes):
3125     """Choose which nodes should talk to which other nodes.
3126
3127     We will make nodes contact all nodes in their group, and one node from
3128     every other group.
3129
3130     @warning: This algorithm has a known issue if one node group is much
3131       smaller than others (e.g. just one node). In such a case all other
3132       nodes will talk to the single node.
3133
3134     """
3135     online_nodes = sorted(node.name for node in group_nodes if not node.offline)
3136     sel = cls._SshNodeSelector(group_uuid, all_nodes)
3137
3138     return (online_nodes,
3139             dict((name, sorted([i.next() for i in sel]))
3140                  for name in online_nodes))
3141
3142   def BuildHooksEnv(self):
3143     """Build hooks env.
3144
3145     Cluster-Verify hooks just ran in the post phase and their failure makes
3146     the output be logged in the verify output and the verification to fail.
3147
3148     """
3149     env = {
3150       "CLUSTER_TAGS": " ".join(self.cfg.GetClusterInfo().GetTags())
3151       }
3152
3153     env.update(("NODE_TAGS_%s" % node.name, " ".join(node.GetTags()))
3154                for node in self.my_node_info.values())
3155
3156     return env
3157
3158   def BuildHooksNodes(self):
3159     """Build hooks nodes.
3160
3161     """
3162     return ([], self.my_node_names)
3163
3164   def Exec(self, feedback_fn):
3165     """Verify integrity of the node group, performing various test on nodes.
3166
3167     """
3168     # This method has too many local variables. pylint: disable=R0914
3169     feedback_fn("* Verifying group '%s'" % self.group_info.name)
3170
3171     if not self.my_node_names:
3172       # empty node group
3173       feedback_fn("* Empty node group, skipping verification")
3174       return True
3175
3176     self.bad = False
3177     _ErrorIf = self._ErrorIf # pylint: disable=C0103
3178     verbose = self.op.verbose
3179     self._feedback_fn = feedback_fn
3180
3181     vg_name = self.cfg.GetVGName()
3182     drbd_helper = self.cfg.GetDRBDHelper()
3183     cluster = self.cfg.GetClusterInfo()
3184     groupinfo = self.cfg.GetAllNodeGroupsInfo()
3185     hypervisors = cluster.enabled_hypervisors
3186     node_data_list = [self.my_node_info[name] for name in self.my_node_names]
3187
3188     i_non_redundant = [] # Non redundant instances
3189     i_non_a_balanced = [] # Non auto-balanced instances
3190     i_offline = 0 # Count of offline instances
3191     n_offline = 0 # Count of offline nodes
3192     n_drained = 0 # Count of nodes being drained
3193     node_vol_should = {}
3194
3195     # FIXME: verify OS list
3196
3197     # File verification
3198     filemap = _ComputeAncillaryFiles(cluster, False)
3199
3200     # do local checksums
3201     master_node = self.master_node = self.cfg.GetMasterNode()
3202     master_ip = self.cfg.GetMasterIP()
3203
3204     feedback_fn("* Gathering data (%d nodes)" % len(self.my_node_names))
3205
3206     user_scripts = []
3207     if self.cfg.GetUseExternalMipScript():
3208       user_scripts.append(constants.EXTERNAL_MASTER_SETUP_SCRIPT)
3209
3210     node_verify_param = {
3211       constants.NV_FILELIST:
3212         utils.UniqueSequence(filename
3213                              for files in filemap
3214                              for filename in files),
3215       constants.NV_NODELIST:
3216         self._SelectSshCheckNodes(node_data_list, self.group_uuid,
3217                                   self.all_node_info.values()),
3218       constants.NV_HYPERVISOR: hypervisors,
3219       constants.NV_HVPARAMS:
3220         _GetAllHypervisorParameters(cluster, self.all_inst_info.values()),
3221       constants.NV_NODENETTEST: [(node.name, node.primary_ip, node.secondary_ip)
3222                                  for node in node_data_list
3223                                  if not node.offline],
3224       constants.NV_INSTANCELIST: hypervisors,
3225       constants.NV_VERSION: None,
3226       constants.NV_HVINFO: self.cfg.GetHypervisorType(),
3227       constants.NV_NODESETUP: None,
3228       constants.NV_TIME: None,
3229       constants.NV_MASTERIP: (master_node, master_ip),
3230       constants.NV_OSLIST: None,
3231       constants.NV_VMNODES: self.cfg.GetNonVmCapableNodeList(),
3232       constants.NV_USERSCRIPTS: user_scripts,
3233       }
3234
3235     if vg_name is not None:
3236       node_verify_param[constants.NV_VGLIST] = None
3237       node_verify_param[constants.NV_LVLIST] = vg_name
3238       node_verify_param[constants.NV_PVLIST] = [vg_name]
3239
3240     if drbd_helper:
3241       node_verify_param[constants.NV_DRBDLIST] = None
3242       node_verify_param[constants.NV_DRBDHELPER] = drbd_helper
3243
3244     # bridge checks
3245     # FIXME: this needs to be changed per node-group, not cluster-wide
3246     bridges = set()
3247     default_nicpp = cluster.nicparams[constants.PP_DEFAULT]
3248     if default_nicpp[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
3249       bridges.add(default_nicpp[constants.NIC_LINK])
3250     for instance in self.my_inst_info.values():
3251       for nic in instance.nics:
3252         full_nic = cluster.SimpleFillNIC(nic.nicparams)
3253         if full_nic[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
3254           bridges.add(full_nic[constants.NIC_LINK])
3255
3256     if bridges:
3257       node_verify_param[constants.NV_BRIDGES] = list(bridges)
3258
3259     # Build our expected cluster state
3260     node_image = dict((node.name, self.NodeImage(offline=node.offline,
3261                                                  name=node.name,
3262                                                  vm_capable=node.vm_capable))
3263                       for node in node_data_list)
3264
3265     # Gather OOB paths
3266     oob_paths = []
3267     for node in self.all_node_info.values():
3268       path = _SupportsOob(self.cfg, node)
3269       if path and path not in oob_paths:
3270         oob_paths.append(path)
3271
3272     if oob_paths:
3273       node_verify_param[constants.NV_OOB_PATHS] = oob_paths
3274
3275     for instance in self.my_inst_names:
3276       inst_config = self.my_inst_info[instance]
3277       if inst_config.admin_state == constants.ADMINST_OFFLINE:
3278         i_offline += 1
3279
3280       for nname in inst_config.all_nodes:
3281         if nname not in node_image:
3282           gnode = self.NodeImage(name=nname)
3283           gnode.ghost = (nname not in self.all_node_info)
3284           node_image[nname] = gnode
3285
3286       inst_config.MapLVsByNode(node_vol_should)
3287
3288       pnode = inst_config.primary_node
3289       node_image[pnode].pinst.append(instance)
3290
3291       for snode in inst_config.secondary_nodes:
3292         nimg = node_image[snode]
3293         nimg.sinst.append(instance)
3294         if pnode not in nimg.sbp:
3295           nimg.sbp[pnode] = []
3296         nimg.sbp[pnode].append(instance)
3297
3298     # At this point, we have the in-memory data structures complete,
3299     # except for the runtime information, which we'll gather next
3300
3301     # Due to the way our RPC system works, exact response times cannot be
3302     # guaranteed (e.g. a broken node could run into a timeout). By keeping the
3303     # time before and after executing the request, we can at least have a time
3304     # window.
3305     nvinfo_starttime = time.time()
3306     all_nvinfo = self.rpc.call_node_verify(self.my_node_names,
3307                                            node_verify_param,
3308                                            self.cfg.GetClusterName())
3309     nvinfo_endtime = time.time()
3310
3311     if self.extra_lv_nodes and vg_name is not None:
3312       extra_lv_nvinfo = \
3313           self.rpc.call_node_verify(self.extra_lv_nodes,
3314                                     {constants.NV_LVLIST: vg_name},
3315                                     self.cfg.GetClusterName())
3316     else:
3317       extra_lv_nvinfo = {}
3318
3319     all_drbd_map = self.cfg.ComputeDRBDMap()
3320
3321     feedback_fn("* Gathering disk information (%s nodes)" %
3322                 len(self.my_node_names))
3323     instdisk = self._CollectDiskInfo(self.my_node_names, node_image,
3324                                      self.my_inst_info)
3325
3326     feedback_fn("* Verifying configuration file consistency")
3327
3328     # If not all nodes are being checked, we need to make sure the master node
3329     # and a non-checked vm_capable node are in the list.
3330     absent_nodes = set(self.all_node_info).difference(self.my_node_info)
3331     if absent_nodes:
3332       vf_nvinfo = all_nvinfo.copy()
3333       vf_node_info = list(self.my_node_info.values())
3334       additional_nodes = []
3335       if master_node not in self.my_node_info:
3336         additional_nodes.append(master_node)
3337         vf_node_info.append(self.all_node_info[master_node])
3338       # Add the first vm_capable node we find which is not included,
3339       # excluding the master node (which we already have)
3340       for node in absent_nodes:
3341         nodeinfo = self.all_node_info[node]
3342         if (nodeinfo.vm_capable and not nodeinfo.offline and
3343             node != master_node):
3344           additional_nodes.append(node)
3345           vf_node_info.append(self.all_node_info[node])
3346           break
3347       key = constants.NV_FILELIST
3348       vf_nvinfo.update(self.rpc.call_node_verify(additional_nodes,
3349                                                  {key: node_verify_param[key]},
3350                                                  self.cfg.GetClusterName()))
3351     else:
3352       vf_nvinfo = all_nvinfo
3353       vf_node_info = self.my_node_info.values()
3354
3355     self._VerifyFiles(_ErrorIf, vf_node_info, master_node, vf_nvinfo, filemap)
3356
3357     feedback_fn("* Verifying node status")
3358
3359     refos_img = None
3360
3361     for node_i in node_data_list:
3362       node = node_i.name
3363       nimg = node_image[node]
3364
3365       if node_i.offline:
3366         if verbose:
3367           feedback_fn("* Skipping offline node %s" % (node,))
3368         n_offline += 1
3369         continue
3370
3371       if node == master_node:
3372         ntype = "master"
3373       elif node_i.master_candidate:
3374         ntype = "master candidate"
3375       elif node_i.drained:
3376         ntype = "drained"
3377         n_drained += 1
3378       else:
3379         ntype = "regular"
3380       if verbose:
3381         feedback_fn("* Verifying node %s (%s)" % (node, ntype))
3382
3383       msg = all_nvinfo[node].fail_msg
3384       _ErrorIf(msg, constants.CV_ENODERPC, node, "while contacting node: %s",
3385                msg)
3386       if msg:
3387         nimg.rpc_fail = True
3388         continue
3389
3390       nresult = all_nvinfo[node].payload
3391
3392       nimg.call_ok = self._VerifyNode(node_i, nresult)
3393       self._VerifyNodeTime(node_i, nresult, nvinfo_starttime, nvinfo_endtime)
3394       self._VerifyNodeNetwork(node_i, nresult)
3395       self._VerifyNodeUserScripts(node_i, nresult)
3396       self._VerifyOob(node_i, nresult)
3397
3398       if nimg.vm_capable:
3399         self._VerifyNodeLVM(node_i, nresult, vg_name)
3400         self._VerifyNodeDrbd(node_i, nresult, self.all_inst_info, drbd_helper,
3401                              all_drbd_map)
3402
3403         self._UpdateNodeVolumes(node_i, nresult, nimg, vg_name)
3404         self._UpdateNodeInstances(node_i, nresult, nimg)
3405         self._UpdateNodeInfo(node_i, nresult, nimg, vg_name)
3406         self._UpdateNodeOS(node_i, nresult, nimg)
3407
3408         if not nimg.os_fail:
3409           if refos_img is None:
3410             refos_img = nimg
3411           self._VerifyNodeOS(node_i, nimg, refos_img)
3412         self._VerifyNodeBridges(node_i, nresult, bridges)
3413
3414         # Check whether all running instancies are primary for the node. (This
3415         # can no longer be done from _VerifyInstance below, since some of the
3416         # wrong instances could be from other node groups.)
3417         non_primary_inst = set(nimg.instances).difference(nimg.pinst)
3418
3419         for inst in non_primary_inst:
3420           test = inst in self.all_inst_info
3421           _ErrorIf(test, constants.CV_EINSTANCEWRONGNODE, inst,
3422                    "instance should not run on node %s", node_i.name)
3423           _ErrorIf(not test, constants.CV_ENODEORPHANINSTANCE, node_i.name,
3424                    "node is running unknown instance %s", inst)
3425
3426     for node, result in extra_lv_nvinfo.items():
3427       self._UpdateNodeVolumes(self.all_node_info[node], result.payload,
3428                               node_image[node], vg_name)
3429
3430     feedback_fn("* Verifying instance status")
3431     for instance in self.my_inst_names:
3432       if verbose:
3433         feedback_fn("* Verifying instance %s" % instance)
3434       inst_config = self.my_inst_info[instance]
3435       self._VerifyInstance(instance, inst_config, node_image,
3436                            instdisk[instance])
3437       inst_nodes_offline = []
3438
3439       pnode = inst_config.primary_node
3440       pnode_img = node_image[pnode]
3441       _ErrorIf(pnode_img.rpc_fail and not pnode_img.offline,
3442                constants.CV_ENODERPC, pnode, "instance %s, connection to"
3443                " primary node failed", instance)
3444
3445       _ErrorIf(inst_config.admin_state == constants.ADMINST_UP and
3446                pnode_img.offline,
3447                constants.CV_EINSTANCEBADNODE, instance,
3448                "instance is marked as running and lives on offline node %s",
3449                inst_config.primary_node)
3450
3451       # If the instance is non-redundant we cannot survive losing its primary
3452       # node, so we are not N+1 compliant.
3453       if inst_config.disk_template not in constants.DTS_MIRRORED:
3454         i_non_redundant.append(instance)
3455
3456       _ErrorIf(len(inst_config.secondary_nodes) > 1,
3457                constants.CV_EINSTANCELAYOUT,
3458                instance, "instance has multiple secondary nodes: %s",
3459                utils.CommaJoin(inst_config.secondary_nodes),
3460                code=self.ETYPE_WARNING)
3461
3462       if inst_config.disk_template in constants.DTS_INT_MIRROR:
3463         pnode = inst_config.primary_node
3464         instance_nodes = utils.NiceSort(inst_config.all_nodes)
3465         instance_groups = {}
3466
3467         for node in instance_nodes:
3468           instance_groups.setdefault(self.all_node_info[node].group,
3469                                      []).append(node)
3470
3471         pretty_list = [
3472           "%s (group %s)" % (utils.CommaJoin(nodes), groupinfo[group].name)
3473           # Sort so that we always list the primary node first.
3474           for group, nodes in sorted(instance_groups.items(),
3475                                      key=lambda (_, nodes): pnode in nodes,
3476                                      reverse=True)]
3477
3478         self._ErrorIf(len(instance_groups) > 1,
3479                       constants.CV_EINSTANCESPLITGROUPS,
3480                       instance, "instance has primary and secondary nodes in"
3481                       " different groups: %s", utils.CommaJoin(pretty_list),
3482                       code=self.ETYPE_WARNING)
3483
3484       if not cluster.FillBE(inst_config)[constants.BE_AUTO_BALANCE]:
3485         i_non_a_balanced.append(instance)
3486
3487       for snode in inst_config.secondary_nodes:
3488         s_img = node_image[snode]
3489         _ErrorIf(s_img.rpc_fail and not s_img.offline, constants.CV_ENODERPC,
3490                  snode, "instance %s, connection to secondary node failed",
3491                  instance)
3492
3493         if s_img.offline:
3494           inst_nodes_offline.append(snode)
3495
3496       # warn that the instance lives on offline nodes
3497       _ErrorIf(inst_nodes_offline, constants.CV_EINSTANCEBADNODE, instance,
3498                "instance has offline secondary node(s) %s",
3499                utils.CommaJoin(inst_nodes_offline))
3500       # ... or ghost/non-vm_capable nodes
3501       for node in inst_config.all_nodes:
3502         _ErrorIf(node_image[node].ghost, constants.CV_EINSTANCEBADNODE,
3503                  instance, "instance lives on ghost node %s", node)
3504         _ErrorIf(not node_image[node].vm_capable, constants.CV_EINSTANCEBADNODE,
3505                  instance, "instance lives on non-vm_capable node %s", node)
3506
3507     feedback_fn("* Verifying orphan volumes")
3508     reserved = utils.FieldSet(*cluster.reserved_lvs)
3509
3510     # We will get spurious "unknown volume" warnings if any node of this group
3511     # is secondary for an instance whose primary is in another group. To avoid
3512     # them, we find these instances and add their volumes to node_vol_should.
3513     for inst in self.all_inst_info.values():
3514       for secondary in inst.secondary_nodes:
3515         if (secondary in self.my_node_info
3516             and inst.name not in self.my_inst_info):
3517           inst.MapLVsByNode(node_vol_should)
3518           break
3519
3520     self._VerifyOrphanVolumes(node_vol_should, node_image, reserved)
3521
3522     if constants.VERIFY_NPLUSONE_MEM not in self.op.skip_checks:
3523       feedback_fn("* Verifying N+1 Memory redundancy")
3524       self._VerifyNPlusOneMemory(node_image, self.my_inst_info)
3525
3526     feedback_fn("* Other Notes")
3527     if i_non_redundant:
3528       feedback_fn("  - NOTICE: %d non-redundant instance(s) found."
3529                   % len(i_non_redundant))
3530
3531     if i_non_a_balanced:
3532       feedback_fn("  - NOTICE: %d non-auto-balanced instance(s) found."
3533                   % len(i_non_a_balanced))
3534
3535     if i_offline:
3536       feedback_fn("  - NOTICE: %d offline instance(s) found." % i_offline)
3537
3538     if n_offline:
3539       feedback_fn("  - NOTICE: %d offline node(s) found." % n_offline)
3540
3541     if n_drained:
3542       feedback_fn("  - NOTICE: %d drained node(s) found." % n_drained)
3543
3544     return not self.bad
3545
3546   def HooksCallBack(self, phase, hooks_results, feedback_fn, lu_result):
3547     """Analyze the post-hooks' result
3548
3549     This method analyses the hook result, handles it, and sends some
3550     nicely-formatted feedback back to the user.
3551
3552     @param phase: one of L{constants.HOOKS_PHASE_POST} or
3553         L{constants.HOOKS_PHASE_PRE}; it denotes the hooks phase
3554     @param hooks_results: the results of the multi-node hooks rpc call
3555     @param feedback_fn: function used send feedback back to the caller
3556     @param lu_result: previous Exec result
3557     @return: the new Exec result, based on the previous result
3558         and hook results
3559
3560     """
3561     # We only really run POST phase hooks, only for non-empty groups,
3562     # and are only interested in their results
3563     if not self.my_node_names:
3564       # empty node group
3565       pass
3566     elif phase == constants.HOOKS_PHASE_POST:
3567       # Used to change hooks' output to proper indentation
3568       feedback_fn("* Hooks Results")
3569       assert hooks_results, "invalid result from hooks"
3570
3571       for node_name in hooks_results:
3572         res = hooks_results[node_name]
3573         msg = res.fail_msg
3574         test = msg and not res.offline
3575         self._ErrorIf(test, constants.CV_ENODEHOOKS, node_name,
3576                       "Communication failure in hooks execution: %s", msg)
3577         if res.offline or msg:
3578           # No need to investigate payload if node is offline or gave
3579           # an error.
3580           continue
3581         for script, hkr, output in res.payload:
3582           test = hkr == constants.HKR_FAIL
3583           self._ErrorIf(test, constants.CV_ENODEHOOKS, node_name,
3584                         "Script %s failed, output:", script)
3585           if test:
3586             output = self._HOOKS_INDENT_RE.sub("      ", output)
3587             feedback_fn("%s" % output)
3588             lu_result = False
3589
3590     return lu_result
3591
3592
3593 class LUClusterVerifyDisks(NoHooksLU):
3594   """Verifies the cluster disks status.
3595
3596   """
3597   REQ_BGL = False
3598
3599   def ExpandNames(self):
3600     self.share_locks = _ShareAll()
3601     self.needed_locks = {
3602       locking.LEVEL_NODEGROUP: locking.ALL_SET,
3603       }
3604
3605   def Exec(self, feedback_fn):
3606     group_names = self.owned_locks(locking.LEVEL_NODEGROUP)
3607
3608     # Submit one instance of L{opcodes.OpGroupVerifyDisks} per node group
3609     return ResultWithJobs([[opcodes.OpGroupVerifyDisks(group_name=group)]
3610                            for group in group_names])
3611
3612
3613 class LUGroupVerifyDisks(NoHooksLU):
3614   """Verifies the status of all disks in a node group.
3615
3616   """
3617   REQ_BGL = False
3618
3619   def ExpandNames(self):
3620     # Raises errors.OpPrereqError on its own if group can't be found
3621     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
3622
3623     self.share_locks = _ShareAll()
3624     self.needed_locks = {
3625       locking.LEVEL_INSTANCE: [],
3626       locking.LEVEL_NODEGROUP: [],
3627       locking.LEVEL_NODE: [],
3628       }
3629
3630   def DeclareLocks(self, level):
3631     if level == locking.LEVEL_INSTANCE:
3632       assert not self.needed_locks[locking.LEVEL_INSTANCE]
3633
3634       # Lock instances optimistically, needs verification once node and group
3635       # locks have been acquired
3636       self.needed_locks[locking.LEVEL_INSTANCE] = \
3637         self.cfg.GetNodeGroupInstances(self.group_uuid)
3638
3639     elif level == locking.LEVEL_NODEGROUP:
3640       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
3641
3642       self.needed_locks[locking.LEVEL_NODEGROUP] = \
3643         set([self.group_uuid] +
3644             # Lock all groups used by instances optimistically; this requires
3645             # going via the node before it's locked, requiring verification
3646             # later on
3647             [group_uuid
3648              for instance_name in self.owned_locks(locking.LEVEL_INSTANCE)
3649              for group_uuid in self.cfg.GetInstanceNodeGroups(instance_name)])
3650
3651     elif level == locking.LEVEL_NODE:
3652       # This will only lock the nodes in the group to be verified which contain
3653       # actual instances
3654       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
3655       self._LockInstancesNodes()
3656
3657       # Lock all nodes in group to be verified
3658       assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
3659       member_nodes = self.cfg.GetNodeGroup(self.group_uuid).members
3660       self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
3661
3662   def CheckPrereq(self):
3663     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
3664     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
3665     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
3666
3667     assert self.group_uuid in owned_groups
3668
3669     # Check if locked instances are still correct
3670     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
3671
3672     # Get instance information
3673     self.instances = dict(self.cfg.GetMultiInstanceInfo(owned_instances))
3674
3675     # Check if node groups for locked instances are still correct
3676     _CheckInstancesNodeGroups(self.cfg, self.instances,
3677                               owned_groups, owned_nodes, self.group_uuid)
3678
3679   def Exec(self, feedback_fn):
3680     """Verify integrity of cluster disks.
3681
3682     @rtype: tuple of three items
3683     @return: a tuple of (dict of node-to-node_error, list of instances
3684         which need activate-disks, dict of instance: (node, volume) for
3685         missing volumes
3686
3687     """
3688     res_nodes = {}
3689     res_instances = set()
3690     res_missing = {}
3691
3692     nv_dict = _MapInstanceDisksToNodes([inst
3693             for inst in self.instances.values()
3694             if inst.admin_state == constants.ADMINST_UP])
3695
3696     if nv_dict:
3697       nodes = utils.NiceSort(set(self.owned_locks(locking.LEVEL_NODE)) &
3698                              set(self.cfg.GetVmCapableNodeList()))
3699
3700       node_lvs = self.rpc.call_lv_list(nodes, [])
3701
3702       for (node, node_res) in node_lvs.items():
3703         if node_res.offline:
3704           continue
3705
3706         msg = node_res.fail_msg
3707         if msg:
3708           logging.warning("Error enumerating LVs on node %s: %s", node, msg)
3709           res_nodes[node] = msg
3710           continue
3711
3712         for lv_name, (_, _, lv_online) in node_res.payload.items():
3713           inst = nv_dict.pop((node, lv_name), None)
3714           if not (lv_online or inst is None):
3715             res_instances.add(inst)
3716
3717       # any leftover items in nv_dict are missing LVs, let's arrange the data
3718       # better
3719       for key, inst in nv_dict.iteritems():
3720         res_missing.setdefault(inst, []).append(list(key))
3721
3722     return (res_nodes, list(res_instances), res_missing)
3723
3724
3725 class LUClusterRepairDiskSizes(NoHooksLU):
3726   """Verifies the cluster disks sizes.
3727
3728   """
3729   REQ_BGL = False
3730
3731   def ExpandNames(self):
3732     if self.op.instances:
3733       self.wanted_names = _GetWantedInstances(self, self.op.instances)
3734       self.needed_locks = {
3735         locking.LEVEL_NODE_RES: [],
3736         locking.LEVEL_INSTANCE: self.wanted_names,
3737         }
3738       self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
3739     else:
3740       self.wanted_names = None
3741       self.needed_locks = {
3742         locking.LEVEL_NODE_RES: locking.ALL_SET,
3743         locking.LEVEL_INSTANCE: locking.ALL_SET,
3744         }
3745     self.share_locks = {
3746       locking.LEVEL_NODE_RES: 1,
3747       locking.LEVEL_INSTANCE: 0,
3748       }
3749
3750   def DeclareLocks(self, level):
3751     if level == locking.LEVEL_NODE_RES and self.wanted_names is not None:
3752       self._LockInstancesNodes(primary_only=True, level=level)
3753
3754   def CheckPrereq(self):
3755     """Check prerequisites.
3756
3757     This only checks the optional instance list against the existing names.
3758
3759     """
3760     if self.wanted_names is None:
3761       self.wanted_names = self.owned_locks(locking.LEVEL_INSTANCE)
3762
3763     self.wanted_instances = \
3764         map(compat.snd, self.cfg.GetMultiInstanceInfo(self.wanted_names))
3765
3766   def _EnsureChildSizes(self, disk):
3767     """Ensure children of the disk have the needed disk size.
3768
3769     This is valid mainly for DRBD8 and fixes an issue where the
3770     children have smaller disk size.
3771
3772     @param disk: an L{ganeti.objects.Disk} object
3773
3774     """
3775     if disk.dev_type == constants.LD_DRBD8:
3776       assert disk.children, "Empty children for DRBD8?"
3777       fchild = disk.children[0]
3778       mismatch = fchild.size < disk.size
3779       if mismatch:
3780         self.LogInfo("Child disk has size %d, parent %d, fixing",
3781                      fchild.size, disk.size)
3782         fchild.size = disk.size
3783
3784       # and we recurse on this child only, not on the metadev
3785       return self._EnsureChildSizes(fchild) or mismatch
3786     else:
3787       return False
3788
3789   def Exec(self, feedback_fn):
3790     """Verify the size of cluster disks.
3791
3792     """
3793     # TODO: check child disks too
3794     # TODO: check differences in size between primary/secondary nodes
3795     per_node_disks = {}
3796     for instance in self.wanted_instances:
3797       pnode = instance.primary_node
3798       if pnode not in per_node_disks:
3799         per_node_disks[pnode] = []
3800       for idx, disk in enumerate(instance.disks):
3801         per_node_disks[pnode].append((instance, idx, disk))
3802
3803     assert not (frozenset(per_node_disks.keys()) -
3804                 self.owned_locks(locking.LEVEL_NODE_RES)), \
3805       "Not owning correct locks"
3806     assert not self.owned_locks(locking.LEVEL_NODE)
3807
3808     changed = []
3809     for node, dskl in per_node_disks.items():
3810       newl = [v[2].Copy() for v in dskl]
3811       for dsk in newl:
3812         self.cfg.SetDiskID(dsk, node)
3813       result = self.rpc.call_blockdev_getsize(node, newl)
3814       if result.fail_msg:
3815         self.LogWarning("Failure in blockdev_getsize call to node"
3816                         " %s, ignoring", node)
3817         continue
3818       if len(result.payload) != len(dskl):
3819         logging.warning("Invalid result from node %s: len(dksl)=%d,"
3820                         " result.payload=%s", node, len(dskl), result.payload)
3821         self.LogWarning("Invalid result from node %s, ignoring node results",
3822                         node)
3823         continue
3824       for ((instance, idx, disk), size) in zip(dskl, result.payload):
3825         if size is None:
3826           self.LogWarning("Disk %d of instance %s did not return size"
3827                           " information, ignoring", idx, instance.name)
3828           continue
3829         if not isinstance(size, (int, long)):
3830           self.LogWarning("Disk %d of instance %s did not return valid"
3831                           " size information, ignoring", idx, instance.name)
3832           continue
3833         size = size >> 20
3834         if size != disk.size:
3835           self.LogInfo("Disk %d of instance %s has mismatched size,"
3836                        " correcting: recorded %d, actual %d", idx,
3837                        instance.name, disk.size, size)
3838           disk.size = size
3839           self.cfg.Update(instance, feedback_fn)
3840           changed.append((instance.name, idx, size))
3841         if self._EnsureChildSizes(disk):
3842           self.cfg.Update(instance, feedback_fn)
3843           changed.append((instance.name, idx, disk.size))
3844     return changed
3845
3846
3847 class LUClusterRename(LogicalUnit):
3848   """Rename the cluster.
3849
3850   """
3851   HPATH = "cluster-rename"
3852   HTYPE = constants.HTYPE_CLUSTER
3853
3854   def BuildHooksEnv(self):
3855     """Build hooks env.
3856
3857     """
3858     return {
3859       "OP_TARGET": self.cfg.GetClusterName(),
3860       "NEW_NAME": self.op.name,
3861       }
3862
3863   def BuildHooksNodes(self):
3864     """Build hooks nodes.
3865
3866     """
3867     return ([self.cfg.GetMasterNode()], self.cfg.GetNodeList())
3868
3869   def CheckPrereq(self):
3870     """Verify that the passed name is a valid one.
3871
3872     """
3873     hostname = netutils.GetHostname(name=self.op.name,
3874                                     family=self.cfg.GetPrimaryIPFamily())
3875
3876     new_name = hostname.name
3877     self.ip = new_ip = hostname.ip
3878     old_name = self.cfg.GetClusterName()
3879     old_ip = self.cfg.GetMasterIP()
3880     if new_name == old_name and new_ip == old_ip:
3881       raise errors.OpPrereqError("Neither the name nor the IP address of the"
3882                                  " cluster has changed",
3883                                  errors.ECODE_INVAL)
3884     if new_ip != old_ip:
3885       if netutils.TcpPing(new_ip, constants.DEFAULT_NODED_PORT):
3886         raise errors.OpPrereqError("The given cluster IP address (%s) is"
3887                                    " reachable on the network" %
3888                                    new_ip, errors.ECODE_NOTUNIQUE)
3889
3890     self.op.name = new_name
3891
3892   def Exec(self, feedback_fn):
3893     """Rename the cluster.
3894
3895     """
3896     clustername = self.op.name
3897     new_ip = self.ip
3898
3899     # shutdown the master IP
3900     master_params = self.cfg.GetMasterNetworkParameters()
3901     ems = self.cfg.GetUseExternalMipScript()
3902     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
3903                                                      master_params, ems)
3904     result.Raise("Could not disable the master role")
3905
3906     try:
3907       cluster = self.cfg.GetClusterInfo()
3908       cluster.cluster_name = clustername
3909       cluster.master_ip = new_ip
3910       self.cfg.Update(cluster, feedback_fn)
3911
3912       # update the known hosts file
3913       ssh.WriteKnownHostsFile(self.cfg, constants.SSH_KNOWN_HOSTS_FILE)
3914       node_list = self.cfg.GetOnlineNodeList()
3915       try:
3916         node_list.remove(master_params.name)
3917       except ValueError:
3918         pass
3919       _UploadHelper(self, node_list, constants.SSH_KNOWN_HOSTS_FILE)
3920     finally:
3921       master_params.ip = new_ip
3922       result = self.rpc.call_node_activate_master_ip(master_params.name,
3923                                                      master_params, ems)
3924       msg = result.fail_msg
3925       if msg:
3926         self.LogWarning("Could not re-enable the master role on"
3927                         " the master, please restart manually: %s", msg)
3928
3929     return clustername
3930
3931
3932 def _ValidateNetmask(cfg, netmask):
3933   """Checks if a netmask is valid.
3934
3935   @type cfg: L{config.ConfigWriter}
3936   @param cfg: The cluster configuration
3937   @type netmask: int
3938   @param netmask: the netmask to be verified
3939   @raise errors.OpPrereqError: if the validation fails
3940
3941   """
3942   ip_family = cfg.GetPrimaryIPFamily()
3943   try:
3944     ipcls = netutils.IPAddress.GetClassFromIpFamily(ip_family)
3945   except errors.ProgrammerError:
3946     raise errors.OpPrereqError("Invalid primary ip family: %s." %
3947                                ip_family)
3948   if not ipcls.ValidateNetmask(netmask):
3949     raise errors.OpPrereqError("CIDR netmask (%s) not valid" %
3950                                 (netmask))
3951
3952
3953 class LUClusterSetParams(LogicalUnit):
3954   """Change the parameters of the cluster.
3955
3956   """
3957   HPATH = "cluster-modify"
3958   HTYPE = constants.HTYPE_CLUSTER
3959   REQ_BGL = False
3960
3961   def CheckArguments(self):
3962     """Check parameters
3963
3964     """
3965     if self.op.uid_pool:
3966       uidpool.CheckUidPool(self.op.uid_pool)
3967
3968     if self.op.add_uids:
3969       uidpool.CheckUidPool(self.op.add_uids)
3970
3971     if self.op.remove_uids:
3972       uidpool.CheckUidPool(self.op.remove_uids)
3973
3974     if self.op.master_netmask is not None:
3975       _ValidateNetmask(self.cfg, self.op.master_netmask)
3976
3977     if self.op.diskparams:
3978       for dt_params in self.op.diskparams.values():
3979         utils.ForceDictType(dt_params, constants.DISK_DT_TYPES)
3980       try:
3981         utils.VerifyDictOptions(self.op.diskparams, constants.DISK_DT_DEFAULTS)
3982       except errors.OpPrereqError, err:
3983         raise errors.OpPrereqError("While verify diskparams options: %s" % err,
3984                                    errors.ECODE_INVAL)
3985
3986   def ExpandNames(self):
3987     # FIXME: in the future maybe other cluster params won't require checking on
3988     # all nodes to be modified.
3989     self.needed_locks = {
3990       locking.LEVEL_NODE: locking.ALL_SET,
3991       locking.LEVEL_INSTANCE: locking.ALL_SET,
3992       locking.LEVEL_NODEGROUP: locking.ALL_SET,
3993     }
3994     self.share_locks = {
3995         locking.LEVEL_NODE: 1,
3996         locking.LEVEL_INSTANCE: 1,
3997         locking.LEVEL_NODEGROUP: 1,
3998     }
3999
4000   def BuildHooksEnv(self):
4001     """Build hooks env.
4002
4003     """
4004     return {
4005       "OP_TARGET": self.cfg.GetClusterName(),
4006       "NEW_VG_NAME": self.op.vg_name,
4007       }
4008
4009   def BuildHooksNodes(self):
4010     """Build hooks nodes.
4011
4012     """
4013     mn = self.cfg.GetMasterNode()
4014     return ([mn], [mn])
4015
4016   def CheckPrereq(self):
4017     """Check prerequisites.
4018
4019     This checks whether the given params don't conflict and
4020     if the given volume group is valid.
4021
4022     """
4023     if self.op.vg_name is not None and not self.op.vg_name:
4024       if self.cfg.HasAnyDiskOfType(constants.LD_LV):
4025         raise errors.OpPrereqError("Cannot disable lvm storage while lvm-based"
4026                                    " instances exist", errors.ECODE_INVAL)
4027
4028     if self.op.drbd_helper is not None and not self.op.drbd_helper:
4029       if self.cfg.HasAnyDiskOfType(constants.LD_DRBD8):
4030         raise errors.OpPrereqError("Cannot disable drbd helper while"
4031                                    " drbd-based instances exist",
4032                                    errors.ECODE_INVAL)
4033
4034     node_list = self.owned_locks(locking.LEVEL_NODE)
4035
4036     # if vg_name not None, checks given volume group on all nodes
4037     if self.op.vg_name:
4038       vglist = self.rpc.call_vg_list(node_list)
4039       for node in node_list:
4040         msg = vglist[node].fail_msg
4041         if msg:
4042           # ignoring down node
4043           self.LogWarning("Error while gathering data on node %s"
4044                           " (ignoring node): %s", node, msg)
4045           continue
4046         vgstatus = utils.CheckVolumeGroupSize(vglist[node].payload,
4047                                               self.op.vg_name,
4048                                               constants.MIN_VG_SIZE)
4049         if vgstatus:
4050           raise errors.OpPrereqError("Error on node '%s': %s" %
4051                                      (node, vgstatus), errors.ECODE_ENVIRON)
4052
4053     if self.op.drbd_helper:
4054       # checks given drbd helper on all nodes
4055       helpers = self.rpc.call_drbd_helper(node_list)
4056       for (node, ninfo) in self.cfg.GetMultiNodeInfo(node_list):
4057         if ninfo.offline:
4058           self.LogInfo("Not checking drbd helper on offline node %s", node)
4059           continue
4060         msg = helpers[node].fail_msg
4061         if msg:
4062           raise errors.OpPrereqError("Error checking drbd helper on node"
4063                                      " '%s': %s" % (node, msg),
4064                                      errors.ECODE_ENVIRON)
4065         node_helper = helpers[node].payload
4066         if node_helper != self.op.drbd_helper:
4067           raise errors.OpPrereqError("Error on node '%s': drbd helper is %s" %
4068                                      (node, node_helper), errors.ECODE_ENVIRON)
4069
4070     self.cluster = cluster = self.cfg.GetClusterInfo()
4071     # validate params changes
4072     if self.op.beparams:
4073       objects.UpgradeBeParams(self.op.beparams)
4074       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
4075       self.new_beparams = cluster.SimpleFillBE(self.op.beparams)
4076
4077     if self.op.ndparams:
4078       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
4079       self.new_ndparams = cluster.SimpleFillND(self.op.ndparams)
4080
4081       # TODO: we need a more general way to handle resetting
4082       # cluster-level parameters to default values
4083       if self.new_ndparams["oob_program"] == "":
4084         self.new_ndparams["oob_program"] = \
4085             constants.NDC_DEFAULTS[constants.ND_OOB_PROGRAM]
4086
4087     if self.op.hv_state:
4088       new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
4089                                             self.cluster.hv_state_static)
4090       self.new_hv_state = dict((hv, cluster.SimpleFillHvState(values))
4091                                for hv, values in new_hv_state.items())
4092
4093     if self.op.disk_state:
4094       new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state,
4095                                                 self.cluster.disk_state_static)
4096       self.new_disk_state = \
4097         dict((storage, dict((name, cluster.SimpleFillDiskState(values))
4098                             for name, values in svalues.items()))
4099              for storage, svalues in new_disk_state.items())
4100
4101     if self.op.ipolicy:
4102       self.new_ipolicy = _GetUpdatedIPolicy(cluster.ipolicy, self.op.ipolicy,
4103                                             group_policy=False)
4104
4105       all_instances = self.cfg.GetAllInstancesInfo().values()
4106       violations = set()
4107       for group in self.cfg.GetAllNodeGroupsInfo().values():
4108         instances = frozenset([inst for inst in all_instances
4109                                if compat.any(node in group.members
4110                                              for node in inst.all_nodes)])
4111         new_ipolicy = objects.FillIPolicy(self.new_ipolicy, group.ipolicy)
4112         new = _ComputeNewInstanceViolations(_CalculateGroupIPolicy(cluster,
4113                                                                    group),
4114                                             new_ipolicy, instances)
4115         if new:
4116           violations.update(new)
4117
4118       if violations:
4119         self.LogWarning("After the ipolicy change the following instances"
4120                         " violate them: %s",
4121                         utils.CommaJoin(utils.NiceSort(violations)))
4122
4123     if self.op.nicparams:
4124       utils.ForceDictType(self.op.nicparams, constants.NICS_PARAMETER_TYPES)
4125       self.new_nicparams = cluster.SimpleFillNIC(self.op.nicparams)
4126       objects.NIC.CheckParameterSyntax(self.new_nicparams)
4127       nic_errors = []
4128
4129       # check all instances for consistency
4130       for instance in self.cfg.GetAllInstancesInfo().values():
4131         for nic_idx, nic in enumerate(instance.nics):
4132           params_copy = copy.deepcopy(nic.nicparams)
4133           params_filled = objects.FillDict(self.new_nicparams, params_copy)
4134
4135           # check parameter syntax
4136           try:
4137             objects.NIC.CheckParameterSyntax(params_filled)
4138           except errors.ConfigurationError, err:
4139             nic_errors.append("Instance %s, nic/%d: %s" %
4140                               (instance.name, nic_idx, err))
4141
4142           # if we're moving instances to routed, check that they have an ip
4143           target_mode = params_filled[constants.NIC_MODE]
4144           if target_mode == constants.NIC_MODE_ROUTED and not nic.ip:
4145             nic_errors.append("Instance %s, nic/%d: routed NIC with no ip"
4146                               " address" % (instance.name, nic_idx))
4147       if nic_errors:
4148         raise errors.OpPrereqError("Cannot apply the change, errors:\n%s" %
4149                                    "\n".join(nic_errors))
4150
4151     # hypervisor list/parameters
4152     self.new_hvparams = new_hvp = objects.FillDict(cluster.hvparams, {})
4153     if self.op.hvparams:
4154       for hv_name, hv_dict in self.op.hvparams.items():
4155         if hv_name not in self.new_hvparams:
4156           self.new_hvparams[hv_name] = hv_dict
4157         else:
4158           self.new_hvparams[hv_name].update(hv_dict)
4159
4160     # disk template parameters
4161     self.new_diskparams = objects.FillDict(cluster.diskparams, {})
4162     if self.op.diskparams:
4163       for dt_name, dt_params in self.op.diskparams.items():
4164         if dt_name not in self.op.diskparams:
4165           self.new_diskparams[dt_name] = dt_params
4166         else:
4167           self.new_diskparams[dt_name].update(dt_params)
4168
4169     # os hypervisor parameters
4170     self.new_os_hvp = objects.FillDict(cluster.os_hvp, {})
4171     if self.op.os_hvp:
4172       for os_name, hvs in self.op.os_hvp.items():
4173         if os_name not in self.new_os_hvp:
4174           self.new_os_hvp[os_name] = hvs
4175         else:
4176           for hv_name, hv_dict in hvs.items():
4177             if hv_name not in self.new_os_hvp[os_name]:
4178               self.new_os_hvp[os_name][hv_name] = hv_dict
4179             else:
4180               self.new_os_hvp[os_name][hv_name].update(hv_dict)
4181
4182     # os parameters
4183     self.new_osp = objects.FillDict(cluster.osparams, {})
4184     if self.op.osparams:
4185       for os_name, osp in self.op.osparams.items():
4186         if os_name not in self.new_osp:
4187           self.new_osp[os_name] = {}
4188
4189         self.new_osp[os_name] = _GetUpdatedParams(self.new_osp[os_name], osp,
4190                                                   use_none=True)
4191
4192         if not self.new_osp[os_name]:
4193           # we removed all parameters
4194           del self.new_osp[os_name]
4195         else:
4196           # check the parameter validity (remote check)
4197           _CheckOSParams(self, False, [self.cfg.GetMasterNode()],
4198                          os_name, self.new_osp[os_name])
4199
4200     # changes to the hypervisor list
4201     if self.op.enabled_hypervisors is not None:
4202       self.hv_list = self.op.enabled_hypervisors
4203       for hv in self.hv_list:
4204         # if the hypervisor doesn't already exist in the cluster
4205         # hvparams, we initialize it to empty, and then (in both
4206         # cases) we make sure to fill the defaults, as we might not
4207         # have a complete defaults list if the hypervisor wasn't
4208         # enabled before
4209         if hv not in new_hvp:
4210           new_hvp[hv] = {}
4211         new_hvp[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], new_hvp[hv])
4212         utils.ForceDictType(new_hvp[hv], constants.HVS_PARAMETER_TYPES)
4213     else:
4214       self.hv_list = cluster.enabled_hypervisors
4215
4216     if self.op.hvparams or self.op.enabled_hypervisors is not None:
4217       # either the enabled list has changed, or the parameters have, validate
4218       for hv_name, hv_params in self.new_hvparams.items():
4219         if ((self.op.hvparams and hv_name in self.op.hvparams) or
4220             (self.op.enabled_hypervisors and
4221              hv_name in self.op.enabled_hypervisors)):
4222           # either this is a new hypervisor, or its parameters have changed
4223           hv_class = hypervisor.GetHypervisor(hv_name)
4224           utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
4225           hv_class.CheckParameterSyntax(hv_params)
4226           _CheckHVParams(self, node_list, hv_name, hv_params)
4227
4228     if self.op.os_hvp:
4229       # no need to check any newly-enabled hypervisors, since the
4230       # defaults have already been checked in the above code-block
4231       for os_name, os_hvp in self.new_os_hvp.items():
4232         for hv_name, hv_params in os_hvp.items():
4233           utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
4234           # we need to fill in the new os_hvp on top of the actual hv_p
4235           cluster_defaults = self.new_hvparams.get(hv_name, {})
4236           new_osp = objects.FillDict(cluster_defaults, hv_params)
4237           hv_class = hypervisor.GetHypervisor(hv_name)
4238           hv_class.CheckParameterSyntax(new_osp)
4239           _CheckHVParams(self, node_list, hv_name, new_osp)
4240
4241     if self.op.default_iallocator:
4242       alloc_script = utils.FindFile(self.op.default_iallocator,
4243                                     constants.IALLOCATOR_SEARCH_PATH,
4244                                     os.path.isfile)
4245       if alloc_script is None:
4246         raise errors.OpPrereqError("Invalid default iallocator script '%s'"
4247                                    " specified" % self.op.default_iallocator,
4248                                    errors.ECODE_INVAL)
4249
4250   def Exec(self, feedback_fn):
4251     """Change the parameters of the cluster.
4252
4253     """
4254     if self.op.vg_name is not None:
4255       new_volume = self.op.vg_name
4256       if not new_volume:
4257         new_volume = None
4258       if new_volume != self.cfg.GetVGName():
4259         self.cfg.SetVGName(new_volume)
4260       else:
4261         feedback_fn("Cluster LVM configuration already in desired"
4262                     " state, not changing")
4263     if self.op.drbd_helper is not None:
4264       new_helper = self.op.drbd_helper
4265       if not new_helper:
4266         new_helper = None
4267       if new_helper != self.cfg.GetDRBDHelper():
4268         self.cfg.SetDRBDHelper(new_helper)
4269       else:
4270         feedback_fn("Cluster DRBD helper already in desired state,"
4271                     " not changing")
4272     if self.op.hvparams:
4273       self.cluster.hvparams = self.new_hvparams
4274     if self.op.os_hvp:
4275       self.cluster.os_hvp = self.new_os_hvp
4276     if self.op.enabled_hypervisors is not None:
4277       self.cluster.hvparams = self.new_hvparams
4278       self.cluster.enabled_hypervisors = self.op.enabled_hypervisors
4279     if self.op.beparams:
4280       self.cluster.beparams[constants.PP_DEFAULT] = self.new_beparams
4281     if self.op.nicparams:
4282       self.cluster.nicparams[constants.PP_DEFAULT] = self.new_nicparams
4283     if self.op.ipolicy:
4284       self.cluster.ipolicy = self.new_ipolicy
4285     if self.op.osparams:
4286       self.cluster.osparams = self.new_osp
4287     if self.op.ndparams:
4288       self.cluster.ndparams = self.new_ndparams
4289     if self.op.diskparams:
4290       self.cluster.diskparams = self.new_diskparams
4291     if self.op.hv_state:
4292       self.cluster.hv_state_static = self.new_hv_state
4293     if self.op.disk_state:
4294       self.cluster.disk_state_static = self.new_disk_state
4295
4296     if self.op.candidate_pool_size is not None:
4297       self.cluster.candidate_pool_size = self.op.candidate_pool_size
4298       # we need to update the pool size here, otherwise the save will fail
4299       _AdjustCandidatePool(self, [])
4300
4301     if self.op.maintain_node_health is not None:
4302       if self.op.maintain_node_health and not constants.ENABLE_CONFD:
4303         feedback_fn("Note: CONFD was disabled at build time, node health"
4304                     " maintenance is not useful (still enabling it)")
4305       self.cluster.maintain_node_health = self.op.maintain_node_health
4306
4307     if self.op.prealloc_wipe_disks is not None:
4308       self.cluster.prealloc_wipe_disks = self.op.prealloc_wipe_disks
4309
4310     if self.op.add_uids is not None:
4311       uidpool.AddToUidPool(self.cluster.uid_pool, self.op.add_uids)
4312
4313     if self.op.remove_uids is not None:
4314       uidpool.RemoveFromUidPool(self.cluster.uid_pool, self.op.remove_uids)
4315
4316     if self.op.uid_pool is not None:
4317       self.cluster.uid_pool = self.op.uid_pool
4318
4319     if self.op.default_iallocator is not None:
4320       self.cluster.default_iallocator = self.op.default_iallocator
4321
4322     if self.op.reserved_lvs is not None:
4323       self.cluster.reserved_lvs = self.op.reserved_lvs
4324
4325     if self.op.use_external_mip_script is not None:
4326       self.cluster.use_external_mip_script = self.op.use_external_mip_script
4327
4328     def helper_os(aname, mods, desc):
4329       desc += " OS list"
4330       lst = getattr(self.cluster, aname)
4331       for key, val in mods:
4332         if key == constants.DDM_ADD:
4333           if val in lst:
4334             feedback_fn("OS %s already in %s, ignoring" % (val, desc))
4335           else:
4336             lst.append(val)
4337         elif key == constants.DDM_REMOVE:
4338           if val in lst:
4339             lst.remove(val)
4340           else:
4341             feedback_fn("OS %s not found in %s, ignoring" % (val, desc))
4342         else:
4343           raise errors.ProgrammerError("Invalid modification '%s'" % key)
4344
4345     if self.op.hidden_os:
4346       helper_os("hidden_os", self.op.hidden_os, "hidden")
4347
4348     if self.op.blacklisted_os:
4349       helper_os("blacklisted_os", self.op.blacklisted_os, "blacklisted")
4350
4351     if self.op.master_netdev:
4352       master_params = self.cfg.GetMasterNetworkParameters()
4353       ems = self.cfg.GetUseExternalMipScript()
4354       feedback_fn("Shutting down master ip on the current netdev (%s)" %
4355                   self.cluster.master_netdev)
4356       result = self.rpc.call_node_deactivate_master_ip(master_params.name,
4357                                                        master_params, ems)
4358       result.Raise("Could not disable the master ip")
4359       feedback_fn("Changing master_netdev from %s to %s" %
4360                   (master_params.netdev, self.op.master_netdev))
4361       self.cluster.master_netdev = self.op.master_netdev
4362
4363     if self.op.master_netmask:
4364       master_params = self.cfg.GetMasterNetworkParameters()
4365       feedback_fn("Changing master IP netmask to %s" % self.op.master_netmask)
4366       result = self.rpc.call_node_change_master_netmask(master_params.name,
4367                                                         master_params.netmask,
4368                                                         self.op.master_netmask,
4369                                                         master_params.ip,
4370                                                         master_params.netdev)
4371       if result.fail_msg:
4372         msg = "Could not change the master IP netmask: %s" % result.fail_msg
4373         feedback_fn(msg)
4374
4375       self.cluster.master_netmask = self.op.master_netmask
4376
4377     self.cfg.Update(self.cluster, feedback_fn)
4378
4379     if self.op.master_netdev:
4380       master_params = self.cfg.GetMasterNetworkParameters()
4381       feedback_fn("Starting the master ip on the new master netdev (%s)" %
4382                   self.op.master_netdev)
4383       ems = self.cfg.GetUseExternalMipScript()
4384       result = self.rpc.call_node_activate_master_ip(master_params.name,
4385                                                      master_params, ems)
4386       if result.fail_msg:
4387         self.LogWarning("Could not re-enable the master ip on"
4388                         " the master, please restart manually: %s",
4389                         result.fail_msg)
4390
4391
4392 def _UploadHelper(lu, nodes, fname):
4393   """Helper for uploading a file and showing warnings.
4394
4395   """
4396   if os.path.exists(fname):
4397     result = lu.rpc.call_upload_file(nodes, fname)
4398     for to_node, to_result in result.items():
4399       msg = to_result.fail_msg
4400       if msg:
4401         msg = ("Copy of file %s to node %s failed: %s" %
4402                (fname, to_node, msg))
4403         lu.proc.LogWarning(msg)
4404
4405
4406 def _ComputeAncillaryFiles(cluster, redist):
4407   """Compute files external to Ganeti which need to be consistent.
4408
4409   @type redist: boolean
4410   @param redist: Whether to include files which need to be redistributed
4411
4412   """
4413   # Compute files for all nodes
4414   files_all = set([
4415     constants.SSH_KNOWN_HOSTS_FILE,
4416     constants.CONFD_HMAC_KEY,
4417     constants.CLUSTER_DOMAIN_SECRET_FILE,
4418     constants.SPICE_CERT_FILE,
4419     constants.SPICE_CACERT_FILE,
4420     constants.RAPI_USERS_FILE,
4421     ])
4422
4423   if not redist:
4424     files_all.update(constants.ALL_CERT_FILES)
4425     files_all.update(ssconf.SimpleStore().GetFileList())
4426   else:
4427     # we need to ship at least the RAPI certificate
4428     files_all.add(constants.RAPI_CERT_FILE)
4429
4430   if cluster.modify_etc_hosts:
4431     files_all.add(constants.ETC_HOSTS)
4432
4433   if cluster.use_external_mip_script:
4434     files_all.add(constants.EXTERNAL_MASTER_SETUP_SCRIPT)
4435
4436   # Files which are optional, these must:
4437   # - be present in one other category as well
4438   # - either exist or not exist on all nodes of that category (mc, vm all)
4439   files_opt = set([
4440     constants.RAPI_USERS_FILE,
4441     ])
4442
4443   # Files which should only be on master candidates
4444   files_mc = set()
4445
4446   if not redist:
4447     files_mc.add(constants.CLUSTER_CONF_FILE)
4448
4449   # Files which should only be on VM-capable nodes
4450   files_vm = set(filename
4451     for hv_name in cluster.enabled_hypervisors
4452     for filename in hypervisor.GetHypervisor(hv_name).GetAncillaryFiles()[0])
4453
4454   files_opt |= set(filename
4455     for hv_name in cluster.enabled_hypervisors
4456     for filename in hypervisor.GetHypervisor(hv_name).GetAncillaryFiles()[1])
4457
4458   # Filenames in each category must be unique
4459   all_files_set = files_all | files_mc | files_vm
4460   assert (len(all_files_set) ==
4461           sum(map(len, [files_all, files_mc, files_vm]))), \
4462          "Found file listed in more than one file list"
4463
4464   # Optional files must be present in one other category
4465   assert all_files_set.issuperset(files_opt), \
4466          "Optional file not in a different required list"
4467
4468   return (files_all, files_opt, files_mc, files_vm)
4469
4470
4471 def _RedistributeAncillaryFiles(lu, additional_nodes=None, additional_vm=True):
4472   """Distribute additional files which are part of the cluster configuration.
4473
4474   ConfigWriter takes care of distributing the config and ssconf files, but
4475   there are more files which should be distributed to all nodes. This function
4476   makes sure those are copied.
4477
4478   @param lu: calling logical unit
4479   @param additional_nodes: list of nodes not in the config to distribute to
4480   @type additional_vm: boolean
4481   @param additional_vm: whether the additional nodes are vm-capable or not
4482
4483   """
4484   # Gather target nodes
4485   cluster = lu.cfg.GetClusterInfo()
4486   master_info = lu.cfg.GetNodeInfo(lu.cfg.GetMasterNode())
4487
4488   online_nodes = lu.cfg.GetOnlineNodeList()
4489   online_set = frozenset(online_nodes)
4490   vm_nodes = list(online_set.intersection(lu.cfg.GetVmCapableNodeList()))
4491
4492   if additional_nodes is not None:
4493     online_nodes.extend(additional_nodes)
4494     if additional_vm:
4495       vm_nodes.extend(additional_nodes)
4496
4497   # Never distribute to master node
4498   for nodelist in [online_nodes, vm_nodes]:
4499     if master_info.name in nodelist:
4500       nodelist.remove(master_info.name)
4501
4502   # Gather file lists
4503   (files_all, _, files_mc, files_vm) = \
4504     _ComputeAncillaryFiles(cluster, True)
4505
4506   # Never re-distribute configuration file from here
4507   assert not (constants.CLUSTER_CONF_FILE in files_all or
4508               constants.CLUSTER_CONF_FILE in files_vm)
4509   assert not files_mc, "Master candidates not handled in this function"
4510
4511   filemap = [
4512     (online_nodes, files_all),
4513     (vm_nodes, files_vm),
4514     ]
4515
4516   # Upload the files
4517   for (node_list, files) in filemap:
4518     for fname in files:
4519       _UploadHelper(lu, node_list, fname)
4520
4521
4522 class LUClusterRedistConf(NoHooksLU):
4523   """Force the redistribution of cluster configuration.
4524
4525   This is a very simple LU.
4526
4527   """
4528   REQ_BGL = False
4529
4530   def ExpandNames(self):
4531     self.needed_locks = {
4532       locking.LEVEL_NODE: locking.ALL_SET,
4533     }
4534     self.share_locks[locking.LEVEL_NODE] = 1
4535
4536   def Exec(self, feedback_fn):
4537     """Redistribute the configuration.
4538
4539     """
4540     self.cfg.Update(self.cfg.GetClusterInfo(), feedback_fn)
4541     _RedistributeAncillaryFiles(self)
4542
4543
4544 class LUClusterActivateMasterIp(NoHooksLU):
4545   """Activate the master IP on the master node.
4546
4547   """
4548   def Exec(self, feedback_fn):
4549     """Activate the master IP.
4550
4551     """
4552     master_params = self.cfg.GetMasterNetworkParameters()
4553     ems = self.cfg.GetUseExternalMipScript()
4554     result = self.rpc.call_node_activate_master_ip(master_params.name,
4555                                                    master_params, ems)
4556     result.Raise("Could not activate the master IP")
4557
4558
4559 class LUClusterDeactivateMasterIp(NoHooksLU):
4560   """Deactivate the master IP on the master node.
4561
4562   """
4563   def Exec(self, feedback_fn):
4564     """Deactivate the master IP.
4565
4566     """
4567     master_params = self.cfg.GetMasterNetworkParameters()
4568     ems = self.cfg.GetUseExternalMipScript()
4569     result = self.rpc.call_node_deactivate_master_ip(master_params.name,
4570                                                      master_params, ems)
4571     result.Raise("Could not deactivate the master IP")
4572
4573
4574 def _WaitForSync(lu, instance, disks=None, oneshot=False):
4575   """Sleep and poll for an instance's disk to sync.
4576
4577   """
4578   if not instance.disks or disks is not None and not disks:
4579     return True
4580
4581   disks = _ExpandCheckDisks(instance, disks)
4582
4583   if not oneshot:
4584     lu.proc.LogInfo("Waiting for instance %s to sync disks." % instance.name)
4585
4586   node = instance.primary_node
4587
4588   for dev in disks:
4589     lu.cfg.SetDiskID(dev, node)
4590
4591   # TODO: Convert to utils.Retry
4592
4593   retries = 0
4594   degr_retries = 10 # in seconds, as we sleep 1 second each time
4595   while True:
4596     max_time = 0
4597     done = True
4598     cumul_degraded = False
4599     rstats = lu.rpc.call_blockdev_getmirrorstatus(node, (disks, instance))
4600     msg = rstats.fail_msg
4601     if msg:
4602       lu.LogWarning("Can't get any data from node %s: %s", node, msg)
4603       retries += 1
4604       if retries >= 10:
4605         raise errors.RemoteError("Can't contact node %s for mirror data,"
4606                                  " aborting." % node)
4607       time.sleep(6)
4608       continue
4609     rstats = rstats.payload
4610     retries = 0
4611     for i, mstat in enumerate(rstats):
4612       if mstat is None:
4613         lu.LogWarning("Can't compute data for node %s/%s",
4614                            node, disks[i].iv_name)
4615         continue
4616
4617       cumul_degraded = (cumul_degraded or
4618                         (mstat.is_degraded and mstat.sync_percent is None))
4619       if mstat.sync_percent is not None:
4620         done = False
4621         if mstat.estimated_time is not None:
4622           rem_time = ("%s remaining (estimated)" %
4623                       utils.FormatSeconds(mstat.estimated_time))
4624           max_time = mstat.estimated_time
4625         else:
4626           rem_time = "no time estimate"
4627         lu.proc.LogInfo("- device %s: %5.2f%% done, %s" %
4628                         (disks[i].iv_name, mstat.sync_percent, rem_time))
4629
4630     # if we're done but degraded, let's do a few small retries, to
4631     # make sure we see a stable and not transient situation; therefore
4632     # we force restart of the loop
4633     if (done or oneshot) and cumul_degraded and degr_retries > 0:
4634       logging.info("Degraded disks found, %d retries left", degr_retries)
4635       degr_retries -= 1
4636       time.sleep(1)
4637       continue
4638
4639     if done or oneshot:
4640       break
4641
4642     time.sleep(min(60, max_time))
4643
4644   if done:
4645     lu.proc.LogInfo("Instance %s's disks are in sync." % instance.name)
4646   return not cumul_degraded
4647
4648
4649 def _BlockdevFind(lu, node, dev, instance):
4650   """Wrapper around call_blockdev_find to annotate diskparams.
4651
4652   @param lu: A reference to the lu object
4653   @param node: The node to call out
4654   @param dev: The device to find
4655   @param instance: The instance object the device belongs to
4656   @returns The result of the rpc call
4657
4658   """
4659   (disk,) = _AnnotateDiskParams(instance, [dev], lu.cfg)
4660   return lu.rpc.call_blockdev_find(node, disk)
4661
4662
4663 def _CheckDiskConsistency(lu, instance, dev, node, on_primary, ldisk=False):
4664   """Wrapper around L{_CheckDiskConsistencyInner}.
4665
4666   """
4667   (disk,) = _AnnotateDiskParams(instance, [dev], lu.cfg)
4668   return _CheckDiskConsistencyInner(lu, instance, disk, node, on_primary,
4669                                     ldisk=ldisk)
4670
4671
4672 def _CheckDiskConsistencyInner(lu, instance, dev, node, on_primary,
4673                                ldisk=False):
4674   """Check that mirrors are not degraded.
4675
4676   @attention: The device has to be annotated already.
4677
4678   The ldisk parameter, if True, will change the test from the
4679   is_degraded attribute (which represents overall non-ok status for
4680   the device(s)) to the ldisk (representing the local storage status).
4681
4682   """
4683   lu.cfg.SetDiskID(dev, node)
4684
4685   result = True
4686
4687   if on_primary or dev.AssembleOnSecondary():
4688     rstats = lu.rpc.call_blockdev_find(node, dev)
4689     msg = rstats.fail_msg
4690     if msg:
4691       lu.LogWarning("Can't find disk on node %s: %s", node, msg)
4692       result = False
4693     elif not rstats.payload:
4694       lu.LogWarning("Can't find disk on node %s", node)
4695       result = False
4696     else:
4697       if ldisk:
4698         result = result and rstats.payload.ldisk_status == constants.LDS_OKAY
4699       else:
4700         result = result and not rstats.payload.is_degraded
4701
4702   if dev.children:
4703     for child in dev.children:
4704       result = result and _CheckDiskConsistencyInner(lu, instance, child, node,
4705                                                      on_primary)
4706
4707   return result
4708
4709
4710 class LUOobCommand(NoHooksLU):
4711   """Logical unit for OOB handling.
4712
4713   """
4714   REQ_BGL = False
4715   _SKIP_MASTER = (constants.OOB_POWER_OFF, constants.OOB_POWER_CYCLE)
4716
4717   def ExpandNames(self):
4718     """Gather locks we need.
4719
4720     """
4721     if self.op.node_names:
4722       self.op.node_names = _GetWantedNodes(self, self.op.node_names)
4723       lock_names = self.op.node_names
4724     else:
4725       lock_names = locking.ALL_SET
4726
4727     self.needed_locks = {
4728       locking.LEVEL_NODE: lock_names,
4729       }
4730
4731   def CheckPrereq(self):
4732     """Check prerequisites.
4733
4734     This checks:
4735      - the node exists in the configuration
4736      - OOB is supported
4737
4738     Any errors are signaled by raising errors.OpPrereqError.
4739
4740     """
4741     self.nodes = []
4742     self.master_node = self.cfg.GetMasterNode()
4743
4744     assert self.op.power_delay >= 0.0
4745
4746     if self.op.node_names:
4747       if (self.op.command in self._SKIP_MASTER and
4748           self.master_node in self.op.node_names):
4749         master_node_obj = self.cfg.GetNodeInfo(self.master_node)
4750         master_oob_handler = _SupportsOob(self.cfg, master_node_obj)
4751
4752         if master_oob_handler:
4753           additional_text = ("run '%s %s %s' if you want to operate on the"
4754                              " master regardless") % (master_oob_handler,
4755                                                       self.op.command,
4756                                                       self.master_node)
4757         else:
4758           additional_text = "it does not support out-of-band operations"
4759
4760         raise errors.OpPrereqError(("Operating on the master node %s is not"
4761                                     " allowed for %s; %s") %
4762                                    (self.master_node, self.op.command,
4763                                     additional_text), errors.ECODE_INVAL)
4764     else:
4765       self.op.node_names = self.cfg.GetNodeList()
4766       if self.op.command in self._SKIP_MASTER:
4767         self.op.node_names.remove(self.master_node)
4768
4769     if self.op.command in self._SKIP_MASTER:
4770       assert self.master_node not in self.op.node_names
4771
4772     for (node_name, node) in self.cfg.GetMultiNodeInfo(self.op.node_names):
4773       if node is None:
4774         raise errors.OpPrereqError("Node %s not found" % node_name,
4775                                    errors.ECODE_NOENT)
4776       else:
4777         self.nodes.append(node)
4778
4779       if (not self.op.ignore_status and
4780           (self.op.command == constants.OOB_POWER_OFF and not node.offline)):
4781         raise errors.OpPrereqError(("Cannot power off node %s because it is"
4782                                     " not marked offline") % node_name,
4783                                    errors.ECODE_STATE)
4784
4785   def Exec(self, feedback_fn):
4786     """Execute OOB and return result if we expect any.
4787
4788     """
4789     master_node = self.master_node
4790     ret = []
4791
4792     for idx, node in enumerate(utils.NiceSort(self.nodes,
4793                                               key=lambda node: node.name)):
4794       node_entry = [(constants.RS_NORMAL, node.name)]
4795       ret.append(node_entry)
4796
4797       oob_program = _SupportsOob(self.cfg, node)
4798
4799       if not oob_program:
4800         node_entry.append((constants.RS_UNAVAIL, None))
4801         continue
4802
4803       logging.info("Executing out-of-band command '%s' using '%s' on %s",
4804                    self.op.command, oob_program, node.name)
4805       result = self.rpc.call_run_oob(master_node, oob_program,
4806                                      self.op.command, node.name,
4807                                      self.op.timeout)
4808
4809       if result.fail_msg:
4810         self.LogWarning("Out-of-band RPC failed on node '%s': %s",
4811                         node.name, result.fail_msg)
4812         node_entry.append((constants.RS_NODATA, None))
4813       else:
4814         try:
4815           self._CheckPayload(result)
4816         except errors.OpExecError, err:
4817           self.LogWarning("Payload returned by node '%s' is not valid: %s",
4818                           node.name, err)
4819           node_entry.append((constants.RS_NODATA, None))
4820         else:
4821           if self.op.command == constants.OOB_HEALTH:
4822             # For health we should log important events
4823             for item, status in result.payload:
4824               if status in [constants.OOB_STATUS_WARNING,
4825                             constants.OOB_STATUS_CRITICAL]:
4826                 self.LogWarning("Item '%s' on node '%s' has status '%s'",
4827                                 item, node.name, status)
4828
4829           if self.op.command == constants.OOB_POWER_ON:
4830             node.powered = True
4831           elif self.op.command == constants.OOB_POWER_OFF:
4832             node.powered = False
4833           elif self.op.command == constants.OOB_POWER_STATUS:
4834             powered = result.payload[constants.OOB_POWER_STATUS_POWERED]
4835             if powered != node.powered:
4836               logging.warning(("Recorded power state (%s) of node '%s' does not"
4837                                " match actual power state (%s)"), node.powered,
4838                               node.name, powered)
4839
4840           # For configuration changing commands we should update the node
4841           if self.op.command in (constants.OOB_POWER_ON,
4842                                  constants.OOB_POWER_OFF):
4843             self.cfg.Update(node, feedback_fn)
4844
4845           node_entry.append((constants.RS_NORMAL, result.payload))
4846
4847           if (self.op.command == constants.OOB_POWER_ON and
4848               idx < len(self.nodes) - 1):
4849             time.sleep(self.op.power_delay)
4850
4851     return ret
4852
4853   def _CheckPayload(self, result):
4854     """Checks if the payload is valid.
4855
4856     @param result: RPC result
4857     @raises errors.OpExecError: If payload is not valid
4858
4859     """
4860     errs = []
4861     if self.op.command == constants.OOB_HEALTH:
4862       if not isinstance(result.payload, list):
4863         errs.append("command 'health' is expected to return a list but got %s" %
4864                     type(result.payload))
4865       else:
4866         for item, status in result.payload:
4867           if status not in constants.OOB_STATUSES:
4868             errs.append("health item '%s' has invalid status '%s'" %
4869                         (item, status))
4870
4871     if self.op.command == constants.OOB_POWER_STATUS:
4872       if not isinstance(result.payload, dict):
4873         errs.append("power-status is expected to return a dict but got %s" %
4874                     type(result.payload))
4875
4876     if self.op.command in [
4877         constants.OOB_POWER_ON,
4878         constants.OOB_POWER_OFF,
4879         constants.OOB_POWER_CYCLE,
4880         ]:
4881       if result.payload is not None:
4882         errs.append("%s is expected to not return payload but got '%s'" %
4883                     (self.op.command, result.payload))
4884
4885     if errs:
4886       raise errors.OpExecError("Check of out-of-band payload failed due to %s" %
4887                                utils.CommaJoin(errs))
4888
4889
4890 class _OsQuery(_QueryBase):
4891   FIELDS = query.OS_FIELDS
4892
4893   def ExpandNames(self, lu):
4894     # Lock all nodes in shared mode
4895     # Temporary removal of locks, should be reverted later
4896     # TODO: reintroduce locks when they are lighter-weight
4897     lu.needed_locks = {}
4898     #self.share_locks[locking.LEVEL_NODE] = 1
4899     #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
4900
4901     # The following variables interact with _QueryBase._GetNames
4902     if self.names:
4903       self.wanted = self.names
4904     else:
4905       self.wanted = locking.ALL_SET
4906
4907     self.do_locking = self.use_locking
4908
4909   def DeclareLocks(self, lu, level):
4910     pass
4911
4912   @staticmethod
4913   def _DiagnoseByOS(rlist):
4914     """Remaps a per-node return list into an a per-os per-node dictionary
4915
4916     @param rlist: a map with node names as keys and OS objects as values
4917
4918     @rtype: dict
4919     @return: a dictionary with osnames as keys and as value another
4920         map, with nodes as keys and tuples of (path, status, diagnose,
4921         variants, parameters, api_versions) as values, eg::
4922
4923           {"debian-etch": {"node1": [(/usr/lib/..., True, "", [], []),
4924                                      (/srv/..., False, "invalid api")],
4925                            "node2": [(/srv/..., True, "", [], [])]}
4926           }
4927
4928     """
4929     all_os = {}
4930     # we build here the list of nodes that didn't fail the RPC (at RPC
4931     # level), so that nodes with a non-responding node daemon don't
4932     # make all OSes invalid
4933     good_nodes = [node_name for node_name in rlist
4934                   if not rlist[node_name].fail_msg]
4935     for node_name, nr in rlist.items():
4936       if nr.fail_msg or not nr.payload:
4937         continue
4938       for (name, path, status, diagnose, variants,
4939            params, api_versions) in nr.payload:
4940         if name not in all_os:
4941           # build a list of nodes for this os containing empty lists
4942           # for each node in node_list
4943           all_os[name] = {}
4944           for nname in good_nodes:
4945             all_os[name][nname] = []
4946         # convert params from [name, help] to (name, help)
4947         params = [tuple(v) for v in params]
4948         all_os[name][node_name].append((path, status, diagnose,
4949                                         variants, params, api_versions))
4950     return all_os
4951
4952   def _GetQueryData(self, lu):
4953     """Computes the list of nodes and their attributes.
4954
4955     """
4956     # Locking is not used
4957     assert not (compat.any(lu.glm.is_owned(level)
4958                            for level in locking.LEVELS
4959                            if level != locking.LEVEL_CLUSTER) or
4960                 self.do_locking or self.use_locking)
4961
4962     valid_nodes = [node.name
4963                    for node in lu.cfg.GetAllNodesInfo().values()
4964                    if not node.offline and node.vm_capable]
4965     pol = self._DiagnoseByOS(lu.rpc.call_os_diagnose(valid_nodes))
4966     cluster = lu.cfg.GetClusterInfo()
4967
4968     data = {}
4969
4970     for (os_name, os_data) in pol.items():
4971       info = query.OsInfo(name=os_name, valid=True, node_status=os_data,
4972                           hidden=(os_name in cluster.hidden_os),
4973                           blacklisted=(os_name in cluster.blacklisted_os))
4974
4975       variants = set()
4976       parameters = set()
4977       api_versions = set()
4978
4979       for idx, osl in enumerate(os_data.values()):
4980         info.valid = bool(info.valid and osl and osl[0][1])
4981         if not info.valid:
4982           break
4983
4984         (node_variants, node_params, node_api) = osl[0][3:6]
4985         if idx == 0:
4986           # First entry
4987           variants.update(node_variants)
4988           parameters.update(node_params)
4989           api_versions.update(node_api)
4990         else:
4991           # Filter out inconsistent values
4992           variants.intersection_update(node_variants)
4993           parameters.intersection_update(node_params)
4994           api_versions.intersection_update(node_api)
4995
4996       info.variants = list(variants)
4997       info.parameters = list(parameters)
4998       info.api_versions = list(api_versions)
4999
5000       data[os_name] = info
5001
5002     # Prepare data in requested order
5003     return [data[name] for name in self._GetNames(lu, pol.keys(), None)
5004             if name in data]
5005
5006
5007 class LUOsDiagnose(NoHooksLU):
5008   """Logical unit for OS diagnose/query.
5009
5010   """
5011   REQ_BGL = False
5012
5013   @staticmethod
5014   def _BuildFilter(fields, names):
5015     """Builds a filter for querying OSes.
5016
5017     """
5018     name_filter = qlang.MakeSimpleFilter("name", names)
5019
5020     # Legacy behaviour: Hide hidden, blacklisted or invalid OSes if the
5021     # respective field is not requested
5022     status_filter = [[qlang.OP_NOT, [qlang.OP_TRUE, fname]]
5023                      for fname in ["hidden", "blacklisted"]
5024                      if fname not in fields]
5025     if "valid" not in fields:
5026       status_filter.append([qlang.OP_TRUE, "valid"])
5027
5028     if status_filter:
5029       status_filter.insert(0, qlang.OP_AND)
5030     else:
5031       status_filter = None
5032
5033     if name_filter and status_filter:
5034       return [qlang.OP_AND, name_filter, status_filter]
5035     elif name_filter:
5036       return name_filter
5037     else:
5038       return status_filter
5039
5040   def CheckArguments(self):
5041     self.oq = _OsQuery(self._BuildFilter(self.op.output_fields, self.op.names),
5042                        self.op.output_fields, False)
5043
5044   def ExpandNames(self):
5045     self.oq.ExpandNames(self)
5046
5047   def Exec(self, feedback_fn):
5048     return self.oq.OldStyleQuery(self)
5049
5050
5051 class _ExtStorageQuery(_QueryBase):
5052   FIELDS = query.EXTSTORAGE_FIELDS
5053
5054   def ExpandNames(self, lu):
5055     # Lock all nodes in shared mode
5056     # Temporary removal of locks, should be reverted later
5057     # TODO: reintroduce locks when they are lighter-weight
5058     lu.needed_locks = {}
5059     #self.share_locks[locking.LEVEL_NODE] = 1
5060     #self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5061
5062     # The following variables interact with _QueryBase._GetNames
5063     if self.names:
5064       self.wanted = self.names
5065     else:
5066       self.wanted = locking.ALL_SET
5067
5068     self.do_locking = self.use_locking
5069
5070   def DeclareLocks(self, lu, level):
5071     pass
5072
5073   @staticmethod
5074   def _DiagnoseByProvider(rlist):
5075     """Remaps a per-node return list into an a per-provider per-node dictionary
5076
5077     @param rlist: a map with node names as keys and ExtStorage objects as values
5078
5079     @rtype: dict
5080     @return: a dictionary with extstorage providers as keys and as
5081         value another map, with nodes as keys and tuples of
5082         (path, status, diagnose, parameters) as values, eg::
5083
5084           {"provider1": {"node1": [(/usr/lib/..., True, "", [])]
5085                          "node2": [(/srv/..., False, "missing file")]
5086                          "node3": [(/srv/..., True, "", [])]
5087           }
5088
5089     """
5090     all_es = {}
5091     # we build here the list of nodes that didn't fail the RPC (at RPC
5092     # level), so that nodes with a non-responding node daemon don't
5093     # make all OSes invalid
5094     good_nodes = [node_name for node_name in rlist
5095                   if not rlist[node_name].fail_msg]
5096     for node_name, nr in rlist.items():
5097       if nr.fail_msg or not nr.payload:
5098         continue
5099       for (name, path, status, diagnose, params) in nr.payload:
5100         if name not in all_es:
5101           # build a list of nodes for this os containing empty lists
5102           # for each node in node_list
5103           all_es[name] = {}
5104           for nname in good_nodes:
5105             all_es[name][nname] = []
5106         # convert params from [name, help] to (name, help)
5107         params = [tuple(v) for v in params]
5108         all_es[name][node_name].append((path, status, diagnose, params))
5109     return all_es
5110
5111   def _GetQueryData(self, lu):
5112     """Computes the list of nodes and their attributes.
5113
5114     """
5115     # Locking is not used
5116     assert not (compat.any(lu.glm.is_owned(level)
5117                            for level in locking.LEVELS
5118                            if level != locking.LEVEL_CLUSTER) or
5119                 self.do_locking or self.use_locking)
5120
5121     valid_nodes = [node.name
5122                    for node in lu.cfg.GetAllNodesInfo().values()
5123                    if not node.offline and node.vm_capable]
5124     pol = self._DiagnoseByProvider(lu.rpc.call_extstorage_diagnose(valid_nodes))
5125
5126     data = {}
5127
5128     nodegroup_list = lu.cfg.GetNodeGroupList()
5129
5130     for (es_name, es_data) in pol.items():
5131       # For every provider compute the nodegroup validity.
5132       # To do this we need to check the validity of each node in es_data
5133       # and then construct the corresponding nodegroup dict:
5134       #      { nodegroup1: status
5135       #        nodegroup2: status
5136       #      }
5137       ndgrp_data = {}
5138       for nodegroup in nodegroup_list:
5139         ndgrp = lu.cfg.GetNodeGroup(nodegroup)
5140
5141         nodegroup_nodes = ndgrp.members
5142         nodegroup_name = ndgrp.name
5143         node_statuses = []
5144
5145         for node in nodegroup_nodes:
5146           if node in valid_nodes:
5147             if es_data[node] != []:
5148               node_status = es_data[node][0][1]
5149               node_statuses.append(node_status)
5150             else:
5151               node_statuses.append(False)
5152
5153         if False in node_statuses:
5154           ndgrp_data[nodegroup_name] = False
5155         else:
5156           ndgrp_data[nodegroup_name] = True
5157
5158       # Compute the provider's parameters
5159       parameters = set()
5160       for idx, esl in enumerate(es_data.values()):
5161         valid = bool(esl and esl[0][1])
5162         if not valid:
5163           break
5164
5165         node_params = esl[0][3]
5166         if idx == 0:
5167           # First entry
5168           parameters.update(node_params)
5169         else:
5170           # Filter out inconsistent values
5171           parameters.intersection_update(node_params)
5172
5173       params = list(parameters)
5174
5175       # Now fill all the info for this provider
5176       info = query.ExtStorageInfo(name=es_name, node_status=es_data,
5177                                   nodegroup_status=ndgrp_data,
5178                                   parameters=params)
5179
5180       data[es_name] = info
5181
5182     # Prepare data in requested order
5183     return [data[name] for name in self._GetNames(lu, pol.keys(), None)
5184             if name in data]
5185
5186
5187 class LUExtStorageDiagnose(NoHooksLU):
5188   """Logical unit for ExtStorage diagnose/query.
5189
5190   """
5191   REQ_BGL = False
5192
5193   def CheckArguments(self):
5194     self.eq = _ExtStorageQuery(qlang.MakeSimpleFilter("name", self.op.names),
5195                                self.op.output_fields, False)
5196
5197   def ExpandNames(self):
5198     self.eq.ExpandNames(self)
5199
5200   def Exec(self, feedback_fn):
5201     return self.eq.OldStyleQuery(self)
5202
5203
5204 class LUNodeRemove(LogicalUnit):
5205   """Logical unit for removing a node.
5206
5207   """
5208   HPATH = "node-remove"
5209   HTYPE = constants.HTYPE_NODE
5210
5211   def BuildHooksEnv(self):
5212     """Build hooks env.
5213
5214     """
5215     return {
5216       "OP_TARGET": self.op.node_name,
5217       "NODE_NAME": self.op.node_name,
5218       }
5219
5220   def BuildHooksNodes(self):
5221     """Build hooks nodes.
5222
5223     This doesn't run on the target node in the pre phase as a failed
5224     node would then be impossible to remove.
5225
5226     """
5227     all_nodes = self.cfg.GetNodeList()
5228     try:
5229       all_nodes.remove(self.op.node_name)
5230     except ValueError:
5231       pass
5232     return (all_nodes, all_nodes)
5233
5234   def CheckPrereq(self):
5235     """Check prerequisites.
5236
5237     This checks:
5238      - the node exists in the configuration
5239      - it does not have primary or secondary instances
5240      - it's not the master
5241
5242     Any errors are signaled by raising errors.OpPrereqError.
5243
5244     """
5245     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5246     node = self.cfg.GetNodeInfo(self.op.node_name)
5247     assert node is not None
5248
5249     masternode = self.cfg.GetMasterNode()
5250     if node.name == masternode:
5251       raise errors.OpPrereqError("Node is the master node, failover to another"
5252                                  " node is required", errors.ECODE_INVAL)
5253
5254     for instance_name, instance in self.cfg.GetAllInstancesInfo().items():
5255       if node.name in instance.all_nodes:
5256         raise errors.OpPrereqError("Instance %s is still running on the node,"
5257                                    " please remove first" % instance_name,
5258                                    errors.ECODE_INVAL)
5259     self.op.node_name = node.name
5260     self.node = node
5261
5262   def Exec(self, feedback_fn):
5263     """Removes the node from the cluster.
5264
5265     """
5266     node = self.node
5267     logging.info("Stopping the node daemon and removing configs from node %s",
5268                  node.name)
5269
5270     modify_ssh_setup = self.cfg.GetClusterInfo().modify_ssh_setup
5271
5272     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER), \
5273       "Not owning BGL"
5274
5275     # Promote nodes to master candidate as needed
5276     _AdjustCandidatePool(self, exceptions=[node.name])
5277     self.context.RemoveNode(node.name)
5278
5279     # Run post hooks on the node before it's removed
5280     _RunPostHook(self, node.name)
5281
5282     result = self.rpc.call_node_leave_cluster(node.name, modify_ssh_setup)
5283     msg = result.fail_msg
5284     if msg:
5285       self.LogWarning("Errors encountered on the remote node while leaving"
5286                       " the cluster: %s", msg)
5287
5288     # Remove node from our /etc/hosts
5289     if self.cfg.GetClusterInfo().modify_etc_hosts:
5290       master_node = self.cfg.GetMasterNode()
5291       result = self.rpc.call_etc_hosts_modify(master_node,
5292                                               constants.ETC_HOSTS_REMOVE,
5293                                               node.name, None)
5294       result.Raise("Can't update hosts file with new host data")
5295       _RedistributeAncillaryFiles(self)
5296
5297
5298 class _NodeQuery(_QueryBase):
5299   FIELDS = query.NODE_FIELDS
5300
5301   def ExpandNames(self, lu):
5302     lu.needed_locks = {}
5303     lu.share_locks = _ShareAll()
5304
5305     if self.names:
5306       self.wanted = _GetWantedNodes(lu, self.names)
5307     else:
5308       self.wanted = locking.ALL_SET
5309
5310     self.do_locking = (self.use_locking and
5311                        query.NQ_LIVE in self.requested_data)
5312
5313     if self.do_locking:
5314       # If any non-static field is requested we need to lock the nodes
5315       lu.needed_locks[locking.LEVEL_NODE] = self.wanted
5316
5317   def DeclareLocks(self, lu, level):
5318     pass
5319
5320   def _GetQueryData(self, lu):
5321     """Computes the list of nodes and their attributes.
5322
5323     """
5324     all_info = lu.cfg.GetAllNodesInfo()
5325
5326     nodenames = self._GetNames(lu, all_info.keys(), locking.LEVEL_NODE)
5327
5328     # Gather data as requested
5329     if query.NQ_LIVE in self.requested_data:
5330       # filter out non-vm_capable nodes
5331       toquery_nodes = [name for name in nodenames if all_info[name].vm_capable]
5332
5333       node_data = lu.rpc.call_node_info(toquery_nodes, [lu.cfg.GetVGName()],
5334                                         [lu.cfg.GetHypervisorType()])
5335       live_data = dict((name, _MakeLegacyNodeInfo(nresult.payload))
5336                        for (name, nresult) in node_data.items()
5337                        if not nresult.fail_msg and nresult.payload)
5338     else:
5339       live_data = None
5340
5341     if query.NQ_INST in self.requested_data:
5342       node_to_primary = dict([(name, set()) for name in nodenames])
5343       node_to_secondary = dict([(name, set()) for name in nodenames])
5344
5345       inst_data = lu.cfg.GetAllInstancesInfo()
5346
5347       for inst in inst_data.values():
5348         if inst.primary_node in node_to_primary:
5349           node_to_primary[inst.primary_node].add(inst.name)
5350         for secnode in inst.secondary_nodes:
5351           if secnode in node_to_secondary:
5352             node_to_secondary[secnode].add(inst.name)
5353     else:
5354       node_to_primary = None
5355       node_to_secondary = None
5356
5357     if query.NQ_OOB in self.requested_data:
5358       oob_support = dict((name, bool(_SupportsOob(lu.cfg, node)))
5359                          for name, node in all_info.iteritems())
5360     else:
5361       oob_support = None
5362
5363     if query.NQ_GROUP in self.requested_data:
5364       groups = lu.cfg.GetAllNodeGroupsInfo()
5365     else:
5366       groups = {}
5367
5368     return query.NodeQueryData([all_info[name] for name in nodenames],
5369                                live_data, lu.cfg.GetMasterNode(),
5370                                node_to_primary, node_to_secondary, groups,
5371                                oob_support, lu.cfg.GetClusterInfo())
5372
5373
5374 class LUNodeQuery(NoHooksLU):
5375   """Logical unit for querying nodes.
5376
5377   """
5378   # pylint: disable=W0142
5379   REQ_BGL = False
5380
5381   def CheckArguments(self):
5382     self.nq = _NodeQuery(qlang.MakeSimpleFilter("name", self.op.names),
5383                          self.op.output_fields, self.op.use_locking)
5384
5385   def ExpandNames(self):
5386     self.nq.ExpandNames(self)
5387
5388   def DeclareLocks(self, level):
5389     self.nq.DeclareLocks(self, level)
5390
5391   def Exec(self, feedback_fn):
5392     return self.nq.OldStyleQuery(self)
5393
5394
5395 class LUNodeQueryvols(NoHooksLU):
5396   """Logical unit for getting volumes on node(s).
5397
5398   """
5399   REQ_BGL = False
5400   _FIELDS_DYNAMIC = utils.FieldSet("phys", "vg", "name", "size", "instance")
5401   _FIELDS_STATIC = utils.FieldSet("node")
5402
5403   def CheckArguments(self):
5404     _CheckOutputFields(static=self._FIELDS_STATIC,
5405                        dynamic=self._FIELDS_DYNAMIC,
5406                        selected=self.op.output_fields)
5407
5408   def ExpandNames(self):
5409     self.share_locks = _ShareAll()
5410     self.needed_locks = {}
5411
5412     if not self.op.nodes:
5413       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5414     else:
5415       self.needed_locks[locking.LEVEL_NODE] = \
5416         _GetWantedNodes(self, self.op.nodes)
5417
5418   def Exec(self, feedback_fn):
5419     """Computes the list of nodes and their attributes.
5420
5421     """
5422     nodenames = self.owned_locks(locking.LEVEL_NODE)
5423     volumes = self.rpc.call_node_volumes(nodenames)
5424
5425     ilist = self.cfg.GetAllInstancesInfo()
5426     vol2inst = _MapInstanceDisksToNodes(ilist.values())
5427
5428     output = []
5429     for node in nodenames:
5430       nresult = volumes[node]
5431       if nresult.offline:
5432         continue
5433       msg = nresult.fail_msg
5434       if msg:
5435         self.LogWarning("Can't compute volume data on node %s: %s", node, msg)
5436         continue
5437
5438       node_vols = sorted(nresult.payload,
5439                          key=operator.itemgetter("dev"))
5440
5441       for vol in node_vols:
5442         node_output = []
5443         for field in self.op.output_fields:
5444           if field == "node":
5445             val = node
5446           elif field == "phys":
5447             val = vol["dev"]
5448           elif field == "vg":
5449             val = vol["vg"]
5450           elif field == "name":
5451             val = vol["name"]
5452           elif field == "size":
5453             val = int(float(vol["size"]))
5454           elif field == "instance":
5455             val = vol2inst.get((node, vol["vg"] + "/" + vol["name"]), "-")
5456           else:
5457             raise errors.ParameterError(field)
5458           node_output.append(str(val))
5459
5460         output.append(node_output)
5461
5462     return output
5463
5464
5465 class LUNodeQueryStorage(NoHooksLU):
5466   """Logical unit for getting information on storage units on node(s).
5467
5468   """
5469   _FIELDS_STATIC = utils.FieldSet(constants.SF_NODE)
5470   REQ_BGL = False
5471
5472   def CheckArguments(self):
5473     _CheckOutputFields(static=self._FIELDS_STATIC,
5474                        dynamic=utils.FieldSet(*constants.VALID_STORAGE_FIELDS),
5475                        selected=self.op.output_fields)
5476
5477   def ExpandNames(self):
5478     self.share_locks = _ShareAll()
5479     self.needed_locks = {}
5480
5481     if self.op.nodes:
5482       self.needed_locks[locking.LEVEL_NODE] = \
5483         _GetWantedNodes(self, self.op.nodes)
5484     else:
5485       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
5486
5487   def Exec(self, feedback_fn):
5488     """Computes the list of nodes and their attributes.
5489
5490     """
5491     self.nodes = self.owned_locks(locking.LEVEL_NODE)
5492
5493     # Always get name to sort by
5494     if constants.SF_NAME in self.op.output_fields:
5495       fields = self.op.output_fields[:]
5496     else:
5497       fields = [constants.SF_NAME] + self.op.output_fields
5498
5499     # Never ask for node or type as it's only known to the LU
5500     for extra in [constants.SF_NODE, constants.SF_TYPE]:
5501       while extra in fields:
5502         fields.remove(extra)
5503
5504     field_idx = dict([(name, idx) for (idx, name) in enumerate(fields)])
5505     name_idx = field_idx[constants.SF_NAME]
5506
5507     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
5508     data = self.rpc.call_storage_list(self.nodes,
5509                                       self.op.storage_type, st_args,
5510                                       self.op.name, fields)
5511
5512     result = []
5513
5514     for node in utils.NiceSort(self.nodes):
5515       nresult = data[node]
5516       if nresult.offline:
5517         continue
5518
5519       msg = nresult.fail_msg
5520       if msg:
5521         self.LogWarning("Can't get storage data from node %s: %s", node, msg)
5522         continue
5523
5524       rows = dict([(row[name_idx], row) for row in nresult.payload])
5525
5526       for name in utils.NiceSort(rows.keys()):
5527         row = rows[name]
5528
5529         out = []
5530
5531         for field in self.op.output_fields:
5532           if field == constants.SF_NODE:
5533             val = node
5534           elif field == constants.SF_TYPE:
5535             val = self.op.storage_type
5536           elif field in field_idx:
5537             val = row[field_idx[field]]
5538           else:
5539             raise errors.ParameterError(field)
5540
5541           out.append(val)
5542
5543         result.append(out)
5544
5545     return result
5546
5547
5548 class _InstanceQuery(_QueryBase):
5549   FIELDS = query.INSTANCE_FIELDS
5550
5551   def ExpandNames(self, lu):
5552     lu.needed_locks = {}
5553     lu.share_locks = _ShareAll()
5554
5555     if self.names:
5556       self.wanted = _GetWantedInstances(lu, self.names)
5557     else:
5558       self.wanted = locking.ALL_SET
5559
5560     self.do_locking = (self.use_locking and
5561                        query.IQ_LIVE in self.requested_data)
5562     if self.do_locking:
5563       lu.needed_locks[locking.LEVEL_INSTANCE] = self.wanted
5564       lu.needed_locks[locking.LEVEL_NODEGROUP] = []
5565       lu.needed_locks[locking.LEVEL_NODE] = []
5566       lu.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
5567
5568     self.do_grouplocks = (self.do_locking and
5569                           query.IQ_NODES in self.requested_data)
5570
5571   def DeclareLocks(self, lu, level):
5572     if self.do_locking:
5573       if level == locking.LEVEL_NODEGROUP and self.do_grouplocks:
5574         assert not lu.needed_locks[locking.LEVEL_NODEGROUP]
5575
5576         # Lock all groups used by instances optimistically; this requires going
5577         # via the node before it's locked, requiring verification later on
5578         lu.needed_locks[locking.LEVEL_NODEGROUP] = \
5579           set(group_uuid
5580               for instance_name in lu.owned_locks(locking.LEVEL_INSTANCE)
5581               for group_uuid in lu.cfg.GetInstanceNodeGroups(instance_name))
5582       elif level == locking.LEVEL_NODE:
5583         lu._LockInstancesNodes() # pylint: disable=W0212
5584
5585   @staticmethod
5586   def _CheckGroupLocks(lu):
5587     owned_instances = frozenset(lu.owned_locks(locking.LEVEL_INSTANCE))
5588     owned_groups = frozenset(lu.owned_locks(locking.LEVEL_NODEGROUP))
5589
5590     # Check if node groups for locked instances are still correct
5591     for instance_name in owned_instances:
5592       _CheckInstanceNodeGroups(lu.cfg, instance_name, owned_groups)
5593
5594   def _GetQueryData(self, lu):
5595     """Computes the list of instances and their attributes.
5596
5597     """
5598     if self.do_grouplocks:
5599       self._CheckGroupLocks(lu)
5600
5601     cluster = lu.cfg.GetClusterInfo()
5602     all_info = lu.cfg.GetAllInstancesInfo()
5603
5604     instance_names = self._GetNames(lu, all_info.keys(), locking.LEVEL_INSTANCE)
5605
5606     instance_list = [all_info[name] for name in instance_names]
5607     nodes = frozenset(itertools.chain(*(inst.all_nodes
5608                                         for inst in instance_list)))
5609     hv_list = list(set([inst.hypervisor for inst in instance_list]))
5610     bad_nodes = []
5611     offline_nodes = []
5612     wrongnode_inst = set()
5613
5614     # Gather data as requested
5615     if self.requested_data & set([query.IQ_LIVE, query.IQ_CONSOLE]):
5616       live_data = {}
5617       node_data = lu.rpc.call_all_instances_info(nodes, hv_list)
5618       for name in nodes:
5619         result = node_data[name]
5620         if result.offline:
5621           # offline nodes will be in both lists
5622           assert result.fail_msg
5623           offline_nodes.append(name)
5624         if result.fail_msg:
5625           bad_nodes.append(name)
5626         elif result.payload:
5627           for inst in result.payload:
5628             if inst in all_info:
5629               if all_info[inst].primary_node == name:
5630                 live_data.update(result.payload)
5631               else:
5632                 wrongnode_inst.add(inst)
5633             else:
5634               # orphan instance; we don't list it here as we don't
5635               # handle this case yet in the output of instance listing
5636               logging.warning("Orphan instance '%s' found on node %s",
5637                               inst, name)
5638         # else no instance is alive
5639     else:
5640       live_data = {}
5641
5642     if query.IQ_DISKUSAGE in self.requested_data:
5643       disk_usage = dict((inst.name,
5644                          _ComputeDiskSize(inst.disk_template,
5645                                           [{constants.IDISK_SIZE: disk.size}
5646                                            for disk in inst.disks]))
5647                         for inst in instance_list)
5648     else:
5649       disk_usage = None
5650
5651     if query.IQ_CONSOLE in self.requested_data:
5652       consinfo = {}
5653       for inst in instance_list:
5654         if inst.name in live_data:
5655           # Instance is running
5656           consinfo[inst.name] = _GetInstanceConsole(cluster, inst)
5657         else:
5658           consinfo[inst.name] = None
5659       assert set(consinfo.keys()) == set(instance_names)
5660     else:
5661       consinfo = None
5662
5663     if query.IQ_NODES in self.requested_data:
5664       node_names = set(itertools.chain(*map(operator.attrgetter("all_nodes"),
5665                                             instance_list)))
5666       nodes = dict(lu.cfg.GetMultiNodeInfo(node_names))
5667       groups = dict((uuid, lu.cfg.GetNodeGroup(uuid))
5668                     for uuid in set(map(operator.attrgetter("group"),
5669                                         nodes.values())))
5670     else:
5671       nodes = None
5672       groups = None
5673
5674     return query.InstanceQueryData(instance_list, lu.cfg.GetClusterInfo(),
5675                                    disk_usage, offline_nodes, bad_nodes,
5676                                    live_data, wrongnode_inst, consinfo,
5677                                    nodes, groups)
5678
5679
5680 class LUQuery(NoHooksLU):
5681   """Query for resources/items of a certain kind.
5682
5683   """
5684   # pylint: disable=W0142
5685   REQ_BGL = False
5686
5687   def CheckArguments(self):
5688     qcls = _GetQueryImplementation(self.op.what)
5689
5690     self.impl = qcls(self.op.qfilter, self.op.fields, self.op.use_locking)
5691
5692   def ExpandNames(self):
5693     self.impl.ExpandNames(self)
5694
5695   def DeclareLocks(self, level):
5696     self.impl.DeclareLocks(self, level)
5697
5698   def Exec(self, feedback_fn):
5699     return self.impl.NewStyleQuery(self)
5700
5701
5702 class LUQueryFields(NoHooksLU):
5703   """Query for resources/items of a certain kind.
5704
5705   """
5706   # pylint: disable=W0142
5707   REQ_BGL = False
5708
5709   def CheckArguments(self):
5710     self.qcls = _GetQueryImplementation(self.op.what)
5711
5712   def ExpandNames(self):
5713     self.needed_locks = {}
5714
5715   def Exec(self, feedback_fn):
5716     return query.QueryFields(self.qcls.FIELDS, self.op.fields)
5717
5718
5719 class LUNodeModifyStorage(NoHooksLU):
5720   """Logical unit for modifying a storage volume on a node.
5721
5722   """
5723   REQ_BGL = False
5724
5725   def CheckArguments(self):
5726     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
5727
5728     storage_type = self.op.storage_type
5729
5730     try:
5731       modifiable = constants.MODIFIABLE_STORAGE_FIELDS[storage_type]
5732     except KeyError:
5733       raise errors.OpPrereqError("Storage units of type '%s' can not be"
5734                                  " modified" % storage_type,
5735                                  errors.ECODE_INVAL)
5736
5737     diff = set(self.op.changes.keys()) - modifiable
5738     if diff:
5739       raise errors.OpPrereqError("The following fields can not be modified for"
5740                                  " storage units of type '%s': %r" %
5741                                  (storage_type, list(diff)),
5742                                  errors.ECODE_INVAL)
5743
5744   def ExpandNames(self):
5745     self.needed_locks = {
5746       locking.LEVEL_NODE: self.op.node_name,
5747       }
5748
5749   def Exec(self, feedback_fn):
5750     """Computes the list of nodes and their attributes.
5751
5752     """
5753     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
5754     result = self.rpc.call_storage_modify(self.op.node_name,
5755                                           self.op.storage_type, st_args,
5756                                           self.op.name, self.op.changes)
5757     result.Raise("Failed to modify storage unit '%s' on %s" %
5758                  (self.op.name, self.op.node_name))
5759
5760
5761 class LUNodeAdd(LogicalUnit):
5762   """Logical unit for adding node to the cluster.
5763
5764   """
5765   HPATH = "node-add"
5766   HTYPE = constants.HTYPE_NODE
5767   _NFLAGS = ["master_capable", "vm_capable"]
5768
5769   def CheckArguments(self):
5770     self.primary_ip_family = self.cfg.GetPrimaryIPFamily()
5771     # validate/normalize the node name
5772     self.hostname = netutils.GetHostname(name=self.op.node_name,
5773                                          family=self.primary_ip_family)
5774     self.op.node_name = self.hostname.name
5775
5776     if self.op.readd and self.op.node_name == self.cfg.GetMasterNode():
5777       raise errors.OpPrereqError("Cannot readd the master node",
5778                                  errors.ECODE_STATE)
5779
5780     if self.op.readd and self.op.group:
5781       raise errors.OpPrereqError("Cannot pass a node group when a node is"
5782                                  " being readded", errors.ECODE_INVAL)
5783
5784   def BuildHooksEnv(self):
5785     """Build hooks env.
5786
5787     This will run on all nodes before, and on all nodes + the new node after.
5788
5789     """
5790     return {
5791       "OP_TARGET": self.op.node_name,
5792       "NODE_NAME": self.op.node_name,
5793       "NODE_PIP": self.op.primary_ip,
5794       "NODE_SIP": self.op.secondary_ip,
5795       "MASTER_CAPABLE": str(self.op.master_capable),
5796       "VM_CAPABLE": str(self.op.vm_capable),
5797       }
5798
5799   def BuildHooksNodes(self):
5800     """Build hooks nodes.
5801
5802     """
5803     # Exclude added node
5804     pre_nodes = list(set(self.cfg.GetNodeList()) - set([self.op.node_name]))
5805     post_nodes = pre_nodes + [self.op.node_name, ]
5806
5807     return (pre_nodes, post_nodes)
5808
5809   def CheckPrereq(self):
5810     """Check prerequisites.
5811
5812     This checks:
5813      - the new node is not already in the config
5814      - it is resolvable
5815      - its parameters (single/dual homed) matches the cluster
5816
5817     Any errors are signaled by raising errors.OpPrereqError.
5818
5819     """
5820     cfg = self.cfg
5821     hostname = self.hostname
5822     node = hostname.name
5823     primary_ip = self.op.primary_ip = hostname.ip
5824     if self.op.secondary_ip is None:
5825       if self.primary_ip_family == netutils.IP6Address.family:
5826         raise errors.OpPrereqError("When using a IPv6 primary address, a valid"
5827                                    " IPv4 address must be given as secondary",
5828                                    errors.ECODE_INVAL)
5829       self.op.secondary_ip = primary_ip
5830
5831     secondary_ip = self.op.secondary_ip
5832     if not netutils.IP4Address.IsValid(secondary_ip):
5833       raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
5834                                  " address" % secondary_ip, errors.ECODE_INVAL)
5835
5836     node_list = cfg.GetNodeList()
5837     if not self.op.readd and node in node_list:
5838       raise errors.OpPrereqError("Node %s is already in the configuration" %
5839                                  node, errors.ECODE_EXISTS)
5840     elif self.op.readd and node not in node_list:
5841       raise errors.OpPrereqError("Node %s is not in the configuration" % node,
5842                                  errors.ECODE_NOENT)
5843
5844     self.changed_primary_ip = False
5845
5846     for existing_node_name, existing_node in cfg.GetMultiNodeInfo(node_list):
5847       if self.op.readd and node == existing_node_name:
5848         if existing_node.secondary_ip != secondary_ip:
5849           raise errors.OpPrereqError("Readded node doesn't have the same IP"
5850                                      " address configuration as before",
5851                                      errors.ECODE_INVAL)
5852         if existing_node.primary_ip != primary_ip:
5853           self.changed_primary_ip = True
5854
5855         continue
5856
5857       if (existing_node.primary_ip == primary_ip or
5858           existing_node.secondary_ip == primary_ip or
5859           existing_node.primary_ip == secondary_ip or
5860           existing_node.secondary_ip == secondary_ip):
5861         raise errors.OpPrereqError("New node ip address(es) conflict with"
5862                                    " existing node %s" % existing_node.name,
5863                                    errors.ECODE_NOTUNIQUE)
5864
5865     # After this 'if' block, None is no longer a valid value for the
5866     # _capable op attributes
5867     if self.op.readd:
5868       old_node = self.cfg.GetNodeInfo(node)
5869       assert old_node is not None, "Can't retrieve locked node %s" % node
5870       for attr in self._NFLAGS:
5871         if getattr(self.op, attr) is None:
5872           setattr(self.op, attr, getattr(old_node, attr))
5873     else:
5874       for attr in self._NFLAGS:
5875         if getattr(self.op, attr) is None:
5876           setattr(self.op, attr, True)
5877
5878     if self.op.readd and not self.op.vm_capable:
5879       pri, sec = cfg.GetNodeInstances(node)
5880       if pri or sec:
5881         raise errors.OpPrereqError("Node %s being re-added with vm_capable"
5882                                    " flag set to false, but it already holds"
5883                                    " instances" % node,
5884                                    errors.ECODE_STATE)
5885
5886     # check that the type of the node (single versus dual homed) is the
5887     # same as for the master
5888     myself = cfg.GetNodeInfo(self.cfg.GetMasterNode())
5889     master_singlehomed = myself.secondary_ip == myself.primary_ip
5890     newbie_singlehomed = secondary_ip == primary_ip
5891     if master_singlehomed != newbie_singlehomed:
5892       if master_singlehomed:
5893         raise errors.OpPrereqError("The master has no secondary ip but the"
5894                                    " new node has one",
5895                                    errors.ECODE_INVAL)
5896       else:
5897         raise errors.OpPrereqError("The master has a secondary ip but the"
5898                                    " new node doesn't have one",
5899                                    errors.ECODE_INVAL)
5900
5901     # checks reachability
5902     if not netutils.TcpPing(primary_ip, constants.DEFAULT_NODED_PORT):
5903       raise errors.OpPrereqError("Node not reachable by ping",
5904                                  errors.ECODE_ENVIRON)
5905
5906     if not newbie_singlehomed:
5907       # check reachability from my secondary ip to newbie's secondary ip
5908       if not netutils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
5909                            source=myself.secondary_ip):
5910         raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
5911                                    " based ping to node daemon port",
5912                                    errors.ECODE_ENVIRON)
5913
5914     if self.op.readd:
5915       exceptions = [node]
5916     else:
5917       exceptions = []
5918
5919     if self.op.master_capable:
5920       self.master_candidate = _DecideSelfPromotion(self, exceptions=exceptions)
5921     else:
5922       self.master_candidate = False
5923
5924     if self.op.readd:
5925       self.new_node = old_node
5926     else:
5927       node_group = cfg.LookupNodeGroup(self.op.group)
5928       self.new_node = objects.Node(name=node,
5929                                    primary_ip=primary_ip,
5930                                    secondary_ip=secondary_ip,
5931                                    master_candidate=self.master_candidate,
5932                                    offline=False, drained=False,
5933                                    group=node_group)
5934
5935     if self.op.ndparams:
5936       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
5937
5938     if self.op.hv_state:
5939       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None)
5940
5941     if self.op.disk_state:
5942       self.new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state, None)
5943
5944     # TODO: If we need to have multiple DnsOnlyRunner we probably should make
5945     #       it a property on the base class.
5946     result = rpc.DnsOnlyRunner().call_version([node])[node]
5947     result.Raise("Can't get version information from node %s" % node)
5948     if constants.PROTOCOL_VERSION == result.payload:
5949       logging.info("Communication to node %s fine, sw version %s match",
5950                    node, result.payload)
5951     else:
5952       raise errors.OpPrereqError("Version mismatch master version %s,"
5953                                  " node version %s" %
5954                                  (constants.PROTOCOL_VERSION, result.payload),
5955                                  errors.ECODE_ENVIRON)
5956
5957   def Exec(self, feedback_fn):
5958     """Adds the new node to the cluster.
5959
5960     """
5961     new_node = self.new_node
5962     node = new_node.name
5963
5964     assert locking.BGL in self.owned_locks(locking.LEVEL_CLUSTER), \
5965       "Not owning BGL"
5966
5967     # We adding a new node so we assume it's powered
5968     new_node.powered = True
5969
5970     # for re-adds, reset the offline/drained/master-candidate flags;
5971     # we need to reset here, otherwise offline would prevent RPC calls
5972     # later in the procedure; this also means that if the re-add
5973     # fails, we are left with a non-offlined, broken node
5974     if self.op.readd:
5975       new_node.drained = new_node.offline = False # pylint: disable=W0201
5976       self.LogInfo("Readding a node, the offline/drained flags were reset")
5977       # if we demote the node, we do cleanup later in the procedure
5978       new_node.master_candidate = self.master_candidate
5979       if self.changed_primary_ip:
5980         new_node.primary_ip = self.op.primary_ip
5981
5982     # copy the master/vm_capable flags
5983     for attr in self._NFLAGS:
5984       setattr(new_node, attr, getattr(self.op, attr))
5985
5986     # notify the user about any possible mc promotion
5987     if new_node.master_candidate:
5988       self.LogInfo("Node will be a master candidate")
5989
5990     if self.op.ndparams:
5991       new_node.ndparams = self.op.ndparams
5992     else:
5993       new_node.ndparams = {}
5994
5995     if self.op.hv_state:
5996       new_node.hv_state_static = self.new_hv_state
5997
5998     if self.op.disk_state:
5999       new_node.disk_state_static = self.new_disk_state
6000
6001     # Add node to our /etc/hosts, and add key to known_hosts
6002     if self.cfg.GetClusterInfo().modify_etc_hosts:
6003       master_node = self.cfg.GetMasterNode()
6004       result = self.rpc.call_etc_hosts_modify(master_node,
6005                                               constants.ETC_HOSTS_ADD,
6006                                               self.hostname.name,
6007                                               self.hostname.ip)
6008       result.Raise("Can't update hosts file with new host data")
6009
6010     if new_node.secondary_ip != new_node.primary_ip:
6011       _CheckNodeHasSecondaryIP(self, new_node.name, new_node.secondary_ip,
6012                                False)
6013
6014     node_verify_list = [self.cfg.GetMasterNode()]
6015     node_verify_param = {
6016       constants.NV_NODELIST: ([node], {}),
6017       # TODO: do a node-net-test as well?
6018     }
6019
6020     result = self.rpc.call_node_verify(node_verify_list, node_verify_param,
6021                                        self.cfg.GetClusterName())
6022     for verifier in node_verify_list:
6023       result[verifier].Raise("Cannot communicate with node %s" % verifier)
6024       nl_payload = result[verifier].payload[constants.NV_NODELIST]
6025       if nl_payload:
6026         for failed in nl_payload:
6027           feedback_fn("ssh/hostname verification failed"
6028                       " (checking from %s): %s" %
6029                       (verifier, nl_payload[failed]))
6030         raise errors.OpExecError("ssh/hostname verification failed")
6031
6032     if self.op.readd:
6033       _RedistributeAncillaryFiles(self)
6034       self.context.ReaddNode(new_node)
6035       # make sure we redistribute the config
6036       self.cfg.Update(new_node, feedback_fn)
6037       # and make sure the new node will not have old files around
6038       if not new_node.master_candidate:
6039         result = self.rpc.call_node_demote_from_mc(new_node.name)
6040         msg = result.fail_msg
6041         if msg:
6042           self.LogWarning("Node failed to demote itself from master"
6043                           " candidate status: %s" % msg)
6044     else:
6045       _RedistributeAncillaryFiles(self, additional_nodes=[node],
6046                                   additional_vm=self.op.vm_capable)
6047       self.context.AddNode(new_node, self.proc.GetECId())
6048
6049
6050 class LUNodeSetParams(LogicalUnit):
6051   """Modifies the parameters of a node.
6052
6053   @cvar _F2R: a dictionary from tuples of flags (mc, drained, offline)
6054       to the node role (as _ROLE_*)
6055   @cvar _R2F: a dictionary from node role to tuples of flags
6056   @cvar _FLAGS: a list of attribute names corresponding to the flags
6057
6058   """
6059   HPATH = "node-modify"
6060   HTYPE = constants.HTYPE_NODE
6061   REQ_BGL = False
6062   (_ROLE_CANDIDATE, _ROLE_DRAINED, _ROLE_OFFLINE, _ROLE_REGULAR) = range(4)
6063   _F2R = {
6064     (True, False, False): _ROLE_CANDIDATE,
6065     (False, True, False): _ROLE_DRAINED,
6066     (False, False, True): _ROLE_OFFLINE,
6067     (False, False, False): _ROLE_REGULAR,
6068     }
6069   _R2F = dict((v, k) for k, v in _F2R.items())
6070   _FLAGS = ["master_candidate", "drained", "offline"]
6071
6072   def CheckArguments(self):
6073     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
6074     all_mods = [self.op.offline, self.op.master_candidate, self.op.drained,
6075                 self.op.master_capable, self.op.vm_capable,
6076                 self.op.secondary_ip, self.op.ndparams, self.op.hv_state,
6077                 self.op.disk_state]
6078     if all_mods.count(None) == len(all_mods):
6079       raise errors.OpPrereqError("Please pass at least one modification",
6080                                  errors.ECODE_INVAL)
6081     if all_mods.count(True) > 1:
6082       raise errors.OpPrereqError("Can't set the node into more than one"
6083                                  " state at the same time",
6084                                  errors.ECODE_INVAL)
6085
6086     # Boolean value that tells us whether we might be demoting from MC
6087     self.might_demote = (self.op.master_candidate == False or
6088                          self.op.offline == True or
6089                          self.op.drained == True or
6090                          self.op.master_capable == False)
6091
6092     if self.op.secondary_ip:
6093       if not netutils.IP4Address.IsValid(self.op.secondary_ip):
6094         raise errors.OpPrereqError("Secondary IP (%s) needs to be a valid IPv4"
6095                                    " address" % self.op.secondary_ip,
6096                                    errors.ECODE_INVAL)
6097
6098     self.lock_all = self.op.auto_promote and self.might_demote
6099     self.lock_instances = self.op.secondary_ip is not None
6100
6101   def _InstanceFilter(self, instance):
6102     """Filter for getting affected instances.
6103
6104     """
6105     return (instance.disk_template in constants.DTS_INT_MIRROR and
6106             self.op.node_name in instance.all_nodes)
6107
6108   def ExpandNames(self):
6109     if self.lock_all:
6110       self.needed_locks = {locking.LEVEL_NODE: locking.ALL_SET}
6111     else:
6112       self.needed_locks = {locking.LEVEL_NODE: self.op.node_name}
6113
6114     # Since modifying a node can have severe effects on currently running
6115     # operations the resource lock is at least acquired in shared mode
6116     self.needed_locks[locking.LEVEL_NODE_RES] = \
6117       self.needed_locks[locking.LEVEL_NODE]
6118
6119     # Get node resource and instance locks in shared mode; they are not used
6120     # for anything but read-only access
6121     self.share_locks[locking.LEVEL_NODE_RES] = 1
6122     self.share_locks[locking.LEVEL_INSTANCE] = 1
6123
6124     if self.lock_instances:
6125       self.needed_locks[locking.LEVEL_INSTANCE] = \
6126         frozenset(self.cfg.GetInstancesInfoByFilter(self._InstanceFilter))
6127
6128   def BuildHooksEnv(self):
6129     """Build hooks env.
6130
6131     This runs on the master node.
6132
6133     """
6134     return {
6135       "OP_TARGET": self.op.node_name,
6136       "MASTER_CANDIDATE": str(self.op.master_candidate),
6137       "OFFLINE": str(self.op.offline),
6138       "DRAINED": str(self.op.drained),
6139       "MASTER_CAPABLE": str(self.op.master_capable),
6140       "VM_CAPABLE": str(self.op.vm_capable),
6141       }
6142
6143   def BuildHooksNodes(self):
6144     """Build hooks nodes.
6145
6146     """
6147     nl = [self.cfg.GetMasterNode(), self.op.node_name]
6148     return (nl, nl)
6149
6150   def CheckPrereq(self):
6151     """Check prerequisites.
6152
6153     This only checks the instance list against the existing names.
6154
6155     """
6156     node = self.node = self.cfg.GetNodeInfo(self.op.node_name)
6157
6158     if self.lock_instances:
6159       affected_instances = \
6160         self.cfg.GetInstancesInfoByFilter(self._InstanceFilter)
6161
6162       # Verify instance locks
6163       owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
6164       wanted_instances = frozenset(affected_instances.keys())
6165       if wanted_instances - owned_instances:
6166         raise errors.OpPrereqError("Instances affected by changing node %s's"
6167                                    " secondary IP address have changed since"
6168                                    " locks were acquired, wanted '%s', have"
6169                                    " '%s'; retry the operation" %
6170                                    (self.op.node_name,
6171                                     utils.CommaJoin(wanted_instances),
6172                                     utils.CommaJoin(owned_instances)),
6173                                    errors.ECODE_STATE)
6174     else:
6175       affected_instances = None
6176
6177     if (self.op.master_candidate is not None or
6178         self.op.drained is not None or
6179         self.op.offline is not None):
6180       # we can't change the master's node flags
6181       if self.op.node_name == self.cfg.GetMasterNode():
6182         raise errors.OpPrereqError("The master role can be changed"
6183                                    " only via master-failover",
6184                                    errors.ECODE_INVAL)
6185
6186     if self.op.master_candidate and not node.master_capable:
6187       raise errors.OpPrereqError("Node %s is not master capable, cannot make"
6188                                  " it a master candidate" % node.name,
6189                                  errors.ECODE_STATE)
6190
6191     if self.op.vm_capable == False:
6192       (ipri, isec) = self.cfg.GetNodeInstances(self.op.node_name)
6193       if ipri or isec:
6194         raise errors.OpPrereqError("Node %s hosts instances, cannot unset"
6195                                    " the vm_capable flag" % node.name,
6196                                    errors.ECODE_STATE)
6197
6198     if node.master_candidate and self.might_demote and not self.lock_all:
6199       assert not self.op.auto_promote, "auto_promote set but lock_all not"
6200       # check if after removing the current node, we're missing master
6201       # candidates
6202       (mc_remaining, mc_should, _) = \
6203           self.cfg.GetMasterCandidateStats(exceptions=[node.name])
6204       if mc_remaining < mc_should:
6205         raise errors.OpPrereqError("Not enough master candidates, please"
6206                                    " pass auto promote option to allow"
6207                                    " promotion (--auto-promote or RAPI"
6208                                    " auto_promote=True)", errors.ECODE_STATE)
6209
6210     self.old_flags = old_flags = (node.master_candidate,
6211                                   node.drained, node.offline)
6212     assert old_flags in self._F2R, "Un-handled old flags %s" % str(old_flags)
6213     self.old_role = old_role = self._F2R[old_flags]
6214
6215     # Check for ineffective changes
6216     for attr in self._FLAGS:
6217       if (getattr(self.op, attr) == False and getattr(node, attr) == False):
6218         self.LogInfo("Ignoring request to unset flag %s, already unset", attr)
6219         setattr(self.op, attr, None)
6220
6221     # Past this point, any flag change to False means a transition
6222     # away from the respective state, as only real changes are kept
6223
6224     # TODO: We might query the real power state if it supports OOB
6225     if _SupportsOob(self.cfg, node):
6226       if self.op.offline is False and not (node.powered or
6227                                            self.op.powered == True):
6228         raise errors.OpPrereqError(("Node %s needs to be turned on before its"
6229                                     " offline status can be reset") %
6230                                    self.op.node_name)
6231     elif self.op.powered is not None:
6232       raise errors.OpPrereqError(("Unable to change powered state for node %s"
6233                                   " as it does not support out-of-band"
6234                                   " handling") % self.op.node_name)
6235
6236     # If we're being deofflined/drained, we'll MC ourself if needed
6237     if (self.op.drained == False or self.op.offline == False or
6238         (self.op.master_capable and not node.master_capable)):
6239       if _DecideSelfPromotion(self):
6240         self.op.master_candidate = True
6241         self.LogInfo("Auto-promoting node to master candidate")
6242
6243     # If we're no longer master capable, we'll demote ourselves from MC
6244     if self.op.master_capable == False and node.master_candidate:
6245       self.LogInfo("Demoting from master candidate")
6246       self.op.master_candidate = False
6247
6248     # Compute new role
6249     assert [getattr(self.op, attr) for attr in self._FLAGS].count(True) <= 1
6250     if self.op.master_candidate:
6251       new_role = self._ROLE_CANDIDATE
6252     elif self.op.drained:
6253       new_role = self._ROLE_DRAINED
6254     elif self.op.offline:
6255       new_role = self._ROLE_OFFLINE
6256     elif False in [self.op.master_candidate, self.op.drained, self.op.offline]:
6257       # False is still in new flags, which means we're un-setting (the
6258       # only) True flag
6259       new_role = self._ROLE_REGULAR
6260     else: # no new flags, nothing, keep old role
6261       new_role = old_role
6262
6263     self.new_role = new_role
6264
6265     if old_role == self._ROLE_OFFLINE and new_role != old_role:
6266       # Trying to transition out of offline status
6267       result = self.rpc.call_version([node.name])[node.name]
6268       if result.fail_msg:
6269         raise errors.OpPrereqError("Node %s is being de-offlined but fails"
6270                                    " to report its version: %s" %
6271                                    (node.name, result.fail_msg),
6272                                    errors.ECODE_STATE)
6273       else:
6274         self.LogWarning("Transitioning node from offline to online state"
6275                         " without using re-add. Please make sure the node"
6276                         " is healthy!")
6277
6278     if self.op.secondary_ip:
6279       # Ok even without locking, because this can't be changed by any LU
6280       master = self.cfg.GetNodeInfo(self.cfg.GetMasterNode())
6281       master_singlehomed = master.secondary_ip == master.primary_ip
6282       if master_singlehomed and self.op.secondary_ip:
6283         raise errors.OpPrereqError("Cannot change the secondary ip on a single"
6284                                    " homed cluster", errors.ECODE_INVAL)
6285
6286       assert not (frozenset(affected_instances) -
6287                   self.owned_locks(locking.LEVEL_INSTANCE))
6288
6289       if node.offline:
6290         if affected_instances:
6291           raise errors.OpPrereqError("Cannot change secondary IP address:"
6292                                      " offline node has instances (%s)"
6293                                      " configured to use it" %
6294                                      utils.CommaJoin(affected_instances.keys()))
6295       else:
6296         # On online nodes, check that no instances are running, and that
6297         # the node has the new ip and we can reach it.
6298         for instance in affected_instances.values():
6299           _CheckInstanceState(self, instance, INSTANCE_DOWN,
6300                               msg="cannot change secondary ip")
6301
6302         _CheckNodeHasSecondaryIP(self, node.name, self.op.secondary_ip, True)
6303         if master.name != node.name:
6304           # check reachability from master secondary ip to new secondary ip
6305           if not netutils.TcpPing(self.op.secondary_ip,
6306                                   constants.DEFAULT_NODED_PORT,
6307                                   source=master.secondary_ip):
6308             raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
6309                                        " based ping to node daemon port",
6310                                        errors.ECODE_ENVIRON)
6311
6312     if self.op.ndparams:
6313       new_ndparams = _GetUpdatedParams(self.node.ndparams, self.op.ndparams)
6314       utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES)
6315       self.new_ndparams = new_ndparams
6316
6317     if self.op.hv_state:
6318       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
6319                                                  self.node.hv_state_static)
6320
6321     if self.op.disk_state:
6322       self.new_disk_state = \
6323         _MergeAndVerifyDiskState(self.op.disk_state,
6324                                  self.node.disk_state_static)
6325
6326   def Exec(self, feedback_fn):
6327     """Modifies a node.
6328
6329     """
6330     node = self.node
6331     old_role = self.old_role
6332     new_role = self.new_role
6333
6334     result = []
6335
6336     if self.op.ndparams:
6337       node.ndparams = self.new_ndparams
6338
6339     if self.op.powered is not None:
6340       node.powered = self.op.powered
6341
6342     if self.op.hv_state:
6343       node.hv_state_static = self.new_hv_state
6344
6345     if self.op.disk_state:
6346       node.disk_state_static = self.new_disk_state
6347
6348     for attr in ["master_capable", "vm_capable"]:
6349       val = getattr(self.op, attr)
6350       if val is not None:
6351         setattr(node, attr, val)
6352         result.append((attr, str(val)))
6353
6354     if new_role != old_role:
6355       # Tell the node to demote itself, if no longer MC and not offline
6356       if old_role == self._ROLE_CANDIDATE and new_role != self._ROLE_OFFLINE:
6357         msg = self.rpc.call_node_demote_from_mc(node.name).fail_msg
6358         if msg:
6359           self.LogWarning("Node failed to demote itself: %s", msg)
6360
6361       new_flags = self._R2F[new_role]
6362       for of, nf, desc in zip(self.old_flags, new_flags, self._FLAGS):
6363         if of != nf:
6364           result.append((desc, str(nf)))
6365       (node.master_candidate, node.drained, node.offline) = new_flags
6366
6367       # we locked all nodes, we adjust the CP before updating this node
6368       if self.lock_all:
6369         _AdjustCandidatePool(self, [node.name])
6370
6371     if self.op.secondary_ip:
6372       node.secondary_ip = self.op.secondary_ip
6373       result.append(("secondary_ip", self.op.secondary_ip))
6374
6375     # this will trigger configuration file update, if needed
6376     self.cfg.Update(node, feedback_fn)
6377
6378     # this will trigger job queue propagation or cleanup if the mc
6379     # flag changed
6380     if [old_role, new_role].count(self._ROLE_CANDIDATE) == 1:
6381       self.context.ReaddNode(node)
6382
6383     return result
6384
6385
6386 class LUNodePowercycle(NoHooksLU):
6387   """Powercycles a node.
6388
6389   """
6390   REQ_BGL = False
6391
6392   def CheckArguments(self):
6393     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
6394     if self.op.node_name == self.cfg.GetMasterNode() and not self.op.force:
6395       raise errors.OpPrereqError("The node is the master and the force"
6396                                  " parameter was not set",
6397                                  errors.ECODE_INVAL)
6398
6399   def ExpandNames(self):
6400     """Locking for PowercycleNode.
6401
6402     This is a last-resort option and shouldn't block on other
6403     jobs. Therefore, we grab no locks.
6404
6405     """
6406     self.needed_locks = {}
6407
6408   def Exec(self, feedback_fn):
6409     """Reboots a node.
6410
6411     """
6412     result = self.rpc.call_node_powercycle(self.op.node_name,
6413                                            self.cfg.GetHypervisorType())
6414     result.Raise("Failed to schedule the reboot")
6415     return result.payload
6416
6417
6418 class LUClusterQuery(NoHooksLU):
6419   """Query cluster configuration.
6420
6421   """
6422   REQ_BGL = False
6423
6424   def ExpandNames(self):
6425     self.needed_locks = {}
6426
6427   def Exec(self, feedback_fn):
6428     """Return cluster config.
6429
6430     """
6431     cluster = self.cfg.GetClusterInfo()
6432     os_hvp = {}
6433
6434     # Filter just for enabled hypervisors
6435     for os_name, hv_dict in cluster.os_hvp.items():
6436       os_hvp[os_name] = {}
6437       for hv_name, hv_params in hv_dict.items():
6438         if hv_name in cluster.enabled_hypervisors:
6439           os_hvp[os_name][hv_name] = hv_params
6440
6441     # Convert ip_family to ip_version
6442     primary_ip_version = constants.IP4_VERSION
6443     if cluster.primary_ip_family == netutils.IP6Address.family:
6444       primary_ip_version = constants.IP6_VERSION
6445
6446     result = {
6447       "software_version": constants.RELEASE_VERSION,
6448       "protocol_version": constants.PROTOCOL_VERSION,
6449       "config_version": constants.CONFIG_VERSION,
6450       "os_api_version": max(constants.OS_API_VERSIONS),
6451       "export_version": constants.EXPORT_VERSION,
6452       "architecture": runtime.GetArchInfo(),
6453       "name": cluster.cluster_name,
6454       "master": cluster.master_node,
6455       "default_hypervisor": cluster.primary_hypervisor,
6456       "enabled_hypervisors": cluster.enabled_hypervisors,
6457       "hvparams": dict([(hypervisor_name, cluster.hvparams[hypervisor_name])
6458                         for hypervisor_name in cluster.enabled_hypervisors]),
6459       "os_hvp": os_hvp,
6460       "beparams": cluster.beparams,
6461       "osparams": cluster.osparams,
6462       "ipolicy": cluster.ipolicy,
6463       "nicparams": cluster.nicparams,
6464       "ndparams": cluster.ndparams,
6465       "diskparams": cluster.diskparams,
6466       "candidate_pool_size": cluster.candidate_pool_size,
6467       "master_netdev": cluster.master_netdev,
6468       "master_netmask": cluster.master_netmask,
6469       "use_external_mip_script": cluster.use_external_mip_script,
6470       "volume_group_name": cluster.volume_group_name,
6471       "drbd_usermode_helper": cluster.drbd_usermode_helper,
6472       "file_storage_dir": cluster.file_storage_dir,
6473       "shared_file_storage_dir": cluster.shared_file_storage_dir,
6474       "maintain_node_health": cluster.maintain_node_health,
6475       "ctime": cluster.ctime,
6476       "mtime": cluster.mtime,
6477       "uuid": cluster.uuid,
6478       "tags": list(cluster.GetTags()),
6479       "uid_pool": cluster.uid_pool,
6480       "default_iallocator": cluster.default_iallocator,
6481       "reserved_lvs": cluster.reserved_lvs,
6482       "primary_ip_version": primary_ip_version,
6483       "prealloc_wipe_disks": cluster.prealloc_wipe_disks,
6484       "hidden_os": cluster.hidden_os,
6485       "blacklisted_os": cluster.blacklisted_os,
6486       }
6487
6488     return result
6489
6490
6491 class LUClusterConfigQuery(NoHooksLU):
6492   """Return configuration values.
6493
6494   """
6495   REQ_BGL = False
6496
6497   def CheckArguments(self):
6498     self.cq = _ClusterQuery(None, self.op.output_fields, False)
6499
6500   def ExpandNames(self):
6501     self.cq.ExpandNames(self)
6502
6503   def DeclareLocks(self, level):
6504     self.cq.DeclareLocks(self, level)
6505
6506   def Exec(self, feedback_fn):
6507     result = self.cq.OldStyleQuery(self)
6508
6509     assert len(result) == 1
6510
6511     return result[0]
6512
6513
6514 class _ClusterQuery(_QueryBase):
6515   FIELDS = query.CLUSTER_FIELDS
6516
6517   #: Do not sort (there is only one item)
6518   SORT_FIELD = None
6519
6520   def ExpandNames(self, lu):
6521     lu.needed_locks = {}
6522
6523     # The following variables interact with _QueryBase._GetNames
6524     self.wanted = locking.ALL_SET
6525     self.do_locking = self.use_locking
6526
6527     if self.do_locking:
6528       raise errors.OpPrereqError("Can not use locking for cluster queries",
6529                                  errors.ECODE_INVAL)
6530
6531   def DeclareLocks(self, lu, level):
6532     pass
6533
6534   def _GetQueryData(self, lu):
6535     """Computes the list of nodes and their attributes.
6536
6537     """
6538     # Locking is not used
6539     assert not (compat.any(lu.glm.is_owned(level)
6540                            for level in locking.LEVELS
6541                            if level != locking.LEVEL_CLUSTER) or
6542                 self.do_locking or self.use_locking)
6543
6544     if query.CQ_CONFIG in self.requested_data:
6545       cluster = lu.cfg.GetClusterInfo()
6546     else:
6547       cluster = NotImplemented
6548
6549     if query.CQ_QUEUE_DRAINED in self.requested_data:
6550       drain_flag = os.path.exists(constants.JOB_QUEUE_DRAIN_FILE)
6551     else:
6552       drain_flag = NotImplemented
6553
6554     if query.CQ_WATCHER_PAUSE in self.requested_data:
6555       watcher_pause = utils.ReadWatcherPauseFile(constants.WATCHER_PAUSEFILE)
6556     else:
6557       watcher_pause = NotImplemented
6558
6559     return query.ClusterQueryData(cluster, drain_flag, watcher_pause)
6560
6561
6562 class LUInstanceActivateDisks(NoHooksLU):
6563   """Bring up an instance's disks.
6564
6565   """
6566   REQ_BGL = False
6567
6568   def ExpandNames(self):
6569     self._ExpandAndLockInstance()
6570     self.needed_locks[locking.LEVEL_NODE] = []
6571     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6572
6573   def DeclareLocks(self, level):
6574     if level == locking.LEVEL_NODE:
6575       self._LockInstancesNodes()
6576
6577   def CheckPrereq(self):
6578     """Check prerequisites.
6579
6580     This checks that the instance is in the cluster.
6581
6582     """
6583     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6584     assert self.instance is not None, \
6585       "Cannot retrieve locked instance %s" % self.op.instance_name
6586     _CheckNodeOnline(self, self.instance.primary_node)
6587
6588   def Exec(self, feedback_fn):
6589     """Activate the disks.
6590
6591     """
6592     disks_ok, disks_info = \
6593               _AssembleInstanceDisks(self, self.instance,
6594                                      ignore_size=self.op.ignore_size)
6595     if not disks_ok:
6596       raise errors.OpExecError("Cannot activate block devices")
6597
6598     return disks_info
6599
6600
6601 def _AssembleInstanceDisks(lu, instance, disks=None, ignore_secondaries=False,
6602                            ignore_size=False, check=True):
6603   """Prepare the block devices for an instance.
6604
6605   This sets up the block devices on all nodes.
6606
6607   @type lu: L{LogicalUnit}
6608   @param lu: the logical unit on whose behalf we execute
6609   @type instance: L{objects.Instance}
6610   @param instance: the instance for whose disks we assemble
6611   @type disks: list of L{objects.Disk} or None
6612   @param disks: which disks to assemble (or all, if None)
6613   @type ignore_secondaries: boolean
6614   @param ignore_secondaries: if true, errors on secondary nodes
6615       won't result in an error return from the function
6616   @type ignore_size: boolean
6617   @param ignore_size: if true, the current known size of the disk
6618       will not be used during the disk activation, useful for cases
6619       when the size is wrong
6620   @return: False if the operation failed, otherwise a list of
6621       (host, instance_visible_name, node_visible_name)
6622       with the mapping from node devices to instance devices
6623
6624   """
6625   device_info = []
6626   disks_ok = True
6627   iname = instance.name
6628   if check:
6629     disks = _ExpandCheckDisks(instance, disks)
6630
6631   # With the two passes mechanism we try to reduce the window of
6632   # opportunity for the race condition of switching DRBD to primary
6633   # before handshaking occured, but we do not eliminate it
6634
6635   # The proper fix would be to wait (with some limits) until the
6636   # connection has been made and drbd transitions from WFConnection
6637   # into any other network-connected state (Connected, SyncTarget,
6638   # SyncSource, etc.)
6639
6640   # 1st pass, assemble on all nodes in secondary mode
6641   for idx, inst_disk in enumerate(disks):
6642     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
6643       if ignore_size:
6644         node_disk = node_disk.Copy()
6645         node_disk.UnsetSize()
6646       lu.cfg.SetDiskID(node_disk, node)
6647       result = lu.rpc.call_blockdev_assemble(node, (node_disk, instance), iname,
6648                                              False, idx)
6649       msg = result.fail_msg
6650       if msg:
6651         is_offline_secondary = (node in instance.secondary_nodes and
6652                                 result.offline)
6653         lu.proc.LogWarning("Could not prepare block device %s on node %s"
6654                            " (is_primary=False, pass=1): %s",
6655                            inst_disk.iv_name, node, msg)
6656         if not (ignore_secondaries or is_offline_secondary):
6657           disks_ok = False
6658
6659   # FIXME: race condition on drbd migration to primary
6660
6661   # 2nd pass, do only the primary node
6662   for idx, inst_disk in enumerate(disks):
6663     dev_path = None
6664
6665     for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
6666       if node != instance.primary_node:
6667         continue
6668       if ignore_size:
6669         node_disk = node_disk.Copy()
6670         node_disk.UnsetSize()
6671       lu.cfg.SetDiskID(node_disk, node)
6672       result = lu.rpc.call_blockdev_assemble(node, (node_disk, instance), iname,
6673                                              True, idx)
6674       msg = result.fail_msg
6675       if msg:
6676         lu.proc.LogWarning("Could not prepare block device %s on node %s"
6677                            " (is_primary=True, pass=2): %s",
6678                            inst_disk.iv_name, node, msg)
6679         disks_ok = False
6680       else:
6681         dev_path = result.payload
6682
6683     device_info.append((instance.primary_node, inst_disk.iv_name, dev_path))
6684
6685   # leave the disks configured for the primary node
6686   # this is a workaround that would be fixed better by
6687   # improving the logical/physical id handling
6688   for disk in disks:
6689     lu.cfg.SetDiskID(disk, instance.primary_node)
6690
6691   return disks_ok, device_info
6692
6693
6694 def _StartInstanceDisks(lu, instance, force):
6695   """Start the disks of an instance.
6696
6697   """
6698   disks_ok, _ = _AssembleInstanceDisks(lu, instance,
6699                                            ignore_secondaries=force)
6700   if not disks_ok:
6701     _ShutdownInstanceDisks(lu, instance)
6702     if force is not None and not force:
6703       lu.proc.LogWarning("", hint="If the message above refers to a"
6704                          " secondary node,"
6705                          " you can retry the operation using '--force'.")
6706     raise errors.OpExecError("Disk consistency error")
6707
6708
6709 class LUInstanceDeactivateDisks(NoHooksLU):
6710   """Shutdown an instance's disks.
6711
6712   """
6713   REQ_BGL = False
6714
6715   def ExpandNames(self):
6716     self._ExpandAndLockInstance()
6717     self.needed_locks[locking.LEVEL_NODE] = []
6718     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
6719
6720   def DeclareLocks(self, level):
6721     if level == locking.LEVEL_NODE:
6722       self._LockInstancesNodes()
6723
6724   def CheckPrereq(self):
6725     """Check prerequisites.
6726
6727     This checks that the instance is in the cluster.
6728
6729     """
6730     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6731     assert self.instance is not None, \
6732       "Cannot retrieve locked instance %s" % self.op.instance_name
6733
6734   def Exec(self, feedback_fn):
6735     """Deactivate the disks
6736
6737     """
6738     instance = self.instance
6739     if self.op.force:
6740       _ShutdownInstanceDisks(self, instance)
6741     else:
6742       _SafeShutdownInstanceDisks(self, instance)
6743
6744
6745 def _SafeShutdownInstanceDisks(lu, instance, disks=None):
6746   """Shutdown block devices of an instance.
6747
6748   This function checks if an instance is running, before calling
6749   _ShutdownInstanceDisks.
6750
6751   """
6752   _CheckInstanceState(lu, instance, INSTANCE_DOWN, msg="cannot shutdown disks")
6753   _ShutdownInstanceDisks(lu, instance, disks=disks)
6754
6755
6756 def _ExpandCheckDisks(instance, disks):
6757   """Return the instance disks selected by the disks list
6758
6759   @type disks: list of L{objects.Disk} or None
6760   @param disks: selected disks
6761   @rtype: list of L{objects.Disk}
6762   @return: selected instance disks to act on
6763
6764   """
6765   if disks is None:
6766     return instance.disks
6767   else:
6768     if not set(disks).issubset(instance.disks):
6769       raise errors.ProgrammerError("Can only act on disks belonging to the"
6770                                    " target instance")
6771     return disks
6772
6773
6774 def _ShutdownInstanceDisks(lu, instance, disks=None, ignore_primary=False):
6775   """Shutdown block devices of an instance.
6776
6777   This does the shutdown on all nodes of the instance.
6778
6779   If the ignore_primary is false, errors on the primary node are
6780   ignored.
6781
6782   """
6783   all_result = True
6784   disks = _ExpandCheckDisks(instance, disks)
6785
6786   for disk in disks:
6787     for node, top_disk in disk.ComputeNodeTree(instance.primary_node):
6788       lu.cfg.SetDiskID(top_disk, node)
6789       result = lu.rpc.call_blockdev_shutdown(node, (top_disk, instance))
6790       msg = result.fail_msg
6791       if msg:
6792         lu.LogWarning("Could not shutdown block device %s on node %s: %s",
6793                       disk.iv_name, node, msg)
6794         if ((node == instance.primary_node and not ignore_primary) or
6795             (node != instance.primary_node and not result.offline)):
6796           all_result = False
6797   return all_result
6798
6799
6800 def _CheckNodeFreeMemory(lu, node, reason, requested, hypervisor_name):
6801   """Checks if a node has enough free memory.
6802
6803   This function check if a given node has the needed amount of free
6804   memory. In case the node has less memory or we cannot get the
6805   information from the node, this function raise an OpPrereqError
6806   exception.
6807
6808   @type lu: C{LogicalUnit}
6809   @param lu: a logical unit from which we get configuration data
6810   @type node: C{str}
6811   @param node: the node to check
6812   @type reason: C{str}
6813   @param reason: string to use in the error message
6814   @type requested: C{int}
6815   @param requested: the amount of memory in MiB to check for
6816   @type hypervisor_name: C{str}
6817   @param hypervisor_name: the hypervisor to ask for memory stats
6818   @rtype: integer
6819   @return: node current free memory
6820   @raise errors.OpPrereqError: if the node doesn't have enough memory, or
6821       we cannot check the node
6822
6823   """
6824   nodeinfo = lu.rpc.call_node_info([node], None, [hypervisor_name])
6825   nodeinfo[node].Raise("Can't get data from node %s" % node,
6826                        prereq=True, ecode=errors.ECODE_ENVIRON)
6827   (_, _, (hv_info, )) = nodeinfo[node].payload
6828
6829   free_mem = hv_info.get("memory_free", None)
6830   if not isinstance(free_mem, int):
6831     raise errors.OpPrereqError("Can't compute free memory on node %s, result"
6832                                " was '%s'" % (node, free_mem),
6833                                errors.ECODE_ENVIRON)
6834   if requested > free_mem:
6835     raise errors.OpPrereqError("Not enough memory on node %s for %s:"
6836                                " needed %s MiB, available %s MiB" %
6837                                (node, reason, requested, free_mem),
6838                                errors.ECODE_NORES)
6839   return free_mem
6840
6841
6842 def _CheckNodesFreeDiskPerVG(lu, nodenames, req_sizes):
6843   """Checks if nodes have enough free disk space in the all VGs.
6844
6845   This function check if all given nodes have the needed amount of
6846   free disk. In case any node has less disk or we cannot get the
6847   information from the node, this function raise an OpPrereqError
6848   exception.
6849
6850   @type lu: C{LogicalUnit}
6851   @param lu: a logical unit from which we get configuration data
6852   @type nodenames: C{list}
6853   @param nodenames: the list of node names to check
6854   @type req_sizes: C{dict}
6855   @param req_sizes: the hash of vg and corresponding amount of disk in
6856       MiB to check for
6857   @raise errors.OpPrereqError: if the node doesn't have enough disk,
6858       or we cannot check the node
6859
6860   """
6861   for vg, req_size in req_sizes.items():
6862     _CheckNodesFreeDiskOnVG(lu, nodenames, vg, req_size)
6863
6864
6865 def _CheckNodesFreeDiskOnVG(lu, nodenames, vg, requested):
6866   """Checks if nodes have enough free disk space in the specified VG.
6867
6868   This function check if all given nodes have the needed amount of
6869   free disk. In case any node has less disk or we cannot get the
6870   information from the node, this function raise an OpPrereqError
6871   exception.
6872
6873   @type lu: C{LogicalUnit}
6874   @param lu: a logical unit from which we get configuration data
6875   @type nodenames: C{list}
6876   @param nodenames: the list of node names to check
6877   @type vg: C{str}
6878   @param vg: the volume group to check
6879   @type requested: C{int}
6880   @param requested: the amount of disk in MiB to check for
6881   @raise errors.OpPrereqError: if the node doesn't have enough disk,
6882       or we cannot check the node
6883
6884   """
6885   nodeinfo = lu.rpc.call_node_info(nodenames, [vg], None)
6886   for node in nodenames:
6887     info = nodeinfo[node]
6888     info.Raise("Cannot get current information from node %s" % node,
6889                prereq=True, ecode=errors.ECODE_ENVIRON)
6890     (_, (vg_info, ), _) = info.payload
6891     vg_free = vg_info.get("vg_free", None)
6892     if not isinstance(vg_free, int):
6893       raise errors.OpPrereqError("Can't compute free disk space on node"
6894                                  " %s for vg %s, result was '%s'" %
6895                                  (node, vg, vg_free), errors.ECODE_ENVIRON)
6896     if requested > vg_free:
6897       raise errors.OpPrereqError("Not enough disk space on target node %s"
6898                                  " vg %s: required %d MiB, available %d MiB" %
6899                                  (node, vg, requested, vg_free),
6900                                  errors.ECODE_NORES)
6901
6902
6903 def _CheckNodesPhysicalCPUs(lu, nodenames, requested, hypervisor_name):
6904   """Checks if nodes have enough physical CPUs
6905
6906   This function checks if all given nodes have the needed number of
6907   physical CPUs. In case any node has less CPUs or we cannot get the
6908   information from the node, this function raises an OpPrereqError
6909   exception.
6910
6911   @type lu: C{LogicalUnit}
6912   @param lu: a logical unit from which we get configuration data
6913   @type nodenames: C{list}
6914   @param nodenames: the list of node names to check
6915   @type requested: C{int}
6916   @param requested: the minimum acceptable number of physical CPUs
6917   @raise errors.OpPrereqError: if the node doesn't have enough CPUs,
6918       or we cannot check the node
6919
6920   """
6921   nodeinfo = lu.rpc.call_node_info(nodenames, None, [hypervisor_name])
6922   for node in nodenames:
6923     info = nodeinfo[node]
6924     info.Raise("Cannot get current information from node %s" % node,
6925                prereq=True, ecode=errors.ECODE_ENVIRON)
6926     (_, _, (hv_info, )) = info.payload
6927     num_cpus = hv_info.get("cpu_total", None)
6928     if not isinstance(num_cpus, int):
6929       raise errors.OpPrereqError("Can't compute the number of physical CPUs"
6930                                  " on node %s, result was '%s'" %
6931                                  (node, num_cpus), errors.ECODE_ENVIRON)
6932     if requested > num_cpus:
6933       raise errors.OpPrereqError("Node %s has %s physical CPUs, but %s are "
6934                                  "required" % (node, num_cpus, requested),
6935                                  errors.ECODE_NORES)
6936
6937
6938 class LUInstanceStartup(LogicalUnit):
6939   """Starts an instance.
6940
6941   """
6942   HPATH = "instance-start"
6943   HTYPE = constants.HTYPE_INSTANCE
6944   REQ_BGL = False
6945
6946   def CheckArguments(self):
6947     # extra beparams
6948     if self.op.beparams:
6949       # fill the beparams dict
6950       objects.UpgradeBeParams(self.op.beparams)
6951       utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
6952
6953   def ExpandNames(self):
6954     self._ExpandAndLockInstance()
6955     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
6956
6957   def DeclareLocks(self, level):
6958     if level == locking.LEVEL_NODE_RES:
6959       self._LockInstancesNodes(primary_only=True, level=locking.LEVEL_NODE_RES)
6960
6961   def BuildHooksEnv(self):
6962     """Build hooks env.
6963
6964     This runs on master, primary and secondary nodes of the instance.
6965
6966     """
6967     env = {
6968       "FORCE": self.op.force,
6969       }
6970
6971     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
6972
6973     return env
6974
6975   def BuildHooksNodes(self):
6976     """Build hooks nodes.
6977
6978     """
6979     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
6980     return (nl, nl)
6981
6982   def CheckPrereq(self):
6983     """Check prerequisites.
6984
6985     This checks that the instance is in the cluster.
6986
6987     """
6988     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
6989     assert self.instance is not None, \
6990       "Cannot retrieve locked instance %s" % self.op.instance_name
6991
6992     # extra hvparams
6993     if self.op.hvparams:
6994       # check hypervisor parameter syntax (locally)
6995       cluster = self.cfg.GetClusterInfo()
6996       utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
6997       filled_hvp = cluster.FillHV(instance)
6998       filled_hvp.update(self.op.hvparams)
6999       hv_type = hypervisor.GetHypervisor(instance.hypervisor)
7000       hv_type.CheckParameterSyntax(filled_hvp)
7001       _CheckHVParams(self, instance.all_nodes, instance.hypervisor, filled_hvp)
7002
7003     _CheckInstanceState(self, instance, INSTANCE_ONLINE)
7004
7005     self.primary_offline = self.cfg.GetNodeInfo(instance.primary_node).offline
7006
7007     if self.primary_offline and self.op.ignore_offline_nodes:
7008       self.proc.LogWarning("Ignoring offline primary node")
7009
7010       if self.op.hvparams or self.op.beparams:
7011         self.proc.LogWarning("Overridden parameters are ignored")
7012     else:
7013       _CheckNodeOnline(self, instance.primary_node)
7014
7015       bep = self.cfg.GetClusterInfo().FillBE(instance)
7016       bep.update(self.op.beparams)
7017
7018       # check bridges existence
7019       _CheckInstanceBridgesExist(self, instance)
7020
7021       remote_info = self.rpc.call_instance_info(instance.primary_node,
7022                                                 instance.name,
7023                                                 instance.hypervisor)
7024       remote_info.Raise("Error checking node %s" % instance.primary_node,
7025                         prereq=True, ecode=errors.ECODE_ENVIRON)
7026       if not remote_info.payload: # not running already
7027         _CheckNodeFreeMemory(self, instance.primary_node,
7028                              "starting instance %s" % instance.name,
7029                              bep[constants.BE_MINMEM], instance.hypervisor)
7030
7031   def Exec(self, feedback_fn):
7032     """Start the instance.
7033
7034     """
7035     instance = self.instance
7036     force = self.op.force
7037
7038     if not self.op.no_remember:
7039       self.cfg.MarkInstanceUp(instance.name)
7040
7041     if self.primary_offline:
7042       assert self.op.ignore_offline_nodes
7043       self.proc.LogInfo("Primary node offline, marked instance as started")
7044     else:
7045       node_current = instance.primary_node
7046
7047       _StartInstanceDisks(self, instance, force)
7048
7049       result = \
7050         self.rpc.call_instance_start(node_current,
7051                                      (instance, self.op.hvparams,
7052                                       self.op.beparams),
7053                                      self.op.startup_paused)
7054       msg = result.fail_msg
7055       if msg:
7056         _ShutdownInstanceDisks(self, instance)
7057         raise errors.OpExecError("Could not start instance: %s" % msg)
7058
7059
7060 class LUInstanceReboot(LogicalUnit):
7061   """Reboot an instance.
7062
7063   """
7064   HPATH = "instance-reboot"
7065   HTYPE = constants.HTYPE_INSTANCE
7066   REQ_BGL = False
7067
7068   def ExpandNames(self):
7069     self._ExpandAndLockInstance()
7070
7071   def BuildHooksEnv(self):
7072     """Build hooks env.
7073
7074     This runs on master, primary and secondary nodes of the instance.
7075
7076     """
7077     env = {
7078       "IGNORE_SECONDARIES": self.op.ignore_secondaries,
7079       "REBOOT_TYPE": self.op.reboot_type,
7080       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
7081       }
7082
7083     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
7084
7085     return env
7086
7087   def BuildHooksNodes(self):
7088     """Build hooks nodes.
7089
7090     """
7091     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7092     return (nl, nl)
7093
7094   def CheckPrereq(self):
7095     """Check prerequisites.
7096
7097     This checks that the instance is in the cluster.
7098
7099     """
7100     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7101     assert self.instance is not None, \
7102       "Cannot retrieve locked instance %s" % self.op.instance_name
7103     _CheckInstanceState(self, instance, INSTANCE_ONLINE)
7104     _CheckNodeOnline(self, instance.primary_node)
7105
7106     # check bridges existence
7107     _CheckInstanceBridgesExist(self, instance)
7108
7109   def Exec(self, feedback_fn):
7110     """Reboot the instance.
7111
7112     """
7113     instance = self.instance
7114     ignore_secondaries = self.op.ignore_secondaries
7115     reboot_type = self.op.reboot_type
7116
7117     remote_info = self.rpc.call_instance_info(instance.primary_node,
7118                                               instance.name,
7119                                               instance.hypervisor)
7120     remote_info.Raise("Error checking node %s" % instance.primary_node)
7121     instance_running = bool(remote_info.payload)
7122
7123     node_current = instance.primary_node
7124
7125     if instance_running and reboot_type in [constants.INSTANCE_REBOOT_SOFT,
7126                                             constants.INSTANCE_REBOOT_HARD]:
7127       for disk in instance.disks:
7128         self.cfg.SetDiskID(disk, node_current)
7129       result = self.rpc.call_instance_reboot(node_current, instance,
7130                                              reboot_type,
7131                                              self.op.shutdown_timeout)
7132       result.Raise("Could not reboot instance")
7133     else:
7134       if instance_running:
7135         result = self.rpc.call_instance_shutdown(node_current, instance,
7136                                                  self.op.shutdown_timeout)
7137         result.Raise("Could not shutdown instance for full reboot")
7138         _ShutdownInstanceDisks(self, instance)
7139       else:
7140         self.LogInfo("Instance %s was already stopped, starting now",
7141                      instance.name)
7142       _StartInstanceDisks(self, instance, ignore_secondaries)
7143       result = self.rpc.call_instance_start(node_current,
7144                                             (instance, None, None), False)
7145       msg = result.fail_msg
7146       if msg:
7147         _ShutdownInstanceDisks(self, instance)
7148         raise errors.OpExecError("Could not start instance for"
7149                                  " full reboot: %s" % msg)
7150
7151     self.cfg.MarkInstanceUp(instance.name)
7152
7153
7154 class LUInstanceShutdown(LogicalUnit):
7155   """Shutdown an instance.
7156
7157   """
7158   HPATH = "instance-stop"
7159   HTYPE = constants.HTYPE_INSTANCE
7160   REQ_BGL = False
7161
7162   def ExpandNames(self):
7163     self._ExpandAndLockInstance()
7164
7165   def BuildHooksEnv(self):
7166     """Build hooks env.
7167
7168     This runs on master, primary and secondary nodes of the instance.
7169
7170     """
7171     env = _BuildInstanceHookEnvByObject(self, self.instance)
7172     env["TIMEOUT"] = self.op.timeout
7173     return env
7174
7175   def BuildHooksNodes(self):
7176     """Build hooks nodes.
7177
7178     """
7179     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7180     return (nl, nl)
7181
7182   def CheckPrereq(self):
7183     """Check prerequisites.
7184
7185     This checks that the instance is in the cluster.
7186
7187     """
7188     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7189     assert self.instance is not None, \
7190       "Cannot retrieve locked instance %s" % self.op.instance_name
7191
7192     _CheckInstanceState(self, self.instance, INSTANCE_ONLINE)
7193
7194     self.primary_offline = \
7195       self.cfg.GetNodeInfo(self.instance.primary_node).offline
7196
7197     if self.primary_offline and self.op.ignore_offline_nodes:
7198       self.proc.LogWarning("Ignoring offline primary node")
7199     else:
7200       _CheckNodeOnline(self, self.instance.primary_node)
7201
7202   def Exec(self, feedback_fn):
7203     """Shutdown the instance.
7204
7205     """
7206     instance = self.instance
7207     node_current = instance.primary_node
7208     timeout = self.op.timeout
7209
7210     if not self.op.no_remember:
7211       self.cfg.MarkInstanceDown(instance.name)
7212
7213     if self.primary_offline:
7214       assert self.op.ignore_offline_nodes
7215       self.proc.LogInfo("Primary node offline, marked instance as stopped")
7216     else:
7217       result = self.rpc.call_instance_shutdown(node_current, instance, timeout)
7218       msg = result.fail_msg
7219       if msg:
7220         self.proc.LogWarning("Could not shutdown instance: %s" % msg)
7221
7222       _ShutdownInstanceDisks(self, instance)
7223
7224
7225 class LUInstanceReinstall(LogicalUnit):
7226   """Reinstall an instance.
7227
7228   """
7229   HPATH = "instance-reinstall"
7230   HTYPE = constants.HTYPE_INSTANCE
7231   REQ_BGL = False
7232
7233   def ExpandNames(self):
7234     self._ExpandAndLockInstance()
7235
7236   def BuildHooksEnv(self):
7237     """Build hooks env.
7238
7239     This runs on master, primary and secondary nodes of the instance.
7240
7241     """
7242     return _BuildInstanceHookEnvByObject(self, self.instance)
7243
7244   def BuildHooksNodes(self):
7245     """Build hooks nodes.
7246
7247     """
7248     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7249     return (nl, nl)
7250
7251   def CheckPrereq(self):
7252     """Check prerequisites.
7253
7254     This checks that the instance is in the cluster and is not running.
7255
7256     """
7257     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7258     assert instance is not None, \
7259       "Cannot retrieve locked instance %s" % self.op.instance_name
7260     _CheckNodeOnline(self, instance.primary_node, "Instance primary node"
7261                      " offline, cannot reinstall")
7262
7263     if instance.disk_template == constants.DT_DISKLESS:
7264       raise errors.OpPrereqError("Instance '%s' has no disks" %
7265                                  self.op.instance_name,
7266                                  errors.ECODE_INVAL)
7267     _CheckInstanceState(self, instance, INSTANCE_DOWN, msg="cannot reinstall")
7268
7269     if self.op.os_type is not None:
7270       # OS verification
7271       pnode = _ExpandNodeName(self.cfg, instance.primary_node)
7272       _CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant)
7273       instance_os = self.op.os_type
7274     else:
7275       instance_os = instance.os
7276
7277     nodelist = list(instance.all_nodes)
7278
7279     if self.op.osparams:
7280       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
7281       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
7282       self.os_inst = i_osdict # the new dict (without defaults)
7283     else:
7284       self.os_inst = None
7285
7286     self.instance = instance
7287
7288   def Exec(self, feedback_fn):
7289     """Reinstall the instance.
7290
7291     """
7292     inst = self.instance
7293
7294     if self.op.os_type is not None:
7295       feedback_fn("Changing OS to '%s'..." % self.op.os_type)
7296       inst.os = self.op.os_type
7297       # Write to configuration
7298       self.cfg.Update(inst, feedback_fn)
7299
7300     _StartInstanceDisks(self, inst, None)
7301     try:
7302       feedback_fn("Running the instance OS create scripts...")
7303       # FIXME: pass debug option from opcode to backend
7304       result = self.rpc.call_instance_os_add(inst.primary_node,
7305                                              (inst, self.os_inst), True,
7306                                              self.op.debug_level)
7307       result.Raise("Could not install OS for instance %s on node %s" %
7308                    (inst.name, inst.primary_node))
7309     finally:
7310       _ShutdownInstanceDisks(self, inst)
7311
7312
7313 class LUInstanceRecreateDisks(LogicalUnit):
7314   """Recreate an instance's missing disks.
7315
7316   """
7317   HPATH = "instance-recreate-disks"
7318   HTYPE = constants.HTYPE_INSTANCE
7319   REQ_BGL = False
7320
7321   _MODIFYABLE = frozenset([
7322     constants.IDISK_SIZE,
7323     constants.IDISK_MODE,
7324     ])
7325
7326   # New or changed disk parameters may have different semantics
7327   assert constants.IDISK_PARAMS == (_MODIFYABLE | frozenset([
7328     constants.IDISK_ADOPT,
7329
7330     # TODO: Implement support changing VG while recreating
7331     constants.IDISK_VG,
7332     constants.IDISK_METAVG,
7333     constants.IDISK_PROVIDER,
7334     ]))
7335
7336   def CheckArguments(self):
7337     if self.op.disks and ht.TPositiveInt(self.op.disks[0]):
7338       # Normalize and convert deprecated list of disk indices
7339       self.op.disks = [(idx, {}) for idx in sorted(frozenset(self.op.disks))]
7340
7341     duplicates = utils.FindDuplicates(map(compat.fst, self.op.disks))
7342     if duplicates:
7343       raise errors.OpPrereqError("Some disks have been specified more than"
7344                                  " once: %s" % utils.CommaJoin(duplicates),
7345                                  errors.ECODE_INVAL)
7346
7347     for (idx, params) in self.op.disks:
7348       utils.ForceDictType(params, constants.IDISK_PARAMS_TYPES)
7349       unsupported = frozenset(params.keys()) - self._MODIFYABLE
7350       if unsupported:
7351         raise errors.OpPrereqError("Parameters for disk %s try to change"
7352                                    " unmodifyable parameter(s): %s" %
7353                                    (idx, utils.CommaJoin(unsupported)),
7354                                    errors.ECODE_INVAL)
7355
7356   def ExpandNames(self):
7357     self._ExpandAndLockInstance()
7358     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
7359     if self.op.nodes:
7360       self.op.nodes = [_ExpandNodeName(self.cfg, n) for n in self.op.nodes]
7361       self.needed_locks[locking.LEVEL_NODE] = list(self.op.nodes)
7362     else:
7363       self.needed_locks[locking.LEVEL_NODE] = []
7364     self.needed_locks[locking.LEVEL_NODE_RES] = []
7365
7366   def DeclareLocks(self, level):
7367     if level == locking.LEVEL_NODE:
7368       # if we replace the nodes, we only need to lock the old primary,
7369       # otherwise we need to lock all nodes for disk re-creation
7370       primary_only = bool(self.op.nodes)
7371       self._LockInstancesNodes(primary_only=primary_only)
7372     elif level == locking.LEVEL_NODE_RES:
7373       # Copy node locks
7374       self.needed_locks[locking.LEVEL_NODE_RES] = \
7375         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
7376
7377   def BuildHooksEnv(self):
7378     """Build hooks env.
7379
7380     This runs on master, primary and secondary nodes of the instance.
7381
7382     """
7383     return _BuildInstanceHookEnvByObject(self, self.instance)
7384
7385   def BuildHooksNodes(self):
7386     """Build hooks nodes.
7387
7388     """
7389     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7390     return (nl, nl)
7391
7392   def CheckPrereq(self):
7393     """Check prerequisites.
7394
7395     This checks that the instance is in the cluster and is not running.
7396
7397     """
7398     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7399     assert instance is not None, \
7400       "Cannot retrieve locked instance %s" % self.op.instance_name
7401     if self.op.nodes:
7402       if len(self.op.nodes) != len(instance.all_nodes):
7403         raise errors.OpPrereqError("Instance %s currently has %d nodes, but"
7404                                    " %d replacement nodes were specified" %
7405                                    (instance.name, len(instance.all_nodes),
7406                                     len(self.op.nodes)),
7407                                    errors.ECODE_INVAL)
7408       assert instance.disk_template != constants.DT_DRBD8 or \
7409           len(self.op.nodes) == 2
7410       assert instance.disk_template != constants.DT_PLAIN or \
7411           len(self.op.nodes) == 1
7412       primary_node = self.op.nodes[0]
7413     else:
7414       primary_node = instance.primary_node
7415     _CheckNodeOnline(self, primary_node)
7416
7417     if instance.disk_template == constants.DT_DISKLESS:
7418       raise errors.OpPrereqError("Instance '%s' has no disks" %
7419                                  self.op.instance_name, errors.ECODE_INVAL)
7420
7421     # if we replace nodes *and* the old primary is offline, we don't
7422     # check
7423     assert instance.primary_node in self.owned_locks(locking.LEVEL_NODE)
7424     assert instance.primary_node in self.owned_locks(locking.LEVEL_NODE_RES)
7425     old_pnode = self.cfg.GetNodeInfo(instance.primary_node)
7426     if not (self.op.nodes and old_pnode.offline):
7427       _CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
7428                           msg="cannot recreate disks")
7429
7430     if self.op.disks:
7431       self.disks = dict(self.op.disks)
7432     else:
7433       self.disks = dict((idx, {}) for idx in range(len(instance.disks)))
7434
7435     maxidx = max(self.disks.keys())
7436     if maxidx >= len(instance.disks):
7437       raise errors.OpPrereqError("Invalid disk index '%s'" % maxidx,
7438                                  errors.ECODE_INVAL)
7439
7440     if (self.op.nodes and
7441         sorted(self.disks.keys()) != range(len(instance.disks))):
7442       raise errors.OpPrereqError("Can't recreate disks partially and"
7443                                  " change the nodes at the same time",
7444                                  errors.ECODE_INVAL)
7445
7446     self.instance = instance
7447
7448   def Exec(self, feedback_fn):
7449     """Recreate the disks.
7450
7451     """
7452     instance = self.instance
7453
7454     assert (self.owned_locks(locking.LEVEL_NODE) ==
7455             self.owned_locks(locking.LEVEL_NODE_RES))
7456
7457     to_skip = []
7458     mods = [] # keeps track of needed changes
7459
7460     for idx, disk in enumerate(instance.disks):
7461       try:
7462         changes = self.disks[idx]
7463       except KeyError:
7464         # Disk should not be recreated
7465         to_skip.append(idx)
7466         continue
7467
7468       # update secondaries for disks, if needed
7469       if self.op.nodes and disk.dev_type == constants.LD_DRBD8:
7470         # need to update the nodes and minors
7471         assert len(self.op.nodes) == 2
7472         assert len(disk.logical_id) == 6 # otherwise disk internals
7473                                          # have changed
7474         (_, _, old_port, _, _, old_secret) = disk.logical_id
7475         new_minors = self.cfg.AllocateDRBDMinor(self.op.nodes, instance.name)
7476         new_id = (self.op.nodes[0], self.op.nodes[1], old_port,
7477                   new_minors[0], new_minors[1], old_secret)
7478         assert len(disk.logical_id) == len(new_id)
7479       else:
7480         new_id = None
7481
7482       mods.append((idx, new_id, changes))
7483
7484     # now that we have passed all asserts above, we can apply the mods
7485     # in a single run (to avoid partial changes)
7486     for idx, new_id, changes in mods:
7487       disk = instance.disks[idx]
7488       if new_id is not None:
7489         assert disk.dev_type == constants.LD_DRBD8
7490         disk.logical_id = new_id
7491       if changes:
7492         disk.Update(size=changes.get(constants.IDISK_SIZE, None),
7493                     mode=changes.get(constants.IDISK_MODE, None))
7494
7495     # change primary node, if needed
7496     if self.op.nodes:
7497       instance.primary_node = self.op.nodes[0]
7498       self.LogWarning("Changing the instance's nodes, you will have to"
7499                       " remove any disks left on the older nodes manually")
7500
7501     if self.op.nodes:
7502       self.cfg.Update(instance, feedback_fn)
7503
7504     _CreateDisks(self, instance, to_skip=to_skip)
7505
7506
7507 class LUInstanceRename(LogicalUnit):
7508   """Rename an instance.
7509
7510   """
7511   HPATH = "instance-rename"
7512   HTYPE = constants.HTYPE_INSTANCE
7513
7514   def CheckArguments(self):
7515     """Check arguments.
7516
7517     """
7518     if self.op.ip_check and not self.op.name_check:
7519       # TODO: make the ip check more flexible and not depend on the name check
7520       raise errors.OpPrereqError("IP address check requires a name check",
7521                                  errors.ECODE_INVAL)
7522
7523   def BuildHooksEnv(self):
7524     """Build hooks env.
7525
7526     This runs on master, primary and secondary nodes of the instance.
7527
7528     """
7529     env = _BuildInstanceHookEnvByObject(self, self.instance)
7530     env["INSTANCE_NEW_NAME"] = self.op.new_name
7531     return env
7532
7533   def BuildHooksNodes(self):
7534     """Build hooks nodes.
7535
7536     """
7537     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
7538     return (nl, nl)
7539
7540   def CheckPrereq(self):
7541     """Check prerequisites.
7542
7543     This checks that the instance is in the cluster and is not running.
7544
7545     """
7546     self.op.instance_name = _ExpandInstanceName(self.cfg,
7547                                                 self.op.instance_name)
7548     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7549     assert instance is not None
7550     _CheckNodeOnline(self, instance.primary_node)
7551     _CheckInstanceState(self, instance, INSTANCE_NOT_RUNNING,
7552                         msg="cannot rename")
7553     self.instance = instance
7554
7555     new_name = self.op.new_name
7556     if self.op.name_check:
7557       hostname = _CheckHostnameSane(self, new_name)
7558       new_name = self.op.new_name = hostname.name
7559       if (self.op.ip_check and
7560           netutils.TcpPing(hostname.ip, constants.DEFAULT_NODED_PORT)):
7561         raise errors.OpPrereqError("IP %s of instance %s already in use" %
7562                                    (hostname.ip, new_name),
7563                                    errors.ECODE_NOTUNIQUE)
7564
7565     instance_list = self.cfg.GetInstanceList()
7566     if new_name in instance_list and new_name != instance.name:
7567       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
7568                                  new_name, errors.ECODE_EXISTS)
7569
7570   def Exec(self, feedback_fn):
7571     """Rename the instance.
7572
7573     """
7574     inst = self.instance
7575     old_name = inst.name
7576
7577     rename_file_storage = False
7578     if (inst.disk_template in constants.DTS_FILEBASED and
7579         self.op.new_name != inst.name):
7580       old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
7581       rename_file_storage = True
7582
7583     self.cfg.RenameInstance(inst.name, self.op.new_name)
7584     # Change the instance lock. This is definitely safe while we hold the BGL.
7585     # Otherwise the new lock would have to be added in acquired mode.
7586     assert self.REQ_BGL
7587     self.glm.remove(locking.LEVEL_INSTANCE, old_name)
7588     self.glm.add(locking.LEVEL_INSTANCE, self.op.new_name)
7589
7590     # re-read the instance from the configuration after rename
7591     inst = self.cfg.GetInstanceInfo(self.op.new_name)
7592
7593     if rename_file_storage:
7594       new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
7595       result = self.rpc.call_file_storage_dir_rename(inst.primary_node,
7596                                                      old_file_storage_dir,
7597                                                      new_file_storage_dir)
7598       result.Raise("Could not rename on node %s directory '%s' to '%s'"
7599                    " (but the instance has been renamed in Ganeti)" %
7600                    (inst.primary_node, old_file_storage_dir,
7601                     new_file_storage_dir))
7602
7603     _StartInstanceDisks(self, inst, None)
7604     try:
7605       result = self.rpc.call_instance_run_rename(inst.primary_node, inst,
7606                                                  old_name, self.op.debug_level)
7607       msg = result.fail_msg
7608       if msg:
7609         msg = ("Could not run OS rename script for instance %s on node %s"
7610                " (but the instance has been renamed in Ganeti): %s" %
7611                (inst.name, inst.primary_node, msg))
7612         self.proc.LogWarning(msg)
7613     finally:
7614       _ShutdownInstanceDisks(self, inst)
7615
7616     return inst.name
7617
7618
7619 class LUInstanceRemove(LogicalUnit):
7620   """Remove an instance.
7621
7622   """
7623   HPATH = "instance-remove"
7624   HTYPE = constants.HTYPE_INSTANCE
7625   REQ_BGL = False
7626
7627   def ExpandNames(self):
7628     self._ExpandAndLockInstance()
7629     self.needed_locks[locking.LEVEL_NODE] = []
7630     self.needed_locks[locking.LEVEL_NODE_RES] = []
7631     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7632
7633   def DeclareLocks(self, level):
7634     if level == locking.LEVEL_NODE:
7635       self._LockInstancesNodes()
7636     elif level == locking.LEVEL_NODE_RES:
7637       # Copy node locks
7638       self.needed_locks[locking.LEVEL_NODE_RES] = \
7639         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
7640
7641   def BuildHooksEnv(self):
7642     """Build hooks env.
7643
7644     This runs on master, primary and secondary nodes of the instance.
7645
7646     """
7647     env = _BuildInstanceHookEnvByObject(self, self.instance)
7648     env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
7649     return env
7650
7651   def BuildHooksNodes(self):
7652     """Build hooks nodes.
7653
7654     """
7655     nl = [self.cfg.GetMasterNode()]
7656     nl_post = list(self.instance.all_nodes) + nl
7657     return (nl, nl_post)
7658
7659   def CheckPrereq(self):
7660     """Check prerequisites.
7661
7662     This checks that the instance is in the cluster.
7663
7664     """
7665     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7666     assert self.instance is not None, \
7667       "Cannot retrieve locked instance %s" % self.op.instance_name
7668
7669   def Exec(self, feedback_fn):
7670     """Remove the instance.
7671
7672     """
7673     instance = self.instance
7674     logging.info("Shutting down instance %s on node %s",
7675                  instance.name, instance.primary_node)
7676
7677     result = self.rpc.call_instance_shutdown(instance.primary_node, instance,
7678                                              self.op.shutdown_timeout)
7679     msg = result.fail_msg
7680     if msg:
7681       if self.op.ignore_failures:
7682         feedback_fn("Warning: can't shutdown instance: %s" % msg)
7683       else:
7684         raise errors.OpExecError("Could not shutdown instance %s on"
7685                                  " node %s: %s" %
7686                                  (instance.name, instance.primary_node, msg))
7687
7688     assert (self.owned_locks(locking.LEVEL_NODE) ==
7689             self.owned_locks(locking.LEVEL_NODE_RES))
7690     assert not (set(instance.all_nodes) -
7691                 self.owned_locks(locking.LEVEL_NODE)), \
7692       "Not owning correct locks"
7693
7694     _RemoveInstance(self, feedback_fn, instance, self.op.ignore_failures)
7695
7696
7697 def _RemoveInstance(lu, feedback_fn, instance, ignore_failures):
7698   """Utility function to remove an instance.
7699
7700   """
7701   logging.info("Removing block devices for instance %s", instance.name)
7702
7703   if not _RemoveDisks(lu, instance, ignore_failures=ignore_failures):
7704     if not ignore_failures:
7705       raise errors.OpExecError("Can't remove instance's disks")
7706     feedback_fn("Warning: can't remove instance's disks")
7707
7708   logging.info("Removing instance %s out of cluster config", instance.name)
7709
7710   lu.cfg.RemoveInstance(instance.name)
7711
7712   assert not lu.remove_locks.get(locking.LEVEL_INSTANCE), \
7713     "Instance lock removal conflict"
7714
7715   # Remove lock for the instance
7716   lu.remove_locks[locking.LEVEL_INSTANCE] = instance.name
7717
7718
7719 class LUInstanceQuery(NoHooksLU):
7720   """Logical unit for querying instances.
7721
7722   """
7723   # pylint: disable=W0142
7724   REQ_BGL = False
7725
7726   def CheckArguments(self):
7727     self.iq = _InstanceQuery(qlang.MakeSimpleFilter("name", self.op.names),
7728                              self.op.output_fields, self.op.use_locking)
7729
7730   def ExpandNames(self):
7731     self.iq.ExpandNames(self)
7732
7733   def DeclareLocks(self, level):
7734     self.iq.DeclareLocks(self, level)
7735
7736   def Exec(self, feedback_fn):
7737     return self.iq.OldStyleQuery(self)
7738
7739
7740 class LUInstanceFailover(LogicalUnit):
7741   """Failover an instance.
7742
7743   """
7744   HPATH = "instance-failover"
7745   HTYPE = constants.HTYPE_INSTANCE
7746   REQ_BGL = False
7747
7748   def CheckArguments(self):
7749     """Check the arguments.
7750
7751     """
7752     self.iallocator = getattr(self.op, "iallocator", None)
7753     self.target_node = getattr(self.op, "target_node", None)
7754
7755   def ExpandNames(self):
7756     self._ExpandAndLockInstance()
7757
7758     if self.op.target_node is not None:
7759       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7760
7761     self.needed_locks[locking.LEVEL_NODE] = []
7762     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7763
7764     self.needed_locks[locking.LEVEL_NODE_RES] = []
7765     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
7766
7767     ignore_consistency = self.op.ignore_consistency
7768     shutdown_timeout = self.op.shutdown_timeout
7769     self._migrater = TLMigrateInstance(self, self.op.instance_name,
7770                                        cleanup=False,
7771                                        failover=True,
7772                                        ignore_consistency=ignore_consistency,
7773                                        shutdown_timeout=shutdown_timeout,
7774                                        ignore_ipolicy=self.op.ignore_ipolicy)
7775     self.tasklets = [self._migrater]
7776
7777   def DeclareLocks(self, level):
7778     if level == locking.LEVEL_NODE:
7779       instance = self.context.cfg.GetInstanceInfo(self.op.instance_name)
7780       if instance.disk_template in constants.DTS_EXT_MIRROR:
7781         if self.op.target_node is None:
7782           self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7783         else:
7784           self.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
7785                                                    self.op.target_node]
7786         del self.recalculate_locks[locking.LEVEL_NODE]
7787       else:
7788         self._LockInstancesNodes()
7789     elif level == locking.LEVEL_NODE_RES:
7790       # Copy node locks
7791       self.needed_locks[locking.LEVEL_NODE_RES] = \
7792         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
7793
7794   def BuildHooksEnv(self):
7795     """Build hooks env.
7796
7797     This runs on master, primary and secondary nodes of the instance.
7798
7799     """
7800     instance = self._migrater.instance
7801     source_node = instance.primary_node
7802     target_node = self.op.target_node
7803     env = {
7804       "IGNORE_CONSISTENCY": self.op.ignore_consistency,
7805       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
7806       "OLD_PRIMARY": source_node,
7807       "NEW_PRIMARY": target_node,
7808       }
7809
7810     if instance.disk_template in constants.DTS_INT_MIRROR:
7811       env["OLD_SECONDARY"] = instance.secondary_nodes[0]
7812       env["NEW_SECONDARY"] = source_node
7813     else:
7814       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = ""
7815
7816     env.update(_BuildInstanceHookEnvByObject(self, instance))
7817
7818     return env
7819
7820   def BuildHooksNodes(self):
7821     """Build hooks nodes.
7822
7823     """
7824     instance = self._migrater.instance
7825     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
7826     return (nl, nl + [instance.primary_node])
7827
7828
7829 class LUInstanceMigrate(LogicalUnit):
7830   """Migrate an instance.
7831
7832   This is migration without shutting down, compared to the failover,
7833   which is done with shutdown.
7834
7835   """
7836   HPATH = "instance-migrate"
7837   HTYPE = constants.HTYPE_INSTANCE
7838   REQ_BGL = False
7839
7840   def ExpandNames(self):
7841     self._ExpandAndLockInstance()
7842
7843     if self.op.target_node is not None:
7844       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7845
7846     self.needed_locks[locking.LEVEL_NODE] = []
7847     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7848
7849     self.needed_locks[locking.LEVEL_NODE] = []
7850     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
7851
7852     self._migrater = \
7853       TLMigrateInstance(self, self.op.instance_name,
7854                         cleanup=self.op.cleanup,
7855                         failover=False,
7856                         fallback=self.op.allow_failover,
7857                         allow_runtime_changes=self.op.allow_runtime_changes,
7858                         ignore_ipolicy=self.op.ignore_ipolicy)
7859     self.tasklets = [self._migrater]
7860
7861   def DeclareLocks(self, level):
7862     if level == locking.LEVEL_NODE:
7863       instance = self.context.cfg.GetInstanceInfo(self.op.instance_name)
7864       if instance.disk_template in constants.DTS_EXT_MIRROR:
7865         if self.op.target_node is None:
7866           self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
7867         else:
7868           self.needed_locks[locking.LEVEL_NODE] = [instance.primary_node,
7869                                                    self.op.target_node]
7870         del self.recalculate_locks[locking.LEVEL_NODE]
7871       else:
7872         self._LockInstancesNodes()
7873     elif level == locking.LEVEL_NODE_RES:
7874       # Copy node locks
7875       self.needed_locks[locking.LEVEL_NODE_RES] = \
7876         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
7877
7878   def BuildHooksEnv(self):
7879     """Build hooks env.
7880
7881     This runs on master, primary and secondary nodes of the instance.
7882
7883     """
7884     instance = self._migrater.instance
7885     source_node = instance.primary_node
7886     target_node = self.op.target_node
7887     env = _BuildInstanceHookEnvByObject(self, instance)
7888     env.update({
7889       "MIGRATE_LIVE": self._migrater.live,
7890       "MIGRATE_CLEANUP": self.op.cleanup,
7891       "OLD_PRIMARY": source_node,
7892       "NEW_PRIMARY": target_node,
7893       "ALLOW_RUNTIME_CHANGES": self.op.allow_runtime_changes,
7894       })
7895
7896     if instance.disk_template in constants.DTS_INT_MIRROR:
7897       env["OLD_SECONDARY"] = target_node
7898       env["NEW_SECONDARY"] = source_node
7899     else:
7900       env["OLD_SECONDARY"] = env["NEW_SECONDARY"] = None
7901
7902     return env
7903
7904   def BuildHooksNodes(self):
7905     """Build hooks nodes.
7906
7907     """
7908     instance = self._migrater.instance
7909     nl = [self.cfg.GetMasterNode()] + list(instance.secondary_nodes)
7910     return (nl, nl + [instance.primary_node])
7911
7912
7913 class LUInstanceMove(LogicalUnit):
7914   """Move an instance by data-copying.
7915
7916   """
7917   HPATH = "instance-move"
7918   HTYPE = constants.HTYPE_INSTANCE
7919   REQ_BGL = False
7920
7921   def ExpandNames(self):
7922     self._ExpandAndLockInstance()
7923     target_node = _ExpandNodeName(self.cfg, self.op.target_node)
7924     self.op.target_node = target_node
7925     self.needed_locks[locking.LEVEL_NODE] = [target_node]
7926     self.needed_locks[locking.LEVEL_NODE_RES] = []
7927     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
7928
7929   def DeclareLocks(self, level):
7930     if level == locking.LEVEL_NODE:
7931       self._LockInstancesNodes(primary_only=True)
7932     elif level == locking.LEVEL_NODE_RES:
7933       # Copy node locks
7934       self.needed_locks[locking.LEVEL_NODE_RES] = \
7935         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
7936
7937   def BuildHooksEnv(self):
7938     """Build hooks env.
7939
7940     This runs on master, primary and secondary nodes of the instance.
7941
7942     """
7943     env = {
7944       "TARGET_NODE": self.op.target_node,
7945       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
7946       }
7947     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
7948     return env
7949
7950   def BuildHooksNodes(self):
7951     """Build hooks nodes.
7952
7953     """
7954     nl = [
7955       self.cfg.GetMasterNode(),
7956       self.instance.primary_node,
7957       self.op.target_node,
7958       ]
7959     return (nl, nl)
7960
7961   def CheckPrereq(self):
7962     """Check prerequisites.
7963
7964     This checks that the instance is in the cluster.
7965
7966     """
7967     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
7968     assert self.instance is not None, \
7969       "Cannot retrieve locked instance %s" % self.op.instance_name
7970
7971     node = self.cfg.GetNodeInfo(self.op.target_node)
7972     assert node is not None, \
7973       "Cannot retrieve locked node %s" % self.op.target_node
7974
7975     self.target_node = target_node = node.name
7976
7977     if target_node == instance.primary_node:
7978       raise errors.OpPrereqError("Instance %s is already on the node %s" %
7979                                  (instance.name, target_node),
7980                                  errors.ECODE_STATE)
7981
7982     bep = self.cfg.GetClusterInfo().FillBE(instance)
7983
7984     for idx, dsk in enumerate(instance.disks):
7985       if dsk.dev_type not in (constants.LD_LV, constants.LD_FILE):
7986         raise errors.OpPrereqError("Instance disk %d has a complex layout,"
7987                                    " cannot copy" % idx, errors.ECODE_STATE)
7988
7989     _CheckNodeOnline(self, target_node)
7990     _CheckNodeNotDrained(self, target_node)
7991     _CheckNodeVmCapable(self, target_node)
7992     ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(),
7993                                      self.cfg.GetNodeGroup(node.group))
7994     _CheckTargetNodeIPolicy(self, ipolicy, instance, node,
7995                             ignore=self.op.ignore_ipolicy)
7996
7997     if instance.admin_state == constants.ADMINST_UP:
7998       # check memory requirements on the secondary node
7999       _CheckNodeFreeMemory(self, target_node, "failing over instance %s" %
8000                            instance.name, bep[constants.BE_MAXMEM],
8001                            instance.hypervisor)
8002     else:
8003       self.LogInfo("Not checking memory on the secondary node as"
8004                    " instance will not be started")
8005
8006     # check bridge existance
8007     _CheckInstanceBridgesExist(self, instance, node=target_node)
8008
8009   def Exec(self, feedback_fn):
8010     """Move an instance.
8011
8012     The move is done by shutting it down on its present node, copying
8013     the data over (slow) and starting it on the new node.
8014
8015     """
8016     instance = self.instance
8017
8018     source_node = instance.primary_node
8019     target_node = self.target_node
8020
8021     self.LogInfo("Shutting down instance %s on source node %s",
8022                  instance.name, source_node)
8023
8024     assert (self.owned_locks(locking.LEVEL_NODE) ==
8025             self.owned_locks(locking.LEVEL_NODE_RES))
8026
8027     result = self.rpc.call_instance_shutdown(source_node, instance,
8028                                              self.op.shutdown_timeout)
8029     msg = result.fail_msg
8030     if msg:
8031       if self.op.ignore_consistency:
8032         self.proc.LogWarning("Could not shutdown instance %s on node %s."
8033                              " Proceeding anyway. Please make sure node"
8034                              " %s is down. Error details: %s",
8035                              instance.name, source_node, source_node, msg)
8036       else:
8037         raise errors.OpExecError("Could not shutdown instance %s on"
8038                                  " node %s: %s" %
8039                                  (instance.name, source_node, msg))
8040
8041     # create the target disks
8042     try:
8043       _CreateDisks(self, instance, target_node=target_node)
8044     except errors.OpExecError:
8045       self.LogWarning("Device creation failed, reverting...")
8046       try:
8047         _RemoveDisks(self, instance, target_node=target_node)
8048       finally:
8049         self.cfg.ReleaseDRBDMinors(instance.name)
8050         raise
8051
8052     cluster_name = self.cfg.GetClusterInfo().cluster_name
8053
8054     errs = []
8055     # activate, get path, copy the data over
8056     for idx, disk in enumerate(instance.disks):
8057       self.LogInfo("Copying data for disk %d", idx)
8058       result = self.rpc.call_blockdev_assemble(target_node, (disk, instance),
8059                                                instance.name, True, idx)
8060       if result.fail_msg:
8061         self.LogWarning("Can't assemble newly created disk %d: %s",
8062                         idx, result.fail_msg)
8063         errs.append(result.fail_msg)
8064         break
8065       dev_path = result.payload
8066       result = self.rpc.call_blockdev_export(source_node, (disk, instance),
8067                                              target_node, dev_path,
8068                                              cluster_name)
8069       if result.fail_msg:
8070         self.LogWarning("Can't copy data over for disk %d: %s",
8071                         idx, result.fail_msg)
8072         errs.append(result.fail_msg)
8073         break
8074
8075     if errs:
8076       self.LogWarning("Some disks failed to copy, aborting")
8077       try:
8078         _RemoveDisks(self, instance, target_node=target_node)
8079       finally:
8080         self.cfg.ReleaseDRBDMinors(instance.name)
8081         raise errors.OpExecError("Errors during disk copy: %s" %
8082                                  (",".join(errs),))
8083
8084     instance.primary_node = target_node
8085     self.cfg.Update(instance, feedback_fn)
8086
8087     self.LogInfo("Removing the disks on the original node")
8088     _RemoveDisks(self, instance, target_node=source_node)
8089
8090     # Only start the instance if it's marked as up
8091     if instance.admin_state == constants.ADMINST_UP:
8092       self.LogInfo("Starting instance %s on node %s",
8093                    instance.name, target_node)
8094
8095       disks_ok, _ = _AssembleInstanceDisks(self, instance,
8096                                            ignore_secondaries=True)
8097       if not disks_ok:
8098         _ShutdownInstanceDisks(self, instance)
8099         raise errors.OpExecError("Can't activate the instance's disks")
8100
8101       result = self.rpc.call_instance_start(target_node,
8102                                             (instance, None, None), False)
8103       msg = result.fail_msg
8104       if msg:
8105         _ShutdownInstanceDisks(self, instance)
8106         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
8107                                  (instance.name, target_node, msg))
8108
8109
8110 class LUNodeMigrate(LogicalUnit):
8111   """Migrate all instances from a node.
8112
8113   """
8114   HPATH = "node-migrate"
8115   HTYPE = constants.HTYPE_NODE
8116   REQ_BGL = False
8117
8118   def CheckArguments(self):
8119     pass
8120
8121   def ExpandNames(self):
8122     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
8123
8124     self.share_locks = _ShareAll()
8125     self.needed_locks = {
8126       locking.LEVEL_NODE: [self.op.node_name],
8127       }
8128
8129   def BuildHooksEnv(self):
8130     """Build hooks env.
8131
8132     This runs on the master, the primary and all the secondaries.
8133
8134     """
8135     return {
8136       "NODE_NAME": self.op.node_name,
8137       "ALLOW_RUNTIME_CHANGES": self.op.allow_runtime_changes,
8138       }
8139
8140   def BuildHooksNodes(self):
8141     """Build hooks nodes.
8142
8143     """
8144     nl = [self.cfg.GetMasterNode()]
8145     return (nl, nl)
8146
8147   def CheckPrereq(self):
8148     pass
8149
8150   def Exec(self, feedback_fn):
8151     # Prepare jobs for migration instances
8152     allow_runtime_changes = self.op.allow_runtime_changes
8153     jobs = [
8154       [opcodes.OpInstanceMigrate(instance_name=inst.name,
8155                                  mode=self.op.mode,
8156                                  live=self.op.live,
8157                                  iallocator=self.op.iallocator,
8158                                  target_node=self.op.target_node,
8159                                  allow_runtime_changes=allow_runtime_changes,
8160                                  ignore_ipolicy=self.op.ignore_ipolicy)]
8161       for inst in _GetNodePrimaryInstances(self.cfg, self.op.node_name)
8162       ]
8163
8164     # TODO: Run iallocator in this opcode and pass correct placement options to
8165     # OpInstanceMigrate. Since other jobs can modify the cluster between
8166     # running the iallocator and the actual migration, a good consistency model
8167     # will have to be found.
8168
8169     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
8170             frozenset([self.op.node_name]))
8171
8172     return ResultWithJobs(jobs)
8173
8174
8175 class TLMigrateInstance(Tasklet):
8176   """Tasklet class for instance migration.
8177
8178   @type live: boolean
8179   @ivar live: whether the migration will be done live or non-live;
8180       this variable is initalized only after CheckPrereq has run
8181   @type cleanup: boolean
8182   @ivar cleanup: Wheater we cleanup from a failed migration
8183   @type iallocator: string
8184   @ivar iallocator: The iallocator used to determine target_node
8185   @type target_node: string
8186   @ivar target_node: If given, the target_node to reallocate the instance to
8187   @type failover: boolean
8188   @ivar failover: Whether operation results in failover or migration
8189   @type fallback: boolean
8190   @ivar fallback: Whether fallback to failover is allowed if migration not
8191                   possible
8192   @type ignore_consistency: boolean
8193   @ivar ignore_consistency: Wheter we should ignore consistency between source
8194                             and target node
8195   @type shutdown_timeout: int
8196   @ivar shutdown_timeout: In case of failover timeout of the shutdown
8197   @type ignore_ipolicy: bool
8198   @ivar ignore_ipolicy: If true, we can ignore instance policy when migrating
8199
8200   """
8201
8202   # Constants
8203   _MIGRATION_POLL_INTERVAL = 1      # seconds
8204   _MIGRATION_FEEDBACK_INTERVAL = 10 # seconds
8205
8206   def __init__(self, lu, instance_name, cleanup=False,
8207                failover=False, fallback=False,
8208                ignore_consistency=False,
8209                allow_runtime_changes=True,
8210                shutdown_timeout=constants.DEFAULT_SHUTDOWN_TIMEOUT,
8211                ignore_ipolicy=False):
8212     """Initializes this class.
8213
8214     """
8215     Tasklet.__init__(self, lu)
8216
8217     # Parameters
8218     self.instance_name = instance_name
8219     self.cleanup = cleanup
8220     self.live = False # will be overridden later
8221     self.failover = failover
8222     self.fallback = fallback
8223     self.ignore_consistency = ignore_consistency
8224     self.shutdown_timeout = shutdown_timeout
8225     self.ignore_ipolicy = ignore_ipolicy
8226     self.allow_runtime_changes = allow_runtime_changes
8227
8228   def CheckPrereq(self):
8229     """Check prerequisites.
8230
8231     This checks that the instance is in the cluster.
8232
8233     """
8234     instance_name = _ExpandInstanceName(self.lu.cfg, self.instance_name)
8235     instance = self.cfg.GetInstanceInfo(instance_name)
8236     assert instance is not None
8237     self.instance = instance
8238     cluster = self.cfg.GetClusterInfo()
8239
8240     if (not self.cleanup and
8241         not instance.admin_state == constants.ADMINST_UP and
8242         not self.failover and self.fallback):
8243       self.lu.LogInfo("Instance is marked down or offline, fallback allowed,"
8244                       " switching to failover")
8245       self.failover = True
8246
8247     if instance.disk_template not in constants.DTS_MIRRORED:
8248       if self.failover:
8249         text = "failovers"
8250       else:
8251         text = "migrations"
8252       raise errors.OpPrereqError("Instance's disk layout '%s' does not allow"
8253                                  " %s" % (instance.disk_template, text),
8254                                  errors.ECODE_STATE)
8255
8256     if instance.disk_template in constants.DTS_EXT_MIRROR:
8257       _CheckIAllocatorOrNode(self.lu, "iallocator", "target_node")
8258
8259       if self.lu.op.iallocator:
8260         self._RunAllocator()
8261       else:
8262         # We set set self.target_node as it is required by
8263         # BuildHooksEnv
8264         self.target_node = self.lu.op.target_node
8265
8266       # Check that the target node is correct in terms of instance policy
8267       nodeinfo = self.cfg.GetNodeInfo(self.target_node)
8268       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
8269       ipolicy = _CalculateGroupIPolicy(cluster, group_info)
8270       _CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo,
8271                               ignore=self.ignore_ipolicy)
8272
8273       # self.target_node is already populated, either directly or by the
8274       # iallocator run
8275       target_node = self.target_node
8276       if self.target_node == instance.primary_node:
8277         raise errors.OpPrereqError("Cannot migrate instance %s"
8278                                    " to its primary (%s)" %
8279                                    (instance.name, instance.primary_node))
8280
8281       if len(self.lu.tasklets) == 1:
8282         # It is safe to release locks only when we're the only tasklet
8283         # in the LU
8284         _ReleaseLocks(self.lu, locking.LEVEL_NODE,
8285                       keep=[instance.primary_node, self.target_node])
8286
8287     else:
8288       secondary_nodes = instance.secondary_nodes
8289       if not secondary_nodes:
8290         raise errors.ConfigurationError("No secondary node but using"
8291                                         " %s disk template" %
8292                                         instance.disk_template)
8293       target_node = secondary_nodes[0]
8294       if self.lu.op.iallocator or (self.lu.op.target_node and
8295                                    self.lu.op.target_node != target_node):
8296         if self.failover:
8297           text = "failed over"
8298         else:
8299           text = "migrated"
8300         raise errors.OpPrereqError("Instances with disk template %s cannot"
8301                                    " be %s to arbitrary nodes"
8302                                    " (neither an iallocator nor a target"
8303                                    " node can be passed)" %
8304                                    (instance.disk_template, text),
8305                                    errors.ECODE_INVAL)
8306       nodeinfo = self.cfg.GetNodeInfo(target_node)
8307       group_info = self.cfg.GetNodeGroup(nodeinfo.group)
8308       ipolicy = _CalculateGroupIPolicy(cluster, group_info)
8309       _CheckTargetNodeIPolicy(self.lu, ipolicy, instance, nodeinfo,
8310                               ignore=self.ignore_ipolicy)
8311
8312     i_be = cluster.FillBE(instance)
8313
8314     # check memory requirements on the secondary node
8315     if (not self.cleanup and
8316          (not self.failover or instance.admin_state == constants.ADMINST_UP)):
8317       self.tgt_free_mem = _CheckNodeFreeMemory(self.lu, target_node,
8318                                                "migrating instance %s" %
8319                                                instance.name,
8320                                                i_be[constants.BE_MINMEM],
8321                                                instance.hypervisor)
8322     else:
8323       self.lu.LogInfo("Not checking memory on the secondary node as"
8324                       " instance will not be started")
8325
8326     # check if failover must be forced instead of migration
8327     if (not self.cleanup and not self.failover and
8328         i_be[constants.BE_ALWAYS_FAILOVER]):
8329       self.lu.LogInfo("Instance configured to always failover; fallback"
8330                       " to failover")
8331       self.failover = True
8332
8333     # check bridge existance
8334     _CheckInstanceBridgesExist(self.lu, instance, node=target_node)
8335
8336     if not self.cleanup:
8337       _CheckNodeNotDrained(self.lu, target_node)
8338       if not self.failover:
8339         result = self.rpc.call_instance_migratable(instance.primary_node,
8340                                                    instance)
8341         if result.fail_msg and self.fallback:
8342           self.lu.LogInfo("Can't migrate, instance offline, fallback to"
8343                           " failover")
8344           self.failover = True
8345         else:
8346           result.Raise("Can't migrate, please use failover",
8347                        prereq=True, ecode=errors.ECODE_STATE)
8348
8349     assert not (self.failover and self.cleanup)
8350
8351     if not self.failover:
8352       if self.lu.op.live is not None and self.lu.op.mode is not None:
8353         raise errors.OpPrereqError("Only one of the 'live' and 'mode'"
8354                                    " parameters are accepted",
8355                                    errors.ECODE_INVAL)
8356       if self.lu.op.live is not None:
8357         if self.lu.op.live:
8358           self.lu.op.mode = constants.HT_MIGRATION_LIVE
8359         else:
8360           self.lu.op.mode = constants.HT_MIGRATION_NONLIVE
8361         # reset the 'live' parameter to None so that repeated
8362         # invocations of CheckPrereq do not raise an exception
8363         self.lu.op.live = None
8364       elif self.lu.op.mode is None:
8365         # read the default value from the hypervisor
8366         i_hv = cluster.FillHV(self.instance, skip_globals=False)
8367         self.lu.op.mode = i_hv[constants.HV_MIGRATION_MODE]
8368
8369       self.live = self.lu.op.mode == constants.HT_MIGRATION_LIVE
8370     else:
8371       # Failover is never live
8372       self.live = False
8373
8374     if not (self.failover or self.cleanup):
8375       remote_info = self.rpc.call_instance_info(instance.primary_node,
8376                                                 instance.name,
8377                                                 instance.hypervisor)
8378       remote_info.Raise("Error checking instance on node %s" %
8379                         instance.primary_node)
8380       instance_running = bool(remote_info.payload)
8381       if instance_running:
8382         self.current_mem = int(remote_info.payload["memory"])
8383
8384   def _RunAllocator(self):
8385     """Run the allocator based on input opcode.
8386
8387     """
8388     # FIXME: add a self.ignore_ipolicy option
8389     ial = IAllocator(self.cfg, self.rpc,
8390                      mode=constants.IALLOCATOR_MODE_RELOC,
8391                      name=self.instance_name,
8392                      relocate_from=[self.instance.primary_node],
8393                      )
8394
8395     ial.Run(self.lu.op.iallocator)
8396
8397     if not ial.success:
8398       raise errors.OpPrereqError("Can't compute nodes using"
8399                                  " iallocator '%s': %s" %
8400                                  (self.lu.op.iallocator, ial.info),
8401                                  errors.ECODE_NORES)
8402     if len(ial.result) != ial.required_nodes:
8403       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
8404                                  " of nodes (%s), required %s" %
8405                                  (self.lu.op.iallocator, len(ial.result),
8406                                   ial.required_nodes), errors.ECODE_FAULT)
8407     self.target_node = ial.result[0]
8408     self.lu.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
8409                  self.instance_name, self.lu.op.iallocator,
8410                  utils.CommaJoin(ial.result))
8411
8412   def _WaitUntilSync(self):
8413     """Poll with custom rpc for disk sync.
8414
8415     This uses our own step-based rpc call.
8416
8417     """
8418     self.feedback_fn("* wait until resync is done")
8419     all_done = False
8420     while not all_done:
8421       all_done = True
8422       result = self.rpc.call_drbd_wait_sync(self.all_nodes,
8423                                             self.nodes_ip,
8424                                             (self.instance.disks,
8425                                              self.instance))
8426       min_percent = 100
8427       for node, nres in result.items():
8428         nres.Raise("Cannot resync disks on node %s" % node)
8429         node_done, node_percent = nres.payload
8430         all_done = all_done and node_done
8431         if node_percent is not None:
8432           min_percent = min(min_percent, node_percent)
8433       if not all_done:
8434         if min_percent < 100:
8435           self.feedback_fn("   - progress: %.1f%%" % min_percent)
8436         time.sleep(2)
8437
8438   def _EnsureSecondary(self, node):
8439     """Demote a node to secondary.
8440
8441     """
8442     self.feedback_fn("* switching node %s to secondary mode" % node)
8443
8444     for dev in self.instance.disks:
8445       self.cfg.SetDiskID(dev, node)
8446
8447     result = self.rpc.call_blockdev_close(node, self.instance.name,
8448                                           self.instance.disks)
8449     result.Raise("Cannot change disk to secondary on node %s" % node)
8450
8451   def _GoStandalone(self):
8452     """Disconnect from the network.
8453
8454     """
8455     self.feedback_fn("* changing into standalone mode")
8456     result = self.rpc.call_drbd_disconnect_net(self.all_nodes, self.nodes_ip,
8457                                                self.instance.disks)
8458     for node, nres in result.items():
8459       nres.Raise("Cannot disconnect disks node %s" % node)
8460
8461   def _GoReconnect(self, multimaster):
8462     """Reconnect to the network.
8463
8464     """
8465     if multimaster:
8466       msg = "dual-master"
8467     else:
8468       msg = "single-master"
8469     self.feedback_fn("* changing disks into %s mode" % msg)
8470     result = self.rpc.call_drbd_attach_net(self.all_nodes, self.nodes_ip,
8471                                            (self.instance.disks, self.instance),
8472                                            self.instance.name, multimaster)
8473     for node, nres in result.items():
8474       nres.Raise("Cannot change disks config on node %s" % node)
8475
8476   def _ExecCleanup(self):
8477     """Try to cleanup after a failed migration.
8478
8479     The cleanup is done by:
8480       - check that the instance is running only on one node
8481         (and update the config if needed)
8482       - change disks on its secondary node to secondary
8483       - wait until disks are fully synchronized
8484       - disconnect from the network
8485       - change disks into single-master mode
8486       - wait again until disks are fully synchronized
8487
8488     """
8489     instance = self.instance
8490     target_node = self.target_node
8491     source_node = self.source_node
8492
8493     # check running on only one node
8494     self.feedback_fn("* checking where the instance actually runs"
8495                      " (if this hangs, the hypervisor might be in"
8496                      " a bad state)")
8497     ins_l = self.rpc.call_instance_list(self.all_nodes, [instance.hypervisor])
8498     for node, result in ins_l.items():
8499       result.Raise("Can't contact node %s" % node)
8500
8501     runningon_source = instance.name in ins_l[source_node].payload
8502     runningon_target = instance.name in ins_l[target_node].payload
8503
8504     if runningon_source and runningon_target:
8505       raise errors.OpExecError("Instance seems to be running on two nodes,"
8506                                " or the hypervisor is confused; you will have"
8507                                " to ensure manually that it runs only on one"
8508                                " and restart this operation")
8509
8510     if not (runningon_source or runningon_target):
8511       raise errors.OpExecError("Instance does not seem to be running at all;"
8512                                " in this case it's safer to repair by"
8513                                " running 'gnt-instance stop' to ensure disk"
8514                                " shutdown, and then restarting it")
8515
8516     if runningon_target:
8517       # the migration has actually succeeded, we need to update the config
8518       self.feedback_fn("* instance running on secondary node (%s),"
8519                        " updating config" % target_node)
8520       instance.primary_node = target_node
8521       self.cfg.Update(instance, self.feedback_fn)
8522       demoted_node = source_node
8523     else:
8524       self.feedback_fn("* instance confirmed to be running on its"
8525                        " primary node (%s)" % source_node)
8526       demoted_node = target_node
8527
8528     if instance.disk_template in constants.DTS_INT_MIRROR:
8529       self._EnsureSecondary(demoted_node)
8530       try:
8531         self._WaitUntilSync()
8532       except errors.OpExecError:
8533         # we ignore here errors, since if the device is standalone, it
8534         # won't be able to sync
8535         pass
8536       self._GoStandalone()
8537       self._GoReconnect(False)
8538       self._WaitUntilSync()
8539
8540     self.feedback_fn("* done")
8541
8542   def _RevertDiskStatus(self):
8543     """Try to revert the disk status after a failed migration.
8544
8545     """
8546     target_node = self.target_node
8547     if self.instance.disk_template in constants.DTS_EXT_MIRROR:
8548       return
8549
8550     try:
8551       self._EnsureSecondary(target_node)
8552       self._GoStandalone()
8553       self._GoReconnect(False)
8554       self._WaitUntilSync()
8555     except errors.OpExecError, err:
8556       self.lu.LogWarning("Migration failed and I can't reconnect the drives,"
8557                          " please try to recover the instance manually;"
8558                          " error '%s'" % str(err))
8559
8560   def _AbortMigration(self):
8561     """Call the hypervisor code to abort a started migration.
8562
8563     """
8564     instance = self.instance
8565     target_node = self.target_node
8566     source_node = self.source_node
8567     migration_info = self.migration_info
8568
8569     abort_result = self.rpc.call_instance_finalize_migration_dst(target_node,
8570                                                                  instance,
8571                                                                  migration_info,
8572                                                                  False)
8573     abort_msg = abort_result.fail_msg
8574     if abort_msg:
8575       logging.error("Aborting migration failed on target node %s: %s",
8576                     target_node, abort_msg)
8577       # Don't raise an exception here, as we stil have to try to revert the
8578       # disk status, even if this step failed.
8579
8580     abort_result = self.rpc.call_instance_finalize_migration_src(source_node,
8581         instance, False, self.live)
8582     abort_msg = abort_result.fail_msg
8583     if abort_msg:
8584       logging.error("Aborting migration failed on source node %s: %s",
8585                     source_node, abort_msg)
8586
8587   def _ExecMigration(self):
8588     """Migrate an instance.
8589
8590     The migrate is done by:
8591       - change the disks into dual-master mode
8592       - wait until disks are fully synchronized again
8593       - migrate the instance
8594       - change disks on the new secondary node (the old primary) to secondary
8595       - wait until disks are fully synchronized
8596       - change disks into single-master mode
8597
8598     """
8599     instance = self.instance
8600     target_node = self.target_node
8601     source_node = self.source_node
8602
8603     # Check for hypervisor version mismatch and warn the user.
8604     nodeinfo = self.rpc.call_node_info([source_node, target_node],
8605                                        None, [self.instance.hypervisor])
8606     for ninfo in nodeinfo.values():
8607       ninfo.Raise("Unable to retrieve node information from node '%s'" %
8608                   ninfo.node)
8609     (_, _, (src_info, )) = nodeinfo[source_node].payload
8610     (_, _, (dst_info, )) = nodeinfo[target_node].payload
8611
8612     if ((constants.HV_NODEINFO_KEY_VERSION in src_info) and
8613         (constants.HV_NODEINFO_KEY_VERSION in dst_info)):
8614       src_version = src_info[constants.HV_NODEINFO_KEY_VERSION]
8615       dst_version = dst_info[constants.HV_NODEINFO_KEY_VERSION]
8616       if src_version != dst_version:
8617         self.feedback_fn("* warning: hypervisor version mismatch between"
8618                          " source (%s) and target (%s) node" %
8619                          (src_version, dst_version))
8620
8621     self.feedback_fn("* checking disk consistency between source and target")
8622     for (idx, dev) in enumerate(instance.disks):
8623       if not _CheckDiskConsistency(self.lu, instance, dev, target_node, False):
8624         raise errors.OpExecError("Disk %s is degraded or not fully"
8625                                  " synchronized on target node,"
8626                                  " aborting migration" % idx)
8627
8628     if self.current_mem > self.tgt_free_mem:
8629       if not self.allow_runtime_changes:
8630         raise errors.OpExecError("Memory ballooning not allowed and not enough"
8631                                  " free memory to fit instance %s on target"
8632                                  " node %s (have %dMB, need %dMB)" %
8633                                  (instance.name, target_node,
8634                                   self.tgt_free_mem, self.current_mem))
8635       self.feedback_fn("* setting instance memory to %s" % self.tgt_free_mem)
8636       rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
8637                                                      instance,
8638                                                      self.tgt_free_mem)
8639       rpcres.Raise("Cannot modify instance runtime memory")
8640
8641     # First get the migration information from the remote node
8642     result = self.rpc.call_migration_info(source_node, instance)
8643     msg = result.fail_msg
8644     if msg:
8645       log_err = ("Failed fetching source migration information from %s: %s" %
8646                  (source_node, msg))
8647       logging.error(log_err)
8648       raise errors.OpExecError(log_err)
8649
8650     self.migration_info = migration_info = result.payload
8651
8652     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
8653       # Then switch the disks to master/master mode
8654       self._EnsureSecondary(target_node)
8655       self._GoStandalone()
8656       self._GoReconnect(True)
8657       self._WaitUntilSync()
8658
8659     self.feedback_fn("* preparing %s to accept the instance" % target_node)
8660     result = self.rpc.call_accept_instance(target_node,
8661                                            instance,
8662                                            migration_info,
8663                                            self.nodes_ip[target_node])
8664
8665     msg = result.fail_msg
8666     if msg:
8667       logging.error("Instance pre-migration failed, trying to revert"
8668                     " disk status: %s", msg)
8669       self.feedback_fn("Pre-migration failed, aborting")
8670       self._AbortMigration()
8671       self._RevertDiskStatus()
8672       raise errors.OpExecError("Could not pre-migrate instance %s: %s" %
8673                                (instance.name, msg))
8674
8675     self.feedback_fn("* migrating instance to %s" % target_node)
8676     result = self.rpc.call_instance_migrate(source_node, instance,
8677                                             self.nodes_ip[target_node],
8678                                             self.live)
8679     msg = result.fail_msg
8680     if msg:
8681       logging.error("Instance migration failed, trying to revert"
8682                     " disk status: %s", msg)
8683       self.feedback_fn("Migration failed, aborting")
8684       self._AbortMigration()
8685       self._RevertDiskStatus()
8686       raise errors.OpExecError("Could not migrate instance %s: %s" %
8687                                (instance.name, msg))
8688
8689     self.feedback_fn("* starting memory transfer")
8690     last_feedback = time.time()
8691     while True:
8692       result = self.rpc.call_instance_get_migration_status(source_node,
8693                                                            instance)
8694       msg = result.fail_msg
8695       ms = result.payload   # MigrationStatus instance
8696       if msg or (ms.status in constants.HV_MIGRATION_FAILED_STATUSES):
8697         logging.error("Instance migration failed, trying to revert"
8698                       " disk status: %s", msg)
8699         self.feedback_fn("Migration failed, aborting")
8700         self._AbortMigration()
8701         self._RevertDiskStatus()
8702         raise errors.OpExecError("Could not migrate instance %s: %s" %
8703                                  (instance.name, msg))
8704
8705       if result.payload.status != constants.HV_MIGRATION_ACTIVE:
8706         self.feedback_fn("* memory transfer complete")
8707         break
8708
8709       if (utils.TimeoutExpired(last_feedback,
8710                                self._MIGRATION_FEEDBACK_INTERVAL) and
8711           ms.transferred_ram is not None):
8712         mem_progress = 100 * float(ms.transferred_ram) / float(ms.total_ram)
8713         self.feedback_fn("* memory transfer progress: %.2f %%" % mem_progress)
8714         last_feedback = time.time()
8715
8716       time.sleep(self._MIGRATION_POLL_INTERVAL)
8717
8718     result = self.rpc.call_instance_finalize_migration_src(source_node,
8719                                                            instance,
8720                                                            True,
8721                                                            self.live)
8722     msg = result.fail_msg
8723     if msg:
8724       logging.error("Instance migration succeeded, but finalization failed"
8725                     " on the source node: %s", msg)
8726       raise errors.OpExecError("Could not finalize instance migration: %s" %
8727                                msg)
8728
8729     instance.primary_node = target_node
8730
8731     # distribute new instance config to the other nodes
8732     self.cfg.Update(instance, self.feedback_fn)
8733
8734     result = self.rpc.call_instance_finalize_migration_dst(target_node,
8735                                                            instance,
8736                                                            migration_info,
8737                                                            True)
8738     msg = result.fail_msg
8739     if msg:
8740       logging.error("Instance migration succeeded, but finalization failed"
8741                     " on the target node: %s", msg)
8742       raise errors.OpExecError("Could not finalize instance migration: %s" %
8743                                msg)
8744
8745     if self.instance.disk_template not in constants.DTS_EXT_MIRROR:
8746       self._EnsureSecondary(source_node)
8747       self._WaitUntilSync()
8748       self._GoStandalone()
8749       self._GoReconnect(False)
8750       self._WaitUntilSync()
8751
8752     # If the instance's disk template is `rbd' or `ext' and there was a
8753     # successful migration, unmap the device from the source node.
8754     if self.instance.disk_template in (constants.DT_RBD, constants.DT_EXT):
8755       disks = _ExpandCheckDisks(instance, instance.disks)
8756       self.feedback_fn("* unmapping instance's disks from %s" % source_node)
8757       for disk in disks:
8758         result = self.rpc.call_blockdev_shutdown(source_node, (disk, instance))
8759         msg = result.fail_msg
8760         if msg:
8761           logging.error("Migration was successful, but couldn't unmap the"
8762                         " block device %s on source node %s: %s",
8763                         disk.iv_name, source_node, msg)
8764           logging.error("You need to unmap the device %s manually on %s",
8765                         disk.iv_name, source_node)
8766
8767     self.feedback_fn("* done")
8768
8769   def _ExecFailover(self):
8770     """Failover an instance.
8771
8772     The failover is done by shutting it down on its present node and
8773     starting it on the secondary.
8774
8775     """
8776     instance = self.instance
8777     primary_node = self.cfg.GetNodeInfo(instance.primary_node)
8778
8779     source_node = instance.primary_node
8780     target_node = self.target_node
8781
8782     if instance.admin_state == constants.ADMINST_UP:
8783       self.feedback_fn("* checking disk consistency between source and target")
8784       for (idx, dev) in enumerate(instance.disks):
8785         # for drbd, these are drbd over lvm
8786         if not _CheckDiskConsistency(self.lu, instance, dev, target_node,
8787                                      False):
8788           if primary_node.offline:
8789             self.feedback_fn("Node %s is offline, ignoring degraded disk %s on"
8790                              " target node %s" %
8791                              (primary_node.name, idx, target_node))
8792           elif not self.ignore_consistency:
8793             raise errors.OpExecError("Disk %s is degraded on target node,"
8794                                      " aborting failover" % idx)
8795     else:
8796       self.feedback_fn("* not checking disk consistency as instance is not"
8797                        " running")
8798
8799     self.feedback_fn("* shutting down instance on source node")
8800     logging.info("Shutting down instance %s on node %s",
8801                  instance.name, source_node)
8802
8803     result = self.rpc.call_instance_shutdown(source_node, instance,
8804                                              self.shutdown_timeout)
8805     msg = result.fail_msg
8806     if msg:
8807       if self.ignore_consistency or primary_node.offline:
8808         self.lu.LogWarning("Could not shutdown instance %s on node %s,"
8809                            " proceeding anyway; please make sure node"
8810                            " %s is down; error details: %s",
8811                            instance.name, source_node, source_node, msg)
8812       else:
8813         raise errors.OpExecError("Could not shutdown instance %s on"
8814                                  " node %s: %s" %
8815                                  (instance.name, source_node, msg))
8816
8817     self.feedback_fn("* deactivating the instance's disks on source node")
8818     if not _ShutdownInstanceDisks(self.lu, instance, ignore_primary=True):
8819       raise errors.OpExecError("Can't shut down the instance's disks")
8820
8821     instance.primary_node = target_node
8822     # distribute new instance config to the other nodes
8823     self.cfg.Update(instance, self.feedback_fn)
8824
8825     # Only start the instance if it's marked as up
8826     if instance.admin_state == constants.ADMINST_UP:
8827       self.feedback_fn("* activating the instance's disks on target node %s" %
8828                        target_node)
8829       logging.info("Starting instance %s on node %s",
8830                    instance.name, target_node)
8831
8832       disks_ok, _ = _AssembleInstanceDisks(self.lu, instance,
8833                                            ignore_secondaries=True)
8834       if not disks_ok:
8835         _ShutdownInstanceDisks(self.lu, instance)
8836         raise errors.OpExecError("Can't activate the instance's disks")
8837
8838       self.feedback_fn("* starting the instance on the target node %s" %
8839                        target_node)
8840       result = self.rpc.call_instance_start(target_node, (instance, None, None),
8841                                             False)
8842       msg = result.fail_msg
8843       if msg:
8844         _ShutdownInstanceDisks(self.lu, instance)
8845         raise errors.OpExecError("Could not start instance %s on node %s: %s" %
8846                                  (instance.name, target_node, msg))
8847
8848   def Exec(self, feedback_fn):
8849     """Perform the migration.
8850
8851     """
8852     self.feedback_fn = feedback_fn
8853     self.source_node = self.instance.primary_node
8854
8855     # FIXME: if we implement migrate-to-any in DRBD, this needs fixing
8856     if self.instance.disk_template in constants.DTS_INT_MIRROR:
8857       self.target_node = self.instance.secondary_nodes[0]
8858       # Otherwise self.target_node has been populated either
8859       # directly, or through an iallocator.
8860
8861     self.all_nodes = [self.source_node, self.target_node]
8862     self.nodes_ip = dict((name, node.secondary_ip) for (name, node)
8863                          in self.cfg.GetMultiNodeInfo(self.all_nodes))
8864
8865     if self.failover:
8866       feedback_fn("Failover instance %s" % self.instance.name)
8867       self._ExecFailover()
8868     else:
8869       feedback_fn("Migrating instance %s" % self.instance.name)
8870
8871       if self.cleanup:
8872         return self._ExecCleanup()
8873       else:
8874         return self._ExecMigration()
8875
8876
8877 def _CreateBlockDev(lu, node, instance, device, force_create, info,
8878                     force_open):
8879   """Wrapper around L{_CreateBlockDevInner}.
8880
8881   This method annotates the root device first.
8882
8883   """
8884   (disk,) = _AnnotateDiskParams(instance, [device], lu.cfg)
8885   return _CreateBlockDevInner(lu, node, instance, disk, force_create, info,
8886                               force_open)
8887
8888
8889 def _CreateBlockDevInner(lu, node, instance, device, force_create,
8890                          info, force_open):
8891   """Create a tree of block devices on a given node.
8892
8893   If this device type has to be created on secondaries, create it and
8894   all its children.
8895
8896   If not, just recurse to children keeping the same 'force' value.
8897
8898   @attention: The device has to be annotated already.
8899
8900   @param lu: the lu on whose behalf we execute
8901   @param node: the node on which to create the device
8902   @type instance: L{objects.Instance}
8903   @param instance: the instance which owns the device
8904   @type device: L{objects.Disk}
8905   @param device: the device to create
8906   @type force_create: boolean
8907   @param force_create: whether to force creation of this device; this
8908       will be change to True whenever we find a device which has
8909       CreateOnSecondary() attribute
8910   @param info: the extra 'metadata' we should attach to the device
8911       (this will be represented as a LVM tag)
8912   @type force_open: boolean
8913   @param force_open: this parameter will be passes to the
8914       L{backend.BlockdevCreate} function where it specifies
8915       whether we run on primary or not, and it affects both
8916       the child assembly and the device own Open() execution
8917
8918   """
8919   if device.CreateOnSecondary():
8920     force_create = True
8921
8922   if device.children:
8923     for child in device.children:
8924       _CreateBlockDevInner(lu, node, instance, child, force_create,
8925                            info, force_open)
8926
8927   if not force_create:
8928     return
8929
8930   _CreateSingleBlockDev(lu, node, instance, device, info, force_open)
8931
8932
8933 def _CreateSingleBlockDev(lu, node, instance, device, info, force_open):
8934   """Create a single block device on a given node.
8935
8936   This will not recurse over children of the device, so they must be
8937   created in advance.
8938
8939   @param lu: the lu on whose behalf we execute
8940   @param node: the node on which to create the device
8941   @type instance: L{objects.Instance}
8942   @param instance: the instance which owns the device
8943   @type device: L{objects.Disk}
8944   @param device: the device to create
8945   @param info: the extra 'metadata' we should attach to the device
8946       (this will be represented as a LVM tag)
8947   @type force_open: boolean
8948   @param force_open: this parameter will be passes to the
8949       L{backend.BlockdevCreate} function where it specifies
8950       whether we run on primary or not, and it affects both
8951       the child assembly and the device own Open() execution
8952
8953   """
8954   lu.cfg.SetDiskID(device, node)
8955   result = lu.rpc.call_blockdev_create(node, device, device.size,
8956                                        instance.name, force_open, info)
8957   result.Raise("Can't create block device %s on"
8958                " node %s for instance %s" % (device, node, instance.name))
8959   if device.physical_id is None:
8960     device.physical_id = result.payload
8961
8962
8963 def _GenerateUniqueNames(lu, exts):
8964   """Generate a suitable LV name.
8965
8966   This will generate a logical volume name for the given instance.
8967
8968   """
8969   results = []
8970   for val in exts:
8971     new_id = lu.cfg.GenerateUniqueID(lu.proc.GetECId())
8972     results.append("%s%s" % (new_id, val))
8973   return results
8974
8975 def _GetPCIInfo(lu, dev_type):
8976
8977   if (hasattr(lu, 'op') and lu.op.hotplug):
8978     # case of InstanceCreate()
8979     if hasattr(lu, 'hotplug_info'):
8980       if lu.hotplug_info is not None:
8981         idx = getattr(lu.hotplug_info, dev_type)
8982         setattr(lu.hotplug_info, dev_type, idx+1)
8983         if dev_type == 'disks' and idx == 0:
8984           lu.LogInfo("Disk 0 cannot be hotpluggable.")
8985           return None, None
8986         pci = lu.hotplug_info.pci_pool.pop()
8987         lu.LogInfo("Choosing pci slot %d" % pci)
8988         return idx, pci
8989     # case of InstanceSetParams()
8990     elif lu.instance.hotplug_info is not None:
8991       idx, pci = lu.cfg.GetPCIInfo(lu.instance, dev_type)
8992       lu.LogInfo("Choosing pci slot %d" % pci)
8993       return idx, pci
8994
8995   lu.LogWarning("Hotplug not supported for this instance.")
8996   return None, None
8997
8998
8999 def _GenerateDRBD8Branch(lu, primary, secondary, size, vgnames, names,
9000                          iv_name, p_minor, s_minor):
9001   """Generate a drbd8 device complete with its children.
9002
9003   """
9004   assert len(vgnames) == len(names) == 2
9005   port = lu.cfg.AllocatePort()
9006   shared_secret = lu.cfg.GenerateDRBDSecret(lu.proc.GetECId())
9007
9008   dev_data = objects.Disk(dev_type=constants.LD_LV, size=size,
9009                           logical_id=(vgnames[0], names[0]),
9010                           params={})
9011   dev_meta = objects.Disk(dev_type=constants.LD_LV, size=DRBD_META_SIZE,
9012                           logical_id=(vgnames[1], names[1]),
9013                           params={})
9014
9015   disk_idx, pci = _GetPCIInfo(lu, 'disks')
9016   drbd_dev = objects.Disk(idx=disk_idx, pci=pci,
9017                           dev_type=constants.LD_DRBD8, size=size,
9018                           logical_id=(primary, secondary, port,
9019                                       p_minor, s_minor,
9020                                       shared_secret),
9021                           children=[dev_data, dev_meta],
9022                           iv_name=iv_name, params={})
9023   return drbd_dev
9024
9025
9026 _DISK_TEMPLATE_NAME_PREFIX = {
9027   constants.DT_PLAIN: "",
9028   constants.DT_RBD: ".rbd",
9029   constants.DT_EXT: ".ext",
9030   }
9031
9032
9033 _DISK_TEMPLATE_DEVICE_TYPE = {
9034   constants.DT_PLAIN: constants.LD_LV,
9035   constants.DT_FILE: constants.LD_FILE,
9036   constants.DT_SHARED_FILE: constants.LD_FILE,
9037   constants.DT_BLOCK: constants.LD_BLOCKDEV,
9038   constants.DT_RBD: constants.LD_RBD,
9039   constants.DT_EXT: constants.LD_EXT,
9040   }
9041
9042
9043 def _GenerateDiskTemplate(lu, template_name, instance_name, primary_node,
9044     secondary_nodes, disk_info, file_storage_dir, file_driver, base_index,
9045     feedback_fn, full_disk_params, _req_file_storage=opcodes.RequireFileStorage,
9046     _req_shr_file_storage=opcodes.RequireSharedFileStorage):
9047   """Generate the entire disk layout for a given template type.
9048
9049   """
9050   #TODO: compute space requirements
9051
9052   vgname = lu.cfg.GetVGName()
9053   disk_count = len(disk_info)
9054   disks = []
9055
9056   if template_name == constants.DT_DISKLESS:
9057     pass
9058   elif template_name == constants.DT_DRBD8:
9059     if len(secondary_nodes) != 1:
9060       raise errors.ProgrammerError("Wrong template configuration")
9061     remote_node = secondary_nodes[0]
9062     minors = lu.cfg.AllocateDRBDMinor(
9063       [primary_node, remote_node] * len(disk_info), instance_name)
9064
9065     (drbd_params, _, _) = objects.Disk.ComputeLDParams(template_name,
9066                                                        full_disk_params)
9067     drbd_default_metavg = drbd_params[constants.LDP_DEFAULT_METAVG]
9068
9069     names = []
9070     for lv_prefix in _GenerateUniqueNames(lu, [".disk%d" % (base_index + i)
9071                                                for i in range(disk_count)]):
9072       names.append(lv_prefix + "_data")
9073       names.append(lv_prefix + "_meta")
9074     for idx, disk in enumerate(disk_info):
9075       disk_index = idx + base_index
9076       data_vg = disk.get(constants.IDISK_VG, vgname)
9077       meta_vg = disk.get(constants.IDISK_METAVG, drbd_default_metavg)
9078       disk_dev = _GenerateDRBD8Branch(lu, primary_node, remote_node,
9079                                       disk[constants.IDISK_SIZE],
9080                                       [data_vg, meta_vg],
9081                                       names[idx * 2:idx * 2 + 2],
9082                                       "disk/%d" % disk_index,
9083                                       minors[idx * 2], minors[idx * 2 + 1])
9084       disk_dev.mode = disk[constants.IDISK_MODE]
9085       disks.append(disk_dev)
9086   else:
9087     if secondary_nodes:
9088       raise errors.ProgrammerError("Wrong template configuration")
9089
9090     if template_name == constants.DT_FILE:
9091       _req_file_storage()
9092     elif template_name == constants.DT_SHARED_FILE:
9093       _req_shr_file_storage()
9094
9095     name_prefix = _DISK_TEMPLATE_NAME_PREFIX.get(template_name, None)
9096     if name_prefix is None:
9097       names = None
9098     else:
9099       names = _GenerateUniqueNames(lu, ["%s.disk%s" %
9100                                         (name_prefix, base_index + i)
9101                                         for i in range(disk_count)])
9102
9103     if template_name == constants.DT_PLAIN:
9104       def logical_id_fn(idx, _, disk):
9105         vg = disk.get(constants.IDISK_VG, vgname)
9106         return (vg, names[idx])
9107     elif template_name in (constants.DT_FILE, constants.DT_SHARED_FILE):
9108       logical_id_fn = \
9109         lambda _, disk_index, disk: (file_driver,
9110                                      "%s/disk%d" % (file_storage_dir,
9111                                                     disk_index))
9112     elif template_name == constants.DT_BLOCK:
9113       logical_id_fn = \
9114         lambda idx, disk_index, disk: (constants.BLOCKDEV_DRIVER_MANUAL,
9115                                        disk[constants.IDISK_ADOPT])
9116     elif template_name == constants.DT_RBD:
9117       logical_id_fn = lambda idx, _, disk: ("rbd", names[idx])
9118     elif template_name == constants.DT_EXT:
9119       def logical_id_fn(idx, _, disk):
9120         provider = disk.get(constants.IDISK_PROVIDER, None)
9121         if provider is None:
9122           raise errors.ProgrammerError("Disk template is %s, but '%s' is"
9123                                        " not found", constants.DT_EXT,
9124                                        constants.IDISK_PROVIDER)
9125         return (provider, names[idx])
9126     else:
9127       raise errors.ProgrammerError("Unknown disk template '%s'" % template_name)
9128
9129     dev_type = _DISK_TEMPLATE_DEVICE_TYPE[template_name]
9130
9131     for idx, disk in enumerate(disk_info):
9132       params={}
9133       # Only for the Ext template add disk_info to params
9134       if template_name == constants.DT_EXT:
9135         params[constants.IDISK_PROVIDER] = disk[constants.IDISK_PROVIDER]
9136         for key in disk:
9137           if key not in constants.IDISK_PARAMS:
9138             params[key] = disk[key]
9139       disk_index = idx + base_index
9140       size = disk[constants.IDISK_SIZE]
9141       feedback_fn("* disk %s, size %s" %
9142                   (disk_index, utils.FormatUnit(size, "h")))
9143
9144       disk_idx, pci = _GetPCIInfo(lu, 'disks')
9145
9146       disks.append(objects.Disk(dev_type=dev_type, size=size,
9147                                 logical_id=logical_id_fn(idx, disk_index, disk),
9148                                 iv_name="disk/%d" % disk_index,
9149                                 mode=disk[constants.IDISK_MODE],
9150                                 params=params, idx=disk_idx, pci=pci))
9151
9152   return disks
9153
9154
9155 def _GetInstanceInfoText(instance):
9156   """Compute that text that should be added to the disk's metadata.
9157
9158   """
9159   return "originstname+%s" % instance.name
9160
9161
9162 def _CalcEta(time_taken, written, total_size):
9163   """Calculates the ETA based on size written and total size.
9164
9165   @param time_taken: The time taken so far
9166   @param written: amount written so far
9167   @param total_size: The total size of data to be written
9168   @return: The remaining time in seconds
9169
9170   """
9171   avg_time = time_taken / float(written)
9172   return (total_size - written) * avg_time
9173
9174
9175 def _WipeDisks(lu, instance):
9176   """Wipes instance disks.
9177
9178   @type lu: L{LogicalUnit}
9179   @param lu: the logical unit on whose behalf we execute
9180   @type instance: L{objects.Instance}
9181   @param instance: the instance whose disks we should create
9182   @return: the success of the wipe
9183
9184   """
9185   node = instance.primary_node
9186
9187   for device in instance.disks:
9188     lu.cfg.SetDiskID(device, node)
9189
9190   logging.info("Pause sync of instance %s disks", instance.name)
9191   result = lu.rpc.call_blockdev_pause_resume_sync(node,
9192                                                   (instance.disks, instance),
9193                                                   True)
9194   result.Raise("Failed RPC to node %s for pausing the disk syncing" % node)
9195
9196   for idx, success in enumerate(result.payload):
9197     if not success:
9198       logging.warn("pause-sync of instance %s for disks %d failed",
9199                    instance.name, idx)
9200
9201   try:
9202     for idx, device in enumerate(instance.disks):
9203       # The wipe size is MIN_WIPE_CHUNK_PERCENT % of the instance disk but
9204       # MAX_WIPE_CHUNK at max
9205       wipe_chunk_size = min(constants.MAX_WIPE_CHUNK, device.size / 100.0 *
9206                             constants.MIN_WIPE_CHUNK_PERCENT)
9207       # we _must_ make this an int, otherwise rounding errors will
9208       # occur
9209       wipe_chunk_size = int(wipe_chunk_size)
9210
9211       lu.LogInfo("* Wiping disk %d", idx)
9212       logging.info("Wiping disk %d for instance %s, node %s using"
9213                    " chunk size %s", idx, instance.name, node, wipe_chunk_size)
9214
9215       offset = 0
9216       size = device.size
9217       last_output = 0
9218       start_time = time.time()
9219
9220       while offset < size:
9221         wipe_size = min(wipe_chunk_size, size - offset)
9222         logging.debug("Wiping disk %d, offset %s, chunk %s",
9223                       idx, offset, wipe_size)
9224         result = lu.rpc.call_blockdev_wipe(node, (device, instance), offset,
9225                                            wipe_size)
9226         result.Raise("Could not wipe disk %d at offset %d for size %d" %
9227                      (idx, offset, wipe_size))
9228         now = time.time()
9229         offset += wipe_size
9230         if now - last_output >= 60:
9231           eta = _CalcEta(now - start_time, offset, size)
9232           lu.LogInfo(" - done: %.1f%% ETA: %s" %
9233                      (offset / float(size) * 100, utils.FormatSeconds(eta)))
9234           last_output = now
9235   finally:
9236     logging.info("Resume sync of instance %s disks", instance.name)
9237
9238     result = lu.rpc.call_blockdev_pause_resume_sync(node,
9239                                                     (instance.disks, instance),
9240                                                     False)
9241
9242     if result.fail_msg:
9243       lu.LogWarning("RPC call to %s for resuming disk syncing failed,"
9244                     " please have a look at the status and troubleshoot"
9245                     " the issue: %s", node, result.fail_msg)
9246     else:
9247       for idx, success in enumerate(result.payload):
9248         if not success:
9249           lu.LogWarning("Resume sync of disk %d failed, please have a"
9250                         " look at the status and troubleshoot the issue", idx)
9251           logging.warn("resume-sync of instance %s for disks %d failed",
9252                        instance.name, idx)
9253
9254
9255 def _CreateDisks(lu, instance, to_skip=None, target_node=None):
9256   """Create all disks for an instance.
9257
9258   This abstracts away some work from AddInstance.
9259
9260   @type lu: L{LogicalUnit}
9261   @param lu: the logical unit on whose behalf we execute
9262   @type instance: L{objects.Instance}
9263   @param instance: the instance whose disks we should create
9264   @type to_skip: list
9265   @param to_skip: list of indices to skip
9266   @type target_node: string
9267   @param target_node: if passed, overrides the target node for creation
9268   @rtype: boolean
9269   @return: the success of the creation
9270
9271   """
9272   info = _GetInstanceInfoText(instance)
9273   if target_node is None:
9274     pnode = instance.primary_node
9275     all_nodes = instance.all_nodes
9276   else:
9277     pnode = target_node
9278     all_nodes = [pnode]
9279
9280   if instance.disk_template in constants.DTS_FILEBASED:
9281     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
9282     result = lu.rpc.call_file_storage_dir_create(pnode, file_storage_dir)
9283
9284     result.Raise("Failed to create directory '%s' on"
9285                  " node %s" % (file_storage_dir, pnode))
9286
9287   # Note: this needs to be kept in sync with adding of disks in
9288   # LUInstanceSetParams
9289   for idx, device in enumerate(instance.disks):
9290     if to_skip and idx in to_skip:
9291       continue
9292     logging.info("Creating disk %s for instance '%s'", idx, instance.name)
9293     #HARDCODE
9294     for node in all_nodes:
9295       f_create = node == pnode
9296       _CreateBlockDev(lu, node, instance, device, f_create, info, f_create)
9297
9298
9299 def _RemoveDisks(lu, instance, target_node=None, ignore_failures=False):
9300   """Remove all disks for an instance.
9301
9302   This abstracts away some work from `AddInstance()` and
9303   `RemoveInstance()`. Note that in case some of the devices couldn't
9304   be removed, the removal will continue with the other ones (compare
9305   with `_CreateDisks()`).
9306
9307   @type lu: L{LogicalUnit}
9308   @param lu: the logical unit on whose behalf we execute
9309   @type instance: L{objects.Instance}
9310   @param instance: the instance whose disks we should remove
9311   @type target_node: string
9312   @param target_node: used to override the node on which to remove the disks
9313   @rtype: boolean
9314   @return: the success of the removal
9315
9316   """
9317   logging.info("Removing block devices for instance %s", instance.name)
9318
9319   all_result = True
9320   ports_to_release = set()
9321   anno_disks = _AnnotateDiskParams(instance, instance.disks, lu.cfg)
9322   for (idx, device) in enumerate(anno_disks):
9323     if target_node:
9324       edata = [(target_node, device)]
9325     else:
9326       edata = device.ComputeNodeTree(instance.primary_node)
9327     for node, disk in edata:
9328       lu.cfg.SetDiskID(disk, node)
9329       result = lu.rpc.call_blockdev_remove(node, disk)
9330       if result.fail_msg:
9331         lu.LogWarning("Could not remove disk %s on node %s,"
9332                       " continuing anyway: %s", idx, node, result.fail_msg)
9333         if not (result.offline and node != instance.primary_node):
9334           all_result = False
9335
9336     # if this is a DRBD disk, return its port to the pool
9337     if device.dev_type in constants.LDS_DRBD:
9338       ports_to_release.add(device.logical_id[2])
9339
9340   if all_result or ignore_failures:
9341     for port in ports_to_release:
9342       lu.cfg.AddTcpUdpPort(port)
9343
9344   if instance.disk_template in constants.DTS_FILEBASED:
9345     file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
9346     if target_node:
9347       tgt = target_node
9348     else:
9349       tgt = instance.primary_node
9350     result = lu.rpc.call_file_storage_dir_remove(tgt, file_storage_dir)
9351     if result.fail_msg:
9352       lu.LogWarning("Could not remove directory '%s' on node %s: %s",
9353                     file_storage_dir, instance.primary_node, result.fail_msg)
9354       all_result = False
9355
9356   return all_result
9357
9358
9359 def _ComputeDiskSizePerVG(disk_template, disks):
9360   """Compute disk size requirements in the volume group
9361
9362   """
9363   def _compute(disks, payload):
9364     """Universal algorithm.
9365
9366     """
9367     vgs = {}
9368     for disk in disks:
9369       vgs[disk[constants.IDISK_VG]] = \
9370         vgs.get(constants.IDISK_VG, 0) + disk[constants.IDISK_SIZE] + payload
9371
9372     return vgs
9373
9374   # Required free disk space as a function of disk and swap space
9375   req_size_dict = {
9376     constants.DT_DISKLESS: {},
9377     constants.DT_PLAIN: _compute(disks, 0),
9378     # 128 MB are added for drbd metadata for each disk
9379     constants.DT_DRBD8: _compute(disks, DRBD_META_SIZE),
9380     constants.DT_FILE: {},
9381     constants.DT_SHARED_FILE: {},
9382   }
9383
9384   if disk_template not in req_size_dict:
9385     raise errors.ProgrammerError("Disk template '%s' size requirement"
9386                                  " is unknown" % disk_template)
9387
9388   return req_size_dict[disk_template]
9389
9390
9391 def _ComputeDiskSize(disk_template, disks):
9392   """Compute disk size requirements according to disk template
9393
9394   """
9395   # Required free disk space as a function of disk and swap space
9396   req_size_dict = {
9397     constants.DT_DISKLESS: None,
9398     constants.DT_PLAIN: sum(d[constants.IDISK_SIZE] for d in disks),
9399     # 128 MB are added for drbd metadata for each disk
9400     constants.DT_DRBD8:
9401       sum(d[constants.IDISK_SIZE] + DRBD_META_SIZE for d in disks),
9402     constants.DT_FILE: sum(d[constants.IDISK_SIZE] for d in disks),
9403     constants.DT_SHARED_FILE: sum(d[constants.IDISK_SIZE] for d in disks),
9404     constants.DT_BLOCK: 0,
9405     constants.DT_RBD: sum(d[constants.IDISK_SIZE] for d in disks),
9406     constants.DT_EXT: sum(d[constants.IDISK_SIZE] for d in disks),
9407   }
9408
9409   if disk_template not in req_size_dict:
9410     raise errors.ProgrammerError("Disk template '%s' size requirement"
9411                                  " is unknown" % disk_template)
9412
9413   return req_size_dict[disk_template]
9414
9415
9416 def _FilterVmNodes(lu, nodenames):
9417   """Filters out non-vm_capable nodes from a list.
9418
9419   @type lu: L{LogicalUnit}
9420   @param lu: the logical unit for which we check
9421   @type nodenames: list
9422   @param nodenames: the list of nodes on which we should check
9423   @rtype: list
9424   @return: the list of vm-capable nodes
9425
9426   """
9427   vm_nodes = frozenset(lu.cfg.GetNonVmCapableNodeList())
9428   return [name for name in nodenames if name not in vm_nodes]
9429
9430
9431 def _CheckHVParams(lu, nodenames, hvname, hvparams):
9432   """Hypervisor parameter validation.
9433
9434   This function abstract the hypervisor parameter validation to be
9435   used in both instance create and instance modify.
9436
9437   @type lu: L{LogicalUnit}
9438   @param lu: the logical unit for which we check
9439   @type nodenames: list
9440   @param nodenames: the list of nodes on which we should check
9441   @type hvname: string
9442   @param hvname: the name of the hypervisor we should use
9443   @type hvparams: dict
9444   @param hvparams: the parameters which we need to check
9445   @raise errors.OpPrereqError: if the parameters are not valid
9446
9447   """
9448   nodenames = _FilterVmNodes(lu, nodenames)
9449
9450   cluster = lu.cfg.GetClusterInfo()
9451   hvfull = objects.FillDict(cluster.hvparams.get(hvname, {}), hvparams)
9452
9453   hvinfo = lu.rpc.call_hypervisor_validate_params(nodenames, hvname, hvfull)
9454   for node in nodenames:
9455     info = hvinfo[node]
9456     if info.offline:
9457       continue
9458     info.Raise("Hypervisor parameter validation failed on node %s" % node)
9459
9460
9461 def _CheckOSParams(lu, required, nodenames, osname, osparams):
9462   """OS parameters validation.
9463
9464   @type lu: L{LogicalUnit}
9465   @param lu: the logical unit for which we check
9466   @type required: boolean
9467   @param required: whether the validation should fail if the OS is not
9468       found
9469   @type nodenames: list
9470   @param nodenames: the list of nodes on which we should check
9471   @type osname: string
9472   @param osname: the name of the hypervisor we should use
9473   @type osparams: dict
9474   @param osparams: the parameters which we need to check
9475   @raise errors.OpPrereqError: if the parameters are not valid
9476
9477   """
9478   nodenames = _FilterVmNodes(lu, nodenames)
9479   result = lu.rpc.call_os_validate(nodenames, required, osname,
9480                                    [constants.OS_VALIDATE_PARAMETERS],
9481                                    osparams)
9482   for node, nres in result.items():
9483     # we don't check for offline cases since this should be run only
9484     # against the master node and/or an instance's nodes
9485     nres.Raise("OS Parameters validation failed on node %s" % node)
9486     if not nres.payload:
9487       lu.LogInfo("OS %s not found on node %s, validation skipped",
9488                  osname, node)
9489
9490
9491 class LUInstanceCreate(LogicalUnit):
9492   """Create an instance.
9493
9494   """
9495   HPATH = "instance-add"
9496   HTYPE = constants.HTYPE_INSTANCE
9497   REQ_BGL = False
9498
9499   def CheckArguments(self):
9500     """Check arguments.
9501
9502     """
9503     # do not require name_check to ease forward/backward compatibility
9504     # for tools
9505     if self.op.no_install and self.op.start:
9506       self.LogInfo("No-installation mode selected, disabling startup")
9507       self.op.start = False
9508     # validate/normalize the instance name
9509     self.op.instance_name = \
9510       netutils.Hostname.GetNormalizedName(self.op.instance_name)
9511
9512     if self.op.ip_check and not self.op.name_check:
9513       # TODO: make the ip check more flexible and not depend on the name check
9514       raise errors.OpPrereqError("Cannot do IP address check without a name"
9515                                  " check", errors.ECODE_INVAL)
9516
9517     # check nics' parameter names
9518     for nic in self.op.nics:
9519       utils.ForceDictType(nic, constants.INIC_PARAMS_TYPES)
9520
9521     # check disks. parameter names and consistent adopt/no-adopt strategy
9522     has_adopt = has_no_adopt = False
9523     for disk in self.op.disks:
9524       if self.op.disk_template != constants.DT_EXT:
9525         utils.ForceDictType(disk, constants.IDISK_PARAMS_TYPES)
9526       if constants.IDISK_ADOPT in disk:
9527         has_adopt = True
9528       else:
9529         has_no_adopt = True
9530     if has_adopt and has_no_adopt:
9531       raise errors.OpPrereqError("Either all disks are adopted or none is",
9532                                  errors.ECODE_INVAL)
9533     if has_adopt:
9534       if self.op.disk_template not in constants.DTS_MAY_ADOPT:
9535         raise errors.OpPrereqError("Disk adoption is not supported for the"
9536                                    " '%s' disk template" %
9537                                    self.op.disk_template,
9538                                    errors.ECODE_INVAL)
9539       if self.op.iallocator is not None:
9540         raise errors.OpPrereqError("Disk adoption not allowed with an"
9541                                    " iallocator script", errors.ECODE_INVAL)
9542       if self.op.mode == constants.INSTANCE_IMPORT:
9543         raise errors.OpPrereqError("Disk adoption not allowed for"
9544                                    " instance import", errors.ECODE_INVAL)
9545     else:
9546       if self.op.disk_template in constants.DTS_MUST_ADOPT:
9547         raise errors.OpPrereqError("Disk template %s requires disk adoption,"
9548                                    " but no 'adopt' parameter given" %
9549                                    self.op.disk_template,
9550                                    errors.ECODE_INVAL)
9551
9552     self.adopt_disks = has_adopt
9553
9554     # instance name verification
9555     if self.op.name_check:
9556       self.hostname1 = _CheckHostnameSane(self, self.op.instance_name)
9557       self.op.instance_name = self.hostname1.name
9558       # used in CheckPrereq for ip ping check
9559       self.check_ip = self.hostname1.ip
9560     else:
9561       self.check_ip = None
9562
9563     # file storage checks
9564     if (self.op.file_driver and
9565         not self.op.file_driver in constants.FILE_DRIVER):
9566       raise errors.OpPrereqError("Invalid file driver name '%s'" %
9567                                  self.op.file_driver, errors.ECODE_INVAL)
9568
9569     if self.op.disk_template == constants.DT_FILE:
9570       opcodes.RequireFileStorage()
9571     elif self.op.disk_template == constants.DT_SHARED_FILE:
9572       opcodes.RequireSharedFileStorage()
9573
9574     ### Node/iallocator related checks
9575     _CheckIAllocatorOrNode(self, "iallocator", "pnode")
9576
9577     if self.op.pnode is not None:
9578       if self.op.disk_template in constants.DTS_INT_MIRROR:
9579         if self.op.snode is None:
9580           raise errors.OpPrereqError("The networked disk templates need"
9581                                      " a mirror node", errors.ECODE_INVAL)
9582       elif self.op.snode:
9583         self.LogWarning("Secondary node will be ignored on non-mirrored disk"
9584                         " template")
9585         self.op.snode = None
9586
9587     self._cds = _GetClusterDomainSecret()
9588
9589     if self.op.mode == constants.INSTANCE_IMPORT:
9590       # On import force_variant must be True, because if we forced it at
9591       # initial install, our only chance when importing it back is that it
9592       # works again!
9593       self.op.force_variant = True
9594
9595       if self.op.no_install:
9596         self.LogInfo("No-installation mode has no effect during import")
9597
9598     elif self.op.mode == constants.INSTANCE_CREATE:
9599       if self.op.os_type is None:
9600         raise errors.OpPrereqError("No guest OS specified",
9601                                    errors.ECODE_INVAL)
9602       if self.op.os_type in self.cfg.GetClusterInfo().blacklisted_os:
9603         raise errors.OpPrereqError("Guest OS '%s' is not allowed for"
9604                                    " installation" % self.op.os_type,
9605                                    errors.ECODE_STATE)
9606       if self.op.disk_template is None:
9607         raise errors.OpPrereqError("No disk template specified",
9608                                    errors.ECODE_INVAL)
9609
9610     elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
9611       # Check handshake to ensure both clusters have the same domain secret
9612       src_handshake = self.op.source_handshake
9613       if not src_handshake:
9614         raise errors.OpPrereqError("Missing source handshake",
9615                                    errors.ECODE_INVAL)
9616
9617       errmsg = masterd.instance.CheckRemoteExportHandshake(self._cds,
9618                                                            src_handshake)
9619       if errmsg:
9620         raise errors.OpPrereqError("Invalid handshake: %s" % errmsg,
9621                                    errors.ECODE_INVAL)
9622
9623       # Load and check source CA
9624       self.source_x509_ca_pem = self.op.source_x509_ca
9625       if not self.source_x509_ca_pem:
9626         raise errors.OpPrereqError("Missing source X509 CA",
9627                                    errors.ECODE_INVAL)
9628
9629       try:
9630         (cert, _) = utils.LoadSignedX509Certificate(self.source_x509_ca_pem,
9631                                                     self._cds)
9632       except OpenSSL.crypto.Error, err:
9633         raise errors.OpPrereqError("Unable to load source X509 CA (%s)" %
9634                                    (err, ), errors.ECODE_INVAL)
9635
9636       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
9637       if errcode is not None:
9638         raise errors.OpPrereqError("Invalid source X509 CA (%s)" % (msg, ),
9639                                    errors.ECODE_INVAL)
9640
9641       self.source_x509_ca = cert
9642
9643       src_instance_name = self.op.source_instance_name
9644       if not src_instance_name:
9645         raise errors.OpPrereqError("Missing source instance name",
9646                                    errors.ECODE_INVAL)
9647
9648       self.source_instance_name = \
9649           netutils.GetHostname(name=src_instance_name).name
9650
9651     else:
9652       raise errors.OpPrereqError("Invalid instance creation mode %r" %
9653                                  self.op.mode, errors.ECODE_INVAL)
9654
9655   def ExpandNames(self):
9656     """ExpandNames for CreateInstance.
9657
9658     Figure out the right locks for instance creation.
9659
9660     """
9661     self.needed_locks = {}
9662
9663     instance_name = self.op.instance_name
9664     # this is just a preventive check, but someone might still add this
9665     # instance in the meantime, and creation will fail at lock-add time
9666     if instance_name in self.cfg.GetInstanceList():
9667       raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
9668                                  instance_name, errors.ECODE_EXISTS)
9669
9670     self.add_locks[locking.LEVEL_INSTANCE] = instance_name
9671
9672     if self.op.iallocator:
9673       # TODO: Find a solution to not lock all nodes in the cluster, e.g. by
9674       # specifying a group on instance creation and then selecting nodes from
9675       # that group
9676       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9677       self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
9678     else:
9679       self.op.pnode = _ExpandNodeName(self.cfg, self.op.pnode)
9680       nodelist = [self.op.pnode]
9681       if self.op.snode is not None:
9682         self.op.snode = _ExpandNodeName(self.cfg, self.op.snode)
9683         nodelist.append(self.op.snode)
9684       self.needed_locks[locking.LEVEL_NODE] = nodelist
9685       # Lock resources of instance's primary and secondary nodes (copy to
9686       # prevent accidential modification)
9687       self.needed_locks[locking.LEVEL_NODE_RES] = list(nodelist)
9688
9689     # in case of import lock the source node too
9690     if self.op.mode == constants.INSTANCE_IMPORT:
9691       src_node = self.op.src_node
9692       src_path = self.op.src_path
9693
9694       if src_path is None:
9695         self.op.src_path = src_path = self.op.instance_name
9696
9697       if src_node is None:
9698         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
9699         self.op.src_node = None
9700         if os.path.isabs(src_path):
9701           raise errors.OpPrereqError("Importing an instance from a path"
9702                                      " requires a source node option",
9703                                      errors.ECODE_INVAL)
9704       else:
9705         self.op.src_node = src_node = _ExpandNodeName(self.cfg, src_node)
9706         if self.needed_locks[locking.LEVEL_NODE] is not locking.ALL_SET:
9707           self.needed_locks[locking.LEVEL_NODE].append(src_node)
9708         if not os.path.isabs(src_path):
9709           self.op.src_path = src_path = \
9710             utils.PathJoin(constants.EXPORT_DIR, src_path)
9711
9712   def _RunAllocator(self):
9713     """Run the allocator based on input opcode.
9714
9715     """
9716     #TODO Export network to iallocator so that it chooses a pnode
9717     #     in a nodegroup that has the desired network connected to
9718     nics = [n.ToDict() for n in self.nics]
9719     ial = IAllocator(self.cfg, self.rpc,
9720                      mode=constants.IALLOCATOR_MODE_ALLOC,
9721                      name=self.op.instance_name,
9722                      disk_template=self.op.disk_template,
9723                      tags=self.op.tags,
9724                      os=self.op.os_type,
9725                      vcpus=self.be_full[constants.BE_VCPUS],
9726                      memory=self.be_full[constants.BE_MAXMEM],
9727                      spindle_use=self.be_full[constants.BE_SPINDLE_USE],
9728                      disks=self.disks,
9729                      nics=nics,
9730                      hypervisor=self.op.hypervisor,
9731                      )
9732
9733     ial.Run(self.op.iallocator)
9734
9735     if not ial.success:
9736       raise errors.OpPrereqError("Can't compute nodes using"
9737                                  " iallocator '%s': %s" %
9738                                  (self.op.iallocator, ial.info),
9739                                  errors.ECODE_NORES)
9740     if len(ial.result) != ial.required_nodes:
9741       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
9742                                  " of nodes (%s), required %s" %
9743                                  (self.op.iallocator, len(ial.result),
9744                                   ial.required_nodes), errors.ECODE_FAULT)
9745     self.op.pnode = ial.result[0]
9746     self.LogInfo("Selected nodes for instance %s via iallocator %s: %s",
9747                  self.op.instance_name, self.op.iallocator,
9748                  utils.CommaJoin(ial.result))
9749     if ial.required_nodes == 2:
9750       self.op.snode = ial.result[1]
9751
9752   def BuildHooksEnv(self):
9753     """Build hooks env.
9754
9755     This runs on master, primary and secondary nodes of the instance.
9756
9757     """
9758     env = {
9759       "ADD_MODE": self.op.mode,
9760       }
9761     if self.op.mode == constants.INSTANCE_IMPORT:
9762       env["SRC_NODE"] = self.op.src_node
9763       env["SRC_PATH"] = self.op.src_path
9764       env["SRC_IMAGES"] = self.src_images
9765
9766     env.update(_BuildInstanceHookEnv(
9767       name=self.op.instance_name,
9768       primary_node=self.op.pnode,
9769       secondary_nodes=self.secondaries,
9770       status=self.op.start,
9771       os_type=self.op.os_type,
9772       minmem=self.be_full[constants.BE_MINMEM],
9773       maxmem=self.be_full[constants.BE_MAXMEM],
9774       vcpus=self.be_full[constants.BE_VCPUS],
9775       nics=_NICListToTuple(self, self.nics),
9776       disk_template=self.op.disk_template,
9777       disks=[(d[constants.IDISK_SIZE], d[constants.IDISK_MODE])
9778              for d in self.disks],
9779       bep=self.be_full,
9780       hvp=self.hv_full,
9781       hypervisor_name=self.op.hypervisor,
9782       tags=self.op.tags,
9783       serial_no=1,
9784     ))
9785
9786     return env
9787
9788   def BuildHooksNodes(self):
9789     """Build hooks nodes.
9790
9791     """
9792     nl = [self.cfg.GetMasterNode(), self.op.pnode] + self.secondaries
9793     return nl, nl
9794
9795   def _ReadExportInfo(self):
9796     """Reads the export information from disk.
9797
9798     It will override the opcode source node and path with the actual
9799     information, if these two were not specified before.
9800
9801     @return: the export information
9802
9803     """
9804     assert self.op.mode == constants.INSTANCE_IMPORT
9805
9806     src_node = self.op.src_node
9807     src_path = self.op.src_path
9808
9809     if src_node is None:
9810       locked_nodes = self.owned_locks(locking.LEVEL_NODE)
9811       exp_list = self.rpc.call_export_list(locked_nodes)
9812       found = False
9813       for node in exp_list:
9814         if exp_list[node].fail_msg:
9815           continue
9816         if src_path in exp_list[node].payload:
9817           found = True
9818           self.op.src_node = src_node = node
9819           self.op.src_path = src_path = utils.PathJoin(constants.EXPORT_DIR,
9820                                                        src_path)
9821           break
9822       if not found:
9823         raise errors.OpPrereqError("No export found for relative path %s" %
9824                                     src_path, errors.ECODE_INVAL)
9825
9826     _CheckNodeOnline(self, src_node)
9827     result = self.rpc.call_export_info(src_node, src_path)
9828     result.Raise("No export or invalid export found in dir %s" % src_path)
9829
9830     export_info = objects.SerializableConfigParser.Loads(str(result.payload))
9831     if not export_info.has_section(constants.INISECT_EXP):
9832       raise errors.ProgrammerError("Corrupted export config",
9833                                    errors.ECODE_ENVIRON)
9834
9835     ei_version = export_info.get(constants.INISECT_EXP, "version")
9836     if (int(ei_version) != constants.EXPORT_VERSION):
9837       raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
9838                                  (ei_version, constants.EXPORT_VERSION),
9839                                  errors.ECODE_ENVIRON)
9840     return export_info
9841
9842   def _ReadExportParams(self, einfo):
9843     """Use export parameters as defaults.
9844
9845     In case the opcode doesn't specify (as in override) some instance
9846     parameters, then try to use them from the export information, if
9847     that declares them.
9848
9849     """
9850     self.op.os_type = einfo.get(constants.INISECT_EXP, "os")
9851
9852     if self.op.disk_template is None:
9853       if einfo.has_option(constants.INISECT_INS, "disk_template"):
9854         self.op.disk_template = einfo.get(constants.INISECT_INS,
9855                                           "disk_template")
9856         if self.op.disk_template not in constants.DISK_TEMPLATES:
9857           raise errors.OpPrereqError("Disk template specified in configuration"
9858                                      " file is not one of the allowed values:"
9859                                      " %s" % " ".join(constants.DISK_TEMPLATES))
9860       else:
9861         raise errors.OpPrereqError("No disk template specified and the export"
9862                                    " is missing the disk_template information",
9863                                    errors.ECODE_INVAL)
9864
9865     if not self.op.disks:
9866       disks = []
9867       # TODO: import the disk iv_name too
9868       for idx in range(constants.MAX_DISKS):
9869         if einfo.has_option(constants.INISECT_INS, "disk%d_size" % idx):
9870           disk_sz = einfo.getint(constants.INISECT_INS, "disk%d_size" % idx)
9871           disks.append({constants.IDISK_SIZE: disk_sz})
9872       self.op.disks = disks
9873       if not disks and self.op.disk_template != constants.DT_DISKLESS:
9874         raise errors.OpPrereqError("No disk info specified and the export"
9875                                    " is missing the disk information",
9876                                    errors.ECODE_INVAL)
9877
9878     if not self.op.nics:
9879       nics = []
9880       for idx in range(constants.MAX_NICS):
9881         if einfo.has_option(constants.INISECT_INS, "nic%d_mac" % idx):
9882           ndict = {}
9883           for name in list(constants.NICS_PARAMETERS) + ["ip", "mac"]:
9884             v = einfo.get(constants.INISECT_INS, "nic%d_%s" % (idx, name))
9885             ndict[name] = v
9886           nics.append(ndict)
9887         else:
9888           break
9889       self.op.nics = nics
9890
9891     if not self.op.tags and einfo.has_option(constants.INISECT_INS, "tags"):
9892       self.op.tags = einfo.get(constants.INISECT_INS, "tags").split()
9893
9894     if (self.op.hypervisor is None and
9895         einfo.has_option(constants.INISECT_INS, "hypervisor")):
9896       self.op.hypervisor = einfo.get(constants.INISECT_INS, "hypervisor")
9897
9898     if einfo.has_section(constants.INISECT_HYP):
9899       # use the export parameters but do not override the ones
9900       # specified by the user
9901       for name, value in einfo.items(constants.INISECT_HYP):
9902         if name not in self.op.hvparams:
9903           self.op.hvparams[name] = value
9904
9905     if einfo.has_section(constants.INISECT_BEP):
9906       # use the parameters, without overriding
9907       for name, value in einfo.items(constants.INISECT_BEP):
9908         if name not in self.op.beparams:
9909           self.op.beparams[name] = value
9910         # Compatibility for the old "memory" be param
9911         if name == constants.BE_MEMORY:
9912           if constants.BE_MAXMEM not in self.op.beparams:
9913             self.op.beparams[constants.BE_MAXMEM] = value
9914           if constants.BE_MINMEM not in self.op.beparams:
9915             self.op.beparams[constants.BE_MINMEM] = value
9916     else:
9917       # try to read the parameters old style, from the main section
9918       for name in constants.BES_PARAMETERS:
9919         if (name not in self.op.beparams and
9920             einfo.has_option(constants.INISECT_INS, name)):
9921           self.op.beparams[name] = einfo.get(constants.INISECT_INS, name)
9922
9923     if einfo.has_section(constants.INISECT_OSP):
9924       # use the parameters, without overriding
9925       for name, value in einfo.items(constants.INISECT_OSP):
9926         if name not in self.op.osparams:
9927           self.op.osparams[name] = value
9928
9929   def _RevertToDefaults(self, cluster):
9930     """Revert the instance parameters to the default values.
9931
9932     """
9933     # hvparams
9934     hv_defs = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type, {})
9935     for name in self.op.hvparams.keys():
9936       if name in hv_defs and hv_defs[name] == self.op.hvparams[name]:
9937         del self.op.hvparams[name]
9938     # beparams
9939     be_defs = cluster.SimpleFillBE({})
9940     for name in self.op.beparams.keys():
9941       if name in be_defs and be_defs[name] == self.op.beparams[name]:
9942         del self.op.beparams[name]
9943     # nic params
9944     nic_defs = cluster.SimpleFillNIC({})
9945     for nic in self.op.nics:
9946       for name in constants.NICS_PARAMETERS:
9947         if name in nic and name in nic_defs and nic[name] == nic_defs[name]:
9948           del nic[name]
9949     # osparams
9950     os_defs = cluster.SimpleFillOS(self.op.os_type, {})
9951     for name in self.op.osparams.keys():
9952       if name in os_defs and os_defs[name] == self.op.osparams[name]:
9953         del self.op.osparams[name]
9954
9955   def _CalculateFileStorageDir(self):
9956     """Calculate final instance file storage dir.
9957
9958     """
9959     # file storage dir calculation/check
9960     self.instance_file_storage_dir = None
9961     if self.op.disk_template in constants.DTS_FILEBASED:
9962       # build the full file storage dir path
9963       joinargs = []
9964
9965       if self.op.disk_template == constants.DT_SHARED_FILE:
9966         get_fsd_fn = self.cfg.GetSharedFileStorageDir
9967       else:
9968         get_fsd_fn = self.cfg.GetFileStorageDir
9969
9970       cfg_storagedir = get_fsd_fn()
9971       if not cfg_storagedir:
9972         raise errors.OpPrereqError("Cluster file storage dir not defined")
9973       joinargs.append(cfg_storagedir)
9974
9975       if self.op.file_storage_dir is not None:
9976         joinargs.append(self.op.file_storage_dir)
9977
9978       joinargs.append(self.op.instance_name)
9979
9980       # pylint: disable=W0142
9981       self.instance_file_storage_dir = utils.PathJoin(*joinargs)
9982
9983   def CheckPrereq(self): # pylint: disable=R0914
9984     """Check prerequisites.
9985
9986     """
9987     self._CalculateFileStorageDir()
9988
9989     if self.op.mode == constants.INSTANCE_IMPORT:
9990       export_info = self._ReadExportInfo()
9991       self._ReadExportParams(export_info)
9992       self._old_instance_name = export_info.get(constants.INISECT_INS, "name")
9993     else:
9994       self._old_instance_name = None
9995
9996     if (not self.cfg.GetVGName() and
9997         self.op.disk_template not in constants.DTS_NOT_LVM):
9998       raise errors.OpPrereqError("Cluster does not support lvm-based"
9999                                  " instances", errors.ECODE_STATE)
10000
10001     if (self.op.hypervisor is None or
10002         self.op.hypervisor == constants.VALUE_AUTO):
10003       self.op.hypervisor = self.cfg.GetHypervisorType()
10004
10005     cluster = self.cfg.GetClusterInfo()
10006     enabled_hvs = cluster.enabled_hypervisors
10007     if self.op.hypervisor not in enabled_hvs:
10008       raise errors.OpPrereqError("Selected hypervisor (%s) not enabled in the"
10009                                  " cluster (%s)" % (self.op.hypervisor,
10010                                   ",".join(enabled_hvs)),
10011                                  errors.ECODE_STATE)
10012
10013     # Check tag validity
10014     for tag in self.op.tags:
10015       objects.TaggableObject.ValidateTag(tag)
10016
10017     # check hypervisor parameter syntax (locally)
10018     utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
10019     filled_hvp = cluster.SimpleFillHV(self.op.hypervisor, self.op.os_type,
10020                                       self.op.hvparams)
10021     hv_type = hypervisor.GetHypervisor(self.op.hypervisor)
10022     hv_type.CheckParameterSyntax(filled_hvp)
10023     self.hv_full = filled_hvp
10024     # check that we don't specify global parameters on an instance
10025     _CheckGlobalHvParams(self.op.hvparams)
10026
10027     # fill and remember the beparams dict
10028     default_beparams = cluster.beparams[constants.PP_DEFAULT]
10029     for param, value in self.op.beparams.iteritems():
10030       if value == constants.VALUE_AUTO:
10031         self.op.beparams[param] = default_beparams[param]
10032     objects.UpgradeBeParams(self.op.beparams)
10033     utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
10034     self.be_full = cluster.SimpleFillBE(self.op.beparams)
10035
10036     # build os parameters
10037     self.os_full = cluster.SimpleFillOS(self.op.os_type, self.op.osparams)
10038
10039     # now that hvp/bep are in final format, let's reset to defaults,
10040     # if told to do so
10041     if self.op.identify_defaults:
10042       self._RevertToDefaults(cluster)
10043
10044     self.hotplug_info = None
10045     if self.op.hotplug:
10046       self.LogInfo("Enabling hotplug.")
10047       self.hotplug_info = objects.HotplugInfo(disks=0, nics=0,
10048                                               pci_pool=list(range(16,32)))
10049     # NIC buildup
10050     self.nics = []
10051     for idx, nic in enumerate(self.op.nics):
10052       nic_mode_req = nic.get(constants.INIC_MODE, None)
10053       nic_mode = nic_mode_req
10054       if nic_mode is None or nic_mode == constants.VALUE_AUTO:
10055         nic_mode = cluster.nicparams[constants.PP_DEFAULT][constants.NIC_MODE]
10056
10057       net = nic.get(constants.INIC_NETWORK, None)
10058       link = nic.get(constants.NIC_LINK, None)
10059       ip = nic.get(constants.INIC_IP, None)
10060
10061       if net is None or net.lower() == constants.VALUE_NONE:
10062         net = None
10063       else:
10064         if nic_mode_req is not None or link is not None:
10065           raise errors.OpPrereqError("If network is given, no mode or link"
10066                                      " is allowed to be passed",
10067                                      errors.ECODE_INVAL)
10068
10069       # ip validity checks
10070       if ip is None or ip.lower() == constants.VALUE_NONE:
10071         nic_ip = None
10072       elif ip.lower() == constants.VALUE_AUTO:
10073         if not self.op.name_check:
10074           raise errors.OpPrereqError("IP address set to auto but name checks"
10075                                      " have been skipped",
10076                                      errors.ECODE_INVAL)
10077         nic_ip = self.hostname1.ip
10078       else:
10079         # We defer pool operations until later, so that the iallocator has
10080         # filled in the instance's node(s) dimara
10081         if ip.lower() == constants.NIC_IP_POOL:
10082           if net is None:
10083             raise errors.OpPrereqError("if ip=pool, parameter network"
10084                                        " must be passed too",
10085                                        errors.ECODE_INVAL)
10086
10087         elif not netutils.IPAddress.IsValid(ip):
10088           raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
10089                                      errors.ECODE_INVAL)
10090
10091         nic_ip = ip
10092
10093       # TODO: check the ip address for uniqueness
10094       if nic_mode == constants.NIC_MODE_ROUTED and not nic_ip:
10095         raise errors.OpPrereqError("Routed nic mode requires an ip address",
10096                                    errors.ECODE_INVAL)
10097
10098       # MAC address verification
10099       mac = nic.get(constants.INIC_MAC, constants.VALUE_AUTO)
10100       if mac not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
10101         mac = utils.NormalizeAndValidateMac(mac)
10102
10103         try:
10104           self.cfg.ReserveMAC(mac, self.proc.GetECId())
10105         except errors.ReservationError:
10106           raise errors.OpPrereqError("MAC address %s already in use"
10107                                      " in cluster" % mac,
10108                                      errors.ECODE_NOTUNIQUE)
10109
10110       #  Build nic parameters
10111       nicparams = {}
10112       if nic_mode_req:
10113         nicparams[constants.NIC_MODE] = nic_mode
10114       if link:
10115         nicparams[constants.NIC_LINK] = link
10116
10117       check_params = cluster.SimpleFillNIC(nicparams)
10118       objects.NIC.CheckParameterSyntax(check_params)
10119       nic_idx, pci = _GetPCIInfo(self, 'nics')
10120       self.nics.append(objects.NIC(idx=nic_idx, pci=pci,
10121                                    mac=mac, ip=nic_ip, network=net,
10122                                    nicparams=check_params))
10123
10124     # disk checks/pre-build
10125     default_vg = self.cfg.GetVGName()
10126     self.disks = []
10127     for disk in self.op.disks:
10128       mode = disk.get(constants.IDISK_MODE, constants.DISK_RDWR)
10129       if mode not in constants.DISK_ACCESS_SET:
10130         raise errors.OpPrereqError("Invalid disk access mode '%s'" %
10131                                    mode, errors.ECODE_INVAL)
10132       size = disk.get(constants.IDISK_SIZE, None)
10133       if size is None:
10134         raise errors.OpPrereqError("Missing disk size", errors.ECODE_INVAL)
10135       try:
10136         size = int(size)
10137       except (TypeError, ValueError):
10138         raise errors.OpPrereqError("Invalid disk size '%s'" % size,
10139                                    errors.ECODE_INVAL)
10140
10141       ext_provider = disk.get(constants.IDISK_PROVIDER, None)
10142       if ext_provider and self.op.disk_template != constants.DT_EXT:
10143         raise errors.OpPrereqError("The '%s' option is only valid for the %s"
10144                                    " disk template, not %s" %
10145                                    (constants.IDISK_PROVIDER, constants.DT_EXT,
10146                                    self.op.disk_template), errors.ECODE_INVAL)
10147
10148       data_vg = disk.get(constants.IDISK_VG, default_vg)
10149       new_disk = {
10150         constants.IDISK_SIZE: size,
10151         constants.IDISK_MODE: mode,
10152         constants.IDISK_VG: data_vg,
10153         }
10154
10155       if constants.IDISK_METAVG in disk:
10156         new_disk[constants.IDISK_METAVG] = disk[constants.IDISK_METAVG]
10157       if constants.IDISK_ADOPT in disk:
10158         new_disk[constants.IDISK_ADOPT] = disk[constants.IDISK_ADOPT]
10159
10160       # For extstorage, demand the `provider' option and add any
10161       # additional parameters (ext-params) to the dict
10162       if self.op.disk_template == constants.DT_EXT:
10163         if ext_provider:
10164           new_disk[constants.IDISK_PROVIDER] = ext_provider
10165           for key in disk:
10166             if key not in constants.IDISK_PARAMS:
10167               new_disk[key] = disk[key]
10168         else:
10169           raise errors.OpPrereqError("Missing provider for template '%s'" %
10170                                      constants.DT_EXT, errors.ECODE_INVAL)
10171
10172       self.disks.append(new_disk)
10173
10174     if self.op.mode == constants.INSTANCE_IMPORT:
10175       disk_images = []
10176       for idx in range(len(self.disks)):
10177         option = "disk%d_dump" % idx
10178         if export_info.has_option(constants.INISECT_INS, option):
10179           # FIXME: are the old os-es, disk sizes, etc. useful?
10180           export_name = export_info.get(constants.INISECT_INS, option)
10181           image = utils.PathJoin(self.op.src_path, export_name)
10182           disk_images.append(image)
10183         else:
10184           disk_images.append(False)
10185
10186       self.src_images = disk_images
10187
10188       if self.op.instance_name == self._old_instance_name:
10189         for idx, nic in enumerate(self.nics):
10190           if nic.mac == constants.VALUE_AUTO:
10191             nic_mac_ini = "nic%d_mac" % idx
10192             nic.mac = export_info.get(constants.INISECT_INS, nic_mac_ini)
10193
10194     # ENDIF: self.op.mode == constants.INSTANCE_IMPORT
10195
10196     # ip ping checks (we use the same ip that was resolved in ExpandNames)
10197     if self.op.ip_check:
10198       if netutils.TcpPing(self.check_ip, constants.DEFAULT_NODED_PORT):
10199         raise errors.OpPrereqError("IP %s of instance %s already in use" %
10200                                    (self.check_ip, self.op.instance_name),
10201                                    errors.ECODE_NOTUNIQUE)
10202
10203     #### mac address generation
10204     # By generating here the mac address both the allocator and the hooks get
10205     # the real final mac address rather than the 'auto' or 'generate' value.
10206     # There is a race condition between the generation and the instance object
10207     # creation, which means that we know the mac is valid now, but we're not
10208     # sure it will be when we actually add the instance. If things go bad
10209     # adding the instance will abort because of a duplicate mac, and the
10210     # creation job will fail.
10211     for nic in self.nics:
10212       if nic.mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
10213         nic.mac = self.cfg.GenerateMAC(nic.network, self.proc.GetECId())
10214
10215     #### allocator run
10216
10217     if self.op.iallocator is not None:
10218       self._RunAllocator()
10219
10220     # Release all unneeded node locks
10221     _ReleaseLocks(self, locking.LEVEL_NODE,
10222                   keep=filter(None, [self.op.pnode, self.op.snode,
10223                                      self.op.src_node]))
10224     _ReleaseLocks(self, locking.LEVEL_NODE_RES,
10225                   keep=filter(None, [self.op.pnode, self.op.snode,
10226                                      self.op.src_node]))
10227
10228     #### node related checks
10229
10230     # check primary node
10231     self.pnode = pnode = self.cfg.GetNodeInfo(self.op.pnode)
10232     assert self.pnode is not None, \
10233       "Cannot retrieve locked node %s" % self.op.pnode
10234     if pnode.offline:
10235       raise errors.OpPrereqError("Cannot use offline primary node '%s'" %
10236                                  pnode.name, errors.ECODE_STATE)
10237     if pnode.drained:
10238       raise errors.OpPrereqError("Cannot use drained primary node '%s'" %
10239                                  pnode.name, errors.ECODE_STATE)
10240     if not pnode.vm_capable:
10241       raise errors.OpPrereqError("Cannot use non-vm_capable primary node"
10242                                  " '%s'" % pnode.name, errors.ECODE_STATE)
10243
10244     self.secondaries = []
10245
10246     # Fill in any IPs from IP pools. This must happen here, because we need to
10247     # know the nic's primary node, as specified by the iallocator
10248     for idx, nic in enumerate(self.nics):
10249       net = nic.network
10250       if net is not None:
10251         netparams = self.cfg.GetGroupNetParams(net, self.pnode.name)
10252         if netparams is None:
10253           raise errors.OpPrereqError("No netparams found for network"
10254                                      " %s. Propably not connected to"
10255                                      " node's %s nodegroup" %
10256                                      (net, self.pnode.name),
10257                                      errors.ECODE_INVAL)
10258         self.LogInfo("NIC/%d inherits netparams %s" %
10259                      (idx, netparams.values()))
10260         nic.nicparams = dict(netparams)
10261         if nic.ip is not None:
10262           filled_params = cluster.SimpleFillNIC(nic.nicparams)
10263           if nic.ip.lower() == constants.NIC_IP_POOL:
10264             try:
10265               nic.ip = self.cfg.GenerateIp(net, self.proc.GetECId())
10266             except errors.ReservationError:
10267               raise errors.OpPrereqError("Unable to get a free IP for NIC %d"
10268                                          " from the address pool" % idx,
10269                                          errors.ECODE_STATE)
10270             self.LogInfo("Chose IP %s from network %s", nic.ip, net)
10271           else:
10272             try:
10273               self.cfg.ReserveIp(net, nic.ip, self.proc.GetECId())
10274             except errors.ReservationError:
10275               raise errors.OpPrereqError("IP address %s already in use"
10276                                          " or does not belong to network %s" %
10277                                          (nic.ip, net),
10278                                          errors.ECODE_NOTUNIQUE)
10279       else:
10280         # net is None, ip None or given
10281         if self.op.conflicts_check:
10282           _CheckForConflictingIp(self, nic.ip, self.pnode.name)
10283
10284
10285     # mirror node verification
10286     if self.op.disk_template in constants.DTS_INT_MIRROR:
10287       if self.op.snode == pnode.name:
10288         raise errors.OpPrereqError("The secondary node cannot be the"
10289                                    " primary node", errors.ECODE_INVAL)
10290       _CheckNodeOnline(self, self.op.snode)
10291       _CheckNodeNotDrained(self, self.op.snode)
10292       _CheckNodeVmCapable(self, self.op.snode)
10293       self.secondaries.append(self.op.snode)
10294
10295       snode = self.cfg.GetNodeInfo(self.op.snode)
10296       if pnode.group != snode.group:
10297         self.LogWarning("The primary and secondary nodes are in two"
10298                         " different node groups; the disk parameters"
10299                         " from the first disk's node group will be"
10300                         " used")
10301
10302     nodenames = [pnode.name] + self.secondaries
10303
10304     if not self.adopt_disks:
10305       if self.op.disk_template == constants.DT_RBD:
10306         # _CheckRADOSFreeSpace() is just a placeholder.
10307         # Any function that checks prerequisites can be placed here.
10308         # Check if there is enough space on the RADOS cluster.
10309         _CheckRADOSFreeSpace()
10310       elif self.op.disk_template == constants.DT_EXT:
10311         # FIXME: Function that checks prereqs if needed
10312         pass
10313       else:
10314         # Check lv size requirements, if not adopting
10315         req_sizes = _ComputeDiskSizePerVG(self.op.disk_template, self.disks)
10316         _CheckNodesFreeDiskPerVG(self, nodenames, req_sizes)
10317
10318     elif self.op.disk_template == constants.DT_PLAIN: # Check the adoption data
10319       all_lvs = set(["%s/%s" % (disk[constants.IDISK_VG],
10320                                 disk[constants.IDISK_ADOPT])
10321                      for disk in self.disks])
10322       if len(all_lvs) != len(self.disks):
10323         raise errors.OpPrereqError("Duplicate volume names given for adoption",
10324                                    errors.ECODE_INVAL)
10325       for lv_name in all_lvs:
10326         try:
10327           # FIXME: lv_name here is "vg/lv" need to ensure that other calls
10328           # to ReserveLV uses the same syntax
10329           self.cfg.ReserveLV(lv_name, self.proc.GetECId())
10330         except errors.ReservationError:
10331           raise errors.OpPrereqError("LV named %s used by another instance" %
10332                                      lv_name, errors.ECODE_NOTUNIQUE)
10333
10334       vg_names = self.rpc.call_vg_list([pnode.name])[pnode.name]
10335       vg_names.Raise("Cannot get VG information from node %s" % pnode.name)
10336
10337       node_lvs = self.rpc.call_lv_list([pnode.name],
10338                                        vg_names.payload.keys())[pnode.name]
10339       node_lvs.Raise("Cannot get LV information from node %s" % pnode.name)
10340       node_lvs = node_lvs.payload
10341
10342       delta = all_lvs.difference(node_lvs.keys())
10343       if delta:
10344         raise errors.OpPrereqError("Missing logical volume(s): %s" %
10345                                    utils.CommaJoin(delta),
10346                                    errors.ECODE_INVAL)
10347       online_lvs = [lv for lv in all_lvs if node_lvs[lv][2]]
10348       if online_lvs:
10349         raise errors.OpPrereqError("Online logical volumes found, cannot"
10350                                    " adopt: %s" % utils.CommaJoin(online_lvs),
10351                                    errors.ECODE_STATE)
10352       # update the size of disk based on what is found
10353       for dsk in self.disks:
10354         dsk[constants.IDISK_SIZE] = \
10355           int(float(node_lvs["%s/%s" % (dsk[constants.IDISK_VG],
10356                                         dsk[constants.IDISK_ADOPT])][0]))
10357
10358     elif self.op.disk_template == constants.DT_BLOCK:
10359       # Normalize and de-duplicate device paths
10360       all_disks = set([os.path.abspath(disk[constants.IDISK_ADOPT])
10361                        for disk in self.disks])
10362       if len(all_disks) != len(self.disks):
10363         raise errors.OpPrereqError("Duplicate disk names given for adoption",
10364                                    errors.ECODE_INVAL)
10365       baddisks = [d for d in all_disks
10366                   if not d.startswith(constants.ADOPTABLE_BLOCKDEV_ROOT)]
10367       if baddisks:
10368         raise errors.OpPrereqError("Device node(s) %s lie outside %s and"
10369                                    " cannot be adopted" %
10370                                    (", ".join(baddisks),
10371                                     constants.ADOPTABLE_BLOCKDEV_ROOT),
10372                                    errors.ECODE_INVAL)
10373
10374       node_disks = self.rpc.call_bdev_sizes([pnode.name],
10375                                             list(all_disks))[pnode.name]
10376       node_disks.Raise("Cannot get block device information from node %s" %
10377                        pnode.name)
10378       node_disks = node_disks.payload
10379       delta = all_disks.difference(node_disks.keys())
10380       if delta:
10381         raise errors.OpPrereqError("Missing block device(s): %s" %
10382                                    utils.CommaJoin(delta),
10383                                    errors.ECODE_INVAL)
10384       for dsk in self.disks:
10385         dsk[constants.IDISK_SIZE] = \
10386           int(float(node_disks[dsk[constants.IDISK_ADOPT]]))
10387
10388     # Verify instance specs
10389     spindle_use = self.be_full.get(constants.BE_SPINDLE_USE, None)
10390     ispec = {
10391       constants.ISPEC_MEM_SIZE: self.be_full.get(constants.BE_MAXMEM, None),
10392       constants.ISPEC_CPU_COUNT: self.be_full.get(constants.BE_VCPUS, None),
10393       constants.ISPEC_DISK_COUNT: len(self.disks),
10394       constants.ISPEC_DISK_SIZE: [disk[constants.IDISK_SIZE]
10395                                   for disk in self.disks],
10396       constants.ISPEC_NIC_COUNT: len(self.nics),
10397       constants.ISPEC_SPINDLE_USE: spindle_use,
10398       }
10399
10400     group_info = self.cfg.GetNodeGroup(pnode.group)
10401     ipolicy = _CalculateGroupIPolicy(cluster, group_info)
10402     res = _ComputeIPolicyInstanceSpecViolation(ipolicy, ispec)
10403     if not self.op.ignore_ipolicy and res:
10404       raise errors.OpPrereqError(("Instance allocation to group %s violates"
10405                                   " policy: %s") % (pnode.group,
10406                                                     utils.CommaJoin(res)),
10407                                   errors.ECODE_INVAL)
10408
10409     _CheckHVParams(self, nodenames, self.op.hypervisor, self.op.hvparams)
10410
10411     _CheckNodeHasOS(self, pnode.name, self.op.os_type, self.op.force_variant)
10412     # check OS parameters (remotely)
10413     _CheckOSParams(self, True, nodenames, self.op.os_type, self.os_full)
10414
10415     _CheckNicsBridgesExist(self, self.nics, self.pnode.name)
10416
10417     #TODO: _CheckExtParams (remotely)
10418     # Check parameters for extstorage
10419
10420     # memory check on primary node
10421     #TODO(dynmem): use MINMEM for checking
10422     if self.op.start:
10423       _CheckNodeFreeMemory(self, self.pnode.name,
10424                            "creating instance %s" % self.op.instance_name,
10425                            self.be_full[constants.BE_MAXMEM],
10426                            self.op.hypervisor)
10427
10428     self.dry_run_result = list(nodenames)
10429
10430   def Exec(self, feedback_fn):
10431     """Create and add the instance to the cluster.
10432
10433     """
10434     instance = self.op.instance_name
10435     pnode_name = self.pnode.name
10436
10437     assert not (self.owned_locks(locking.LEVEL_NODE_RES) -
10438                 self.owned_locks(locking.LEVEL_NODE)), \
10439       "Node locks differ from node resource locks"
10440
10441     ht_kind = self.op.hypervisor
10442     if ht_kind in constants.HTS_REQ_PORT:
10443       network_port = self.cfg.AllocatePort()
10444     else:
10445       network_port = None
10446
10447     # This is ugly but we got a chicken-egg problem here
10448     # We can only take the group disk parameters, as the instance
10449     # has no disks yet (we are generating them right here).
10450     node = self.cfg.GetNodeInfo(pnode_name)
10451     nodegroup = self.cfg.GetNodeGroup(node.group)
10452     disks = _GenerateDiskTemplate(self,
10453                                   self.op.disk_template,
10454                                   instance, pnode_name,
10455                                   self.secondaries,
10456                                   self.disks,
10457                                   self.instance_file_storage_dir,
10458                                   self.op.file_driver,
10459                                   0,
10460                                   feedback_fn,
10461                                   self.cfg.GetGroupDiskParams(nodegroup))
10462
10463     iobj = objects.Instance(name=instance, os=self.op.os_type,
10464                             primary_node=pnode_name,
10465                             nics=self.nics, disks=disks,
10466                             disk_template=self.op.disk_template,
10467                             admin_state=constants.ADMINST_DOWN,
10468                             network_port=network_port,
10469                             beparams=self.op.beparams,
10470                             hvparams=self.op.hvparams,
10471                             hypervisor=self.op.hypervisor,
10472                             osparams=self.op.osparams,
10473                             hotplug_info=self.hotplug_info,
10474                             )
10475
10476     if self.op.tags:
10477       for tag in self.op.tags:
10478         iobj.AddTag(tag)
10479
10480     if self.adopt_disks:
10481       if self.op.disk_template == constants.DT_PLAIN:
10482         # rename LVs to the newly-generated names; we need to construct
10483         # 'fake' LV disks with the old data, plus the new unique_id
10484         tmp_disks = [objects.Disk.FromDict(v.ToDict()) for v in disks]
10485         rename_to = []
10486         for t_dsk, a_dsk in zip(tmp_disks, self.disks):
10487           rename_to.append(t_dsk.logical_id)
10488           t_dsk.logical_id = (t_dsk.logical_id[0], a_dsk[constants.IDISK_ADOPT])
10489           self.cfg.SetDiskID(t_dsk, pnode_name)
10490         result = self.rpc.call_blockdev_rename(pnode_name,
10491                                                zip(tmp_disks, rename_to))
10492         result.Raise("Failed to rename adoped LVs")
10493     else:
10494       feedback_fn("* creating instance disks...")
10495       try:
10496         _CreateDisks(self, iobj)
10497       except errors.OpExecError:
10498         self.LogWarning("Device creation failed, reverting...")
10499         try:
10500           _RemoveDisks(self, iobj)
10501         finally:
10502           self.cfg.ReleaseDRBDMinors(instance)
10503           raise
10504
10505     feedback_fn("adding instance %s to cluster config" % instance)
10506
10507     self.cfg.AddInstance(iobj, self.proc.GetECId())
10508
10509     # Declare that we don't want to remove the instance lock anymore, as we've
10510     # added the instance to the config
10511     del self.remove_locks[locking.LEVEL_INSTANCE]
10512
10513     if self.op.mode == constants.INSTANCE_IMPORT:
10514       # Release unused nodes
10515       _ReleaseLocks(self, locking.LEVEL_NODE, keep=[self.op.src_node])
10516     else:
10517       # Release all nodes
10518       _ReleaseLocks(self, locking.LEVEL_NODE)
10519
10520     disk_abort = False
10521     if not self.adopt_disks and self.cfg.GetClusterInfo().prealloc_wipe_disks:
10522       feedback_fn("* wiping instance disks...")
10523       try:
10524         _WipeDisks(self, iobj)
10525       except errors.OpExecError, err:
10526         logging.exception("Wiping disks failed")
10527         self.LogWarning("Wiping instance disks failed (%s)", err)
10528         disk_abort = True
10529
10530     if disk_abort:
10531       # Something is already wrong with the disks, don't do anything else
10532       pass
10533     elif self.op.wait_for_sync:
10534       disk_abort = not _WaitForSync(self, iobj)
10535     elif iobj.disk_template in constants.DTS_INT_MIRROR:
10536       # make sure the disks are not degraded (still sync-ing is ok)
10537       feedback_fn("* checking mirrors status")
10538       disk_abort = not _WaitForSync(self, iobj, oneshot=True)
10539     else:
10540       disk_abort = False
10541
10542     if disk_abort:
10543       _RemoveDisks(self, iobj)
10544       self.cfg.RemoveInstance(iobj.name)
10545       # Make sure the instance lock gets removed
10546       self.remove_locks[locking.LEVEL_INSTANCE] = iobj.name
10547       raise errors.OpExecError("There are some degraded disks for"
10548                                " this instance")
10549
10550     # Release all node resource locks
10551     _ReleaseLocks(self, locking.LEVEL_NODE_RES)
10552
10553     if iobj.disk_template != constants.DT_DISKLESS and not self.adopt_disks:
10554       # we need to set the disks ID to the primary node, since the
10555       # preceding code might or might have not done it, depending on
10556       # disk template and other options
10557       for disk in iobj.disks:
10558         self.cfg.SetDiskID(disk, pnode_name)
10559       if self.op.mode == constants.INSTANCE_CREATE:
10560         if not self.op.no_install:
10561           pause_sync = (iobj.disk_template in constants.DTS_INT_MIRROR and
10562                         not self.op.wait_for_sync)
10563           if pause_sync:
10564             feedback_fn("* pausing disk sync to install instance OS")
10565             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
10566                                                               (iobj.disks,
10567                                                                iobj), True)
10568             for idx, success in enumerate(result.payload):
10569               if not success:
10570                 logging.warn("pause-sync of instance %s for disk %d failed",
10571                              instance, idx)
10572
10573           feedback_fn("* running the instance OS create scripts...")
10574           # FIXME: pass debug option from opcode to backend
10575           os_add_result = \
10576             self.rpc.call_instance_os_add(pnode_name, (iobj, None), False,
10577                                           self.op.debug_level)
10578           if pause_sync:
10579             feedback_fn("* resuming disk sync")
10580             result = self.rpc.call_blockdev_pause_resume_sync(pnode_name,
10581                                                               (iobj.disks,
10582                                                                iobj), False)
10583             for idx, success in enumerate(result.payload):
10584               if not success:
10585                 logging.warn("resume-sync of instance %s for disk %d failed",
10586                              instance, idx)
10587
10588           os_add_result.Raise("Could not add os for instance %s"
10589                               " on node %s" % (instance, pnode_name))
10590
10591       else:
10592         if self.op.mode == constants.INSTANCE_IMPORT:
10593           feedback_fn("* running the instance OS import scripts...")
10594
10595           transfers = []
10596
10597           for idx, image in enumerate(self.src_images):
10598             if not image:
10599               continue
10600
10601             # FIXME: pass debug option from opcode to backend
10602             dt = masterd.instance.DiskTransfer("disk/%s" % idx,
10603                                                constants.IEIO_FILE, (image, ),
10604                                                constants.IEIO_SCRIPT,
10605                                                (iobj.disks[idx], idx),
10606                                                None)
10607             transfers.append(dt)
10608
10609           import_result = \
10610             masterd.instance.TransferInstanceData(self, feedback_fn,
10611                                                   self.op.src_node, pnode_name,
10612                                                   self.pnode.secondary_ip,
10613                                                   iobj, transfers)
10614           if not compat.all(import_result):
10615             self.LogWarning("Some disks for instance %s on node %s were not"
10616                             " imported successfully" % (instance, pnode_name))
10617
10618           rename_from = self._old_instance_name
10619
10620         elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
10621           feedback_fn("* preparing remote import...")
10622           # The source cluster will stop the instance before attempting to make
10623           # a connection. In some cases stopping an instance can take a long
10624           # time, hence the shutdown timeout is added to the connection
10625           # timeout.
10626           connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
10627                              self.op.source_shutdown_timeout)
10628           timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
10629
10630           assert iobj.primary_node == self.pnode.name
10631           disk_results = \
10632             masterd.instance.RemoteImport(self, feedback_fn, iobj, self.pnode,
10633                                           self.source_x509_ca,
10634                                           self._cds, timeouts)
10635           if not compat.all(disk_results):
10636             # TODO: Should the instance still be started, even if some disks
10637             # failed to import (valid for local imports, too)?
10638             self.LogWarning("Some disks for instance %s on node %s were not"
10639                             " imported successfully" % (instance, pnode_name))
10640
10641           rename_from = self.source_instance_name
10642
10643         else:
10644           # also checked in the prereq part
10645           raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
10646                                        % self.op.mode)
10647
10648         # Run rename script on newly imported instance
10649         assert iobj.name == instance
10650         feedback_fn("Running rename script for %s" % instance)
10651         result = self.rpc.call_instance_run_rename(pnode_name, iobj,
10652                                                    rename_from,
10653                                                    self.op.debug_level)
10654         if result.fail_msg:
10655           self.LogWarning("Failed to run rename script for %s on node"
10656                           " %s: %s" % (instance, pnode_name, result.fail_msg))
10657
10658     assert not self.owned_locks(locking.LEVEL_NODE_RES)
10659
10660     if self.op.start:
10661       iobj.admin_state = constants.ADMINST_UP
10662       self.cfg.Update(iobj, feedback_fn)
10663       logging.info("Starting instance %s on node %s", instance, pnode_name)
10664       feedback_fn("* starting instance...")
10665       result = self.rpc.call_instance_start(pnode_name, (iobj, None, None),
10666                                             False)
10667       result.Raise("Could not start instance")
10668
10669     return list(iobj.all_nodes)
10670
10671
10672 def _CheckRADOSFreeSpace():
10673   """Compute disk size requirements inside the RADOS cluster.
10674
10675   """
10676   # For the RADOS cluster we assume there is always enough space.
10677   pass
10678
10679
10680 class LUInstanceConsole(NoHooksLU):
10681   """Connect to an instance's console.
10682
10683   This is somewhat special in that it returns the command line that
10684   you need to run on the master node in order to connect to the
10685   console.
10686
10687   """
10688   REQ_BGL = False
10689
10690   def ExpandNames(self):
10691     self.share_locks = _ShareAll()
10692     self._ExpandAndLockInstance()
10693
10694   def CheckPrereq(self):
10695     """Check prerequisites.
10696
10697     This checks that the instance is in the cluster.
10698
10699     """
10700     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
10701     assert self.instance is not None, \
10702       "Cannot retrieve locked instance %s" % self.op.instance_name
10703     _CheckNodeOnline(self, self.instance.primary_node)
10704
10705   def Exec(self, feedback_fn):
10706     """Connect to the console of an instance
10707
10708     """
10709     instance = self.instance
10710     node = instance.primary_node
10711
10712     node_insts = self.rpc.call_instance_list([node],
10713                                              [instance.hypervisor])[node]
10714     node_insts.Raise("Can't get node information from %s" % node)
10715
10716     if instance.name not in node_insts.payload:
10717       if instance.admin_state == constants.ADMINST_UP:
10718         state = constants.INSTST_ERRORDOWN
10719       elif instance.admin_state == constants.ADMINST_DOWN:
10720         state = constants.INSTST_ADMINDOWN
10721       else:
10722         state = constants.INSTST_ADMINOFFLINE
10723       raise errors.OpExecError("Instance %s is not running (state %s)" %
10724                                (instance.name, state))
10725
10726     logging.debug("Connecting to console of %s on %s", instance.name, node)
10727
10728     return _GetInstanceConsole(self.cfg.GetClusterInfo(), instance)
10729
10730
10731 def _GetInstanceConsole(cluster, instance):
10732   """Returns console information for an instance.
10733
10734   @type cluster: L{objects.Cluster}
10735   @type instance: L{objects.Instance}
10736   @rtype: dict
10737
10738   """
10739   hyper = hypervisor.GetHypervisor(instance.hypervisor)
10740   # beparams and hvparams are passed separately, to avoid editing the
10741   # instance and then saving the defaults in the instance itself.
10742   hvparams = cluster.FillHV(instance)
10743   beparams = cluster.FillBE(instance)
10744   console = hyper.GetInstanceConsole(instance, hvparams, beparams)
10745
10746   assert console.instance == instance.name
10747   assert console.Validate()
10748
10749   return console.ToDict()
10750
10751
10752 class LUInstanceReplaceDisks(LogicalUnit):
10753   """Replace the disks of an instance.
10754
10755   """
10756   HPATH = "mirrors-replace"
10757   HTYPE = constants.HTYPE_INSTANCE
10758   REQ_BGL = False
10759
10760   def CheckArguments(self):
10761     TLReplaceDisks.CheckArguments(self.op.mode, self.op.remote_node,
10762                                   self.op.iallocator)
10763
10764   def ExpandNames(self):
10765     self._ExpandAndLockInstance()
10766
10767     assert locking.LEVEL_NODE not in self.needed_locks
10768     assert locking.LEVEL_NODE_RES not in self.needed_locks
10769     assert locking.LEVEL_NODEGROUP not in self.needed_locks
10770
10771     assert self.op.iallocator is None or self.op.remote_node is None, \
10772       "Conflicting options"
10773
10774     if self.op.remote_node is not None:
10775       self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
10776
10777       # Warning: do not remove the locking of the new secondary here
10778       # unless DRBD8.AddChildren is changed to work in parallel;
10779       # currently it doesn't since parallel invocations of
10780       # FindUnusedMinor will conflict
10781       self.needed_locks[locking.LEVEL_NODE] = [self.op.remote_node]
10782       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
10783     else:
10784       self.needed_locks[locking.LEVEL_NODE] = []
10785       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
10786
10787       if self.op.iallocator is not None:
10788         # iallocator will select a new node in the same group
10789         self.needed_locks[locking.LEVEL_NODEGROUP] = []
10790
10791     self.needed_locks[locking.LEVEL_NODE_RES] = []
10792
10793     self.replacer = TLReplaceDisks(self, self.op.instance_name, self.op.mode,
10794                                    self.op.iallocator, self.op.remote_node,
10795                                    self.op.disks, False, self.op.early_release,
10796                                    self.op.ignore_ipolicy)
10797
10798     self.tasklets = [self.replacer]
10799
10800   def DeclareLocks(self, level):
10801     if level == locking.LEVEL_NODEGROUP:
10802       assert self.op.remote_node is None
10803       assert self.op.iallocator is not None
10804       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
10805
10806       self.share_locks[locking.LEVEL_NODEGROUP] = 1
10807       # Lock all groups used by instance optimistically; this requires going
10808       # via the node before it's locked, requiring verification later on
10809       self.needed_locks[locking.LEVEL_NODEGROUP] = \
10810         self.cfg.GetInstanceNodeGroups(self.op.instance_name)
10811
10812     elif level == locking.LEVEL_NODE:
10813       if self.op.iallocator is not None:
10814         assert self.op.remote_node is None
10815         assert not self.needed_locks[locking.LEVEL_NODE]
10816
10817         # Lock member nodes of all locked groups
10818         self.needed_locks[locking.LEVEL_NODE] = [node_name
10819           for group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
10820           for node_name in self.cfg.GetNodeGroup(group_uuid).members]
10821       else:
10822         self._LockInstancesNodes()
10823     elif level == locking.LEVEL_NODE_RES:
10824       # Reuse node locks
10825       self.needed_locks[locking.LEVEL_NODE_RES] = \
10826         self.needed_locks[locking.LEVEL_NODE]
10827
10828   def BuildHooksEnv(self):
10829     """Build hooks env.
10830
10831     This runs on the master, the primary and all the secondaries.
10832
10833     """
10834     instance = self.replacer.instance
10835     env = {
10836       "MODE": self.op.mode,
10837       "NEW_SECONDARY": self.op.remote_node,
10838       "OLD_SECONDARY": instance.secondary_nodes[0],
10839       }
10840     env.update(_BuildInstanceHookEnvByObject(self, instance))
10841     return env
10842
10843   def BuildHooksNodes(self):
10844     """Build hooks nodes.
10845
10846     """
10847     instance = self.replacer.instance
10848     nl = [
10849       self.cfg.GetMasterNode(),
10850       instance.primary_node,
10851       ]
10852     if self.op.remote_node is not None:
10853       nl.append(self.op.remote_node)
10854     return nl, nl
10855
10856   def CheckPrereq(self):
10857     """Check prerequisites.
10858
10859     """
10860     assert (self.glm.is_owned(locking.LEVEL_NODEGROUP) or
10861             self.op.iallocator is None)
10862
10863     # Verify if node group locks are still correct
10864     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
10865     if owned_groups:
10866       _CheckInstanceNodeGroups(self.cfg, self.op.instance_name, owned_groups)
10867
10868     return LogicalUnit.CheckPrereq(self)
10869
10870
10871 class TLReplaceDisks(Tasklet):
10872   """Replaces disks for an instance.
10873
10874   Note: Locking is not within the scope of this class.
10875
10876   """
10877   def __init__(self, lu, instance_name, mode, iallocator_name, remote_node,
10878                disks, delay_iallocator, early_release, ignore_ipolicy):
10879     """Initializes this class.
10880
10881     """
10882     Tasklet.__init__(self, lu)
10883
10884     # Parameters
10885     self.instance_name = instance_name
10886     self.mode = mode
10887     self.iallocator_name = iallocator_name
10888     self.remote_node = remote_node
10889     self.disks = disks
10890     self.delay_iallocator = delay_iallocator
10891     self.early_release = early_release
10892     self.ignore_ipolicy = ignore_ipolicy
10893
10894     # Runtime data
10895     self.instance = None
10896     self.new_node = None
10897     self.target_node = None
10898     self.other_node = None
10899     self.remote_node_info = None
10900     self.node_secondary_ip = None
10901
10902   @staticmethod
10903   def CheckArguments(mode, remote_node, iallocator):
10904     """Helper function for users of this class.
10905
10906     """
10907     # check for valid parameter combination
10908     if mode == constants.REPLACE_DISK_CHG:
10909       if remote_node is None and iallocator is None:
10910         raise errors.OpPrereqError("When changing the secondary either an"
10911                                    " iallocator script must be used or the"
10912                                    " new node given", errors.ECODE_INVAL)
10913
10914       if remote_node is not None and iallocator is not None:
10915         raise errors.OpPrereqError("Give either the iallocator or the new"
10916                                    " secondary, not both", errors.ECODE_INVAL)
10917
10918     elif remote_node is not None or iallocator is not None:
10919       # Not replacing the secondary
10920       raise errors.OpPrereqError("The iallocator and new node options can"
10921                                  " only be used when changing the"
10922                                  " secondary node", errors.ECODE_INVAL)
10923
10924   @staticmethod
10925   def _RunAllocator(lu, iallocator_name, instance_name, relocate_from):
10926     """Compute a new secondary node using an IAllocator.
10927
10928     """
10929     ial = IAllocator(lu.cfg, lu.rpc,
10930                      mode=constants.IALLOCATOR_MODE_RELOC,
10931                      name=instance_name,
10932                      relocate_from=list(relocate_from))
10933
10934     ial.Run(iallocator_name)
10935
10936     if not ial.success:
10937       raise errors.OpPrereqError("Can't compute nodes using iallocator '%s':"
10938                                  " %s" % (iallocator_name, ial.info),
10939                                  errors.ECODE_NORES)
10940
10941     if len(ial.result) != ial.required_nodes:
10942       raise errors.OpPrereqError("iallocator '%s' returned invalid number"
10943                                  " of nodes (%s), required %s" %
10944                                  (iallocator_name,
10945                                   len(ial.result), ial.required_nodes),
10946                                  errors.ECODE_FAULT)
10947
10948     remote_node_name = ial.result[0]
10949
10950     lu.LogInfo("Selected new secondary for instance '%s': %s",
10951                instance_name, remote_node_name)
10952
10953     return remote_node_name
10954
10955   def _FindFaultyDisks(self, node_name):
10956     """Wrapper for L{_FindFaultyInstanceDisks}.
10957
10958     """
10959     return _FindFaultyInstanceDisks(self.cfg, self.rpc, self.instance,
10960                                     node_name, True)
10961
10962   def _CheckDisksActivated(self, instance):
10963     """Checks if the instance disks are activated.
10964
10965     @param instance: The instance to check disks
10966     @return: True if they are activated, False otherwise
10967
10968     """
10969     nodes = instance.all_nodes
10970
10971     for idx, dev in enumerate(instance.disks):
10972       for node in nodes:
10973         self.lu.LogInfo("Checking disk/%d on %s", idx, node)
10974         self.cfg.SetDiskID(dev, node)
10975
10976         result = _BlockdevFind(self, node, dev, instance)
10977
10978         if result.offline:
10979           continue
10980         elif result.fail_msg or not result.payload:
10981           return False
10982
10983     return True
10984
10985   def CheckPrereq(self):
10986     """Check prerequisites.
10987
10988     This checks that the instance is in the cluster.
10989
10990     """
10991     self.instance = instance = self.cfg.GetInstanceInfo(self.instance_name)
10992     assert instance is not None, \
10993       "Cannot retrieve locked instance %s" % self.instance_name
10994
10995     if instance.disk_template != constants.DT_DRBD8:
10996       raise errors.OpPrereqError("Can only run replace disks for DRBD8-based"
10997                                  " instances", errors.ECODE_INVAL)
10998
10999     if len(instance.secondary_nodes) != 1:
11000       raise errors.OpPrereqError("The instance has a strange layout,"
11001                                  " expected one secondary but found %d" %
11002                                  len(instance.secondary_nodes),
11003                                  errors.ECODE_FAULT)
11004
11005     if not self.delay_iallocator:
11006       self._CheckPrereq2()
11007
11008   def _CheckPrereq2(self):
11009     """Check prerequisites, second part.
11010
11011     This function should always be part of CheckPrereq. It was separated and is
11012     now called from Exec because during node evacuation iallocator was only
11013     called with an unmodified cluster model, not taking planned changes into
11014     account.
11015
11016     """
11017     instance = self.instance
11018     secondary_node = instance.secondary_nodes[0]
11019
11020     if self.iallocator_name is None:
11021       remote_node = self.remote_node
11022     else:
11023       remote_node = self._RunAllocator(self.lu, self.iallocator_name,
11024                                        instance.name, instance.secondary_nodes)
11025
11026     if remote_node is None:
11027       self.remote_node_info = None
11028     else:
11029       assert remote_node in self.lu.owned_locks(locking.LEVEL_NODE), \
11030              "Remote node '%s' is not locked" % remote_node
11031
11032       self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
11033       assert self.remote_node_info is not None, \
11034         "Cannot retrieve locked node %s" % remote_node
11035
11036     if remote_node == self.instance.primary_node:
11037       raise errors.OpPrereqError("The specified node is the primary node of"
11038                                  " the instance", errors.ECODE_INVAL)
11039
11040     if remote_node == secondary_node:
11041       raise errors.OpPrereqError("The specified node is already the"
11042                                  " secondary node of the instance",
11043                                  errors.ECODE_INVAL)
11044
11045     if self.disks and self.mode in (constants.REPLACE_DISK_AUTO,
11046                                     constants.REPLACE_DISK_CHG):
11047       raise errors.OpPrereqError("Cannot specify disks to be replaced",
11048                                  errors.ECODE_INVAL)
11049
11050     if self.mode == constants.REPLACE_DISK_AUTO:
11051       if not self._CheckDisksActivated(instance):
11052         raise errors.OpPrereqError("Please run activate-disks on instance %s"
11053                                    " first" % self.instance_name,
11054                                    errors.ECODE_STATE)
11055       faulty_primary = self._FindFaultyDisks(instance.primary_node)
11056       faulty_secondary = self._FindFaultyDisks(secondary_node)
11057
11058       if faulty_primary and faulty_secondary:
11059         raise errors.OpPrereqError("Instance %s has faulty disks on more than"
11060                                    " one node and can not be repaired"
11061                                    " automatically" % self.instance_name,
11062                                    errors.ECODE_STATE)
11063
11064       if faulty_primary:
11065         self.disks = faulty_primary
11066         self.target_node = instance.primary_node
11067         self.other_node = secondary_node
11068         check_nodes = [self.target_node, self.other_node]
11069       elif faulty_secondary:
11070         self.disks = faulty_secondary
11071         self.target_node = secondary_node
11072         self.other_node = instance.primary_node
11073         check_nodes = [self.target_node, self.other_node]
11074       else:
11075         self.disks = []
11076         check_nodes = []
11077
11078     else:
11079       # Non-automatic modes
11080       if self.mode == constants.REPLACE_DISK_PRI:
11081         self.target_node = instance.primary_node
11082         self.other_node = secondary_node
11083         check_nodes = [self.target_node, self.other_node]
11084
11085       elif self.mode == constants.REPLACE_DISK_SEC:
11086         self.target_node = secondary_node
11087         self.other_node = instance.primary_node
11088         check_nodes = [self.target_node, self.other_node]
11089
11090       elif self.mode == constants.REPLACE_DISK_CHG:
11091         self.new_node = remote_node
11092         self.other_node = instance.primary_node
11093         self.target_node = secondary_node
11094         check_nodes = [self.new_node, self.other_node]
11095
11096         _CheckNodeNotDrained(self.lu, remote_node)
11097         _CheckNodeVmCapable(self.lu, remote_node)
11098
11099         old_node_info = self.cfg.GetNodeInfo(secondary_node)
11100         assert old_node_info is not None
11101         if old_node_info.offline and not self.early_release:
11102           # doesn't make sense to delay the release
11103           self.early_release = True
11104           self.lu.LogInfo("Old secondary %s is offline, automatically enabling"
11105                           " early-release mode", secondary_node)
11106
11107       else:
11108         raise errors.ProgrammerError("Unhandled disk replace mode (%s)" %
11109                                      self.mode)
11110
11111       # If not specified all disks should be replaced
11112       if not self.disks:
11113         self.disks = range(len(self.instance.disks))
11114
11115     # TODO: This is ugly, but right now we can't distinguish between internal
11116     # submitted opcode and external one. We should fix that.
11117     if self.remote_node_info:
11118       # We change the node, lets verify it still meets instance policy
11119       new_group_info = self.cfg.GetNodeGroup(self.remote_node_info.group)
11120       ipolicy = _CalculateGroupIPolicy(self.cfg.GetClusterInfo(),
11121                                        new_group_info)
11122       _CheckTargetNodeIPolicy(self, ipolicy, instance, self.remote_node_info,
11123                               ignore=self.ignore_ipolicy)
11124
11125     for node in check_nodes:
11126       _CheckNodeOnline(self.lu, node)
11127
11128     touched_nodes = frozenset(node_name for node_name in [self.new_node,
11129                                                           self.other_node,
11130                                                           self.target_node]
11131                               if node_name is not None)
11132
11133     # Release unneeded node and node resource locks
11134     _ReleaseLocks(self.lu, locking.LEVEL_NODE, keep=touched_nodes)
11135     _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES, keep=touched_nodes)
11136
11137     # Release any owned node group
11138     if self.lu.glm.is_owned(locking.LEVEL_NODEGROUP):
11139       _ReleaseLocks(self.lu, locking.LEVEL_NODEGROUP)
11140
11141     # Check whether disks are valid
11142     for disk_idx in self.disks:
11143       instance.FindDisk(disk_idx)
11144
11145     # Get secondary node IP addresses
11146     self.node_secondary_ip = dict((name, node.secondary_ip) for (name, node)
11147                                   in self.cfg.GetMultiNodeInfo(touched_nodes))
11148
11149   def Exec(self, feedback_fn):
11150     """Execute disk replacement.
11151
11152     This dispatches the disk replacement to the appropriate handler.
11153
11154     """
11155     if self.delay_iallocator:
11156       self._CheckPrereq2()
11157
11158     if __debug__:
11159       # Verify owned locks before starting operation
11160       owned_nodes = self.lu.owned_locks(locking.LEVEL_NODE)
11161       assert set(owned_nodes) == set(self.node_secondary_ip), \
11162           ("Incorrect node locks, owning %s, expected %s" %
11163            (owned_nodes, self.node_secondary_ip.keys()))
11164       assert (self.lu.owned_locks(locking.LEVEL_NODE) ==
11165               self.lu.owned_locks(locking.LEVEL_NODE_RES))
11166
11167       owned_instances = self.lu.owned_locks(locking.LEVEL_INSTANCE)
11168       assert list(owned_instances) == [self.instance_name], \
11169           "Instance '%s' not locked" % self.instance_name
11170
11171       assert not self.lu.glm.is_owned(locking.LEVEL_NODEGROUP), \
11172           "Should not own any node group lock at this point"
11173
11174     if not self.disks:
11175       feedback_fn("No disks need replacement for instance '%s'" %
11176                   self.instance.name)
11177       return
11178
11179     feedback_fn("Replacing disk(s) %s for instance '%s'" %
11180                 (utils.CommaJoin(self.disks), self.instance.name))
11181     feedback_fn("Current primary node: %s", self.instance.primary_node)
11182     feedback_fn("Current seconary node: %s",
11183                 utils.CommaJoin(self.instance.secondary_nodes))
11184
11185     activate_disks = (self.instance.admin_state != constants.ADMINST_UP)
11186
11187     # Activate the instance disks if we're replacing them on a down instance
11188     if activate_disks:
11189       _StartInstanceDisks(self.lu, self.instance, True)
11190
11191     try:
11192       # Should we replace the secondary node?
11193       if self.new_node is not None:
11194         fn = self._ExecDrbd8Secondary
11195       else:
11196         fn = self._ExecDrbd8DiskOnly
11197
11198       result = fn(feedback_fn)
11199     finally:
11200       # Deactivate the instance disks if we're replacing them on a
11201       # down instance
11202       if activate_disks:
11203         _SafeShutdownInstanceDisks(self.lu, self.instance)
11204
11205     assert not self.lu.owned_locks(locking.LEVEL_NODE)
11206
11207     if __debug__:
11208       # Verify owned locks
11209       owned_nodes = self.lu.owned_locks(locking.LEVEL_NODE_RES)
11210       nodes = frozenset(self.node_secondary_ip)
11211       assert ((self.early_release and not owned_nodes) or
11212               (not self.early_release and not (set(owned_nodes) - nodes))), \
11213         ("Not owning the correct locks, early_release=%s, owned=%r,"
11214          " nodes=%r" % (self.early_release, owned_nodes, nodes))
11215
11216     return result
11217
11218   def _CheckVolumeGroup(self, nodes):
11219     self.lu.LogInfo("Checking volume groups")
11220
11221     vgname = self.cfg.GetVGName()
11222
11223     # Make sure volume group exists on all involved nodes
11224     results = self.rpc.call_vg_list(nodes)
11225     if not results:
11226       raise errors.OpExecError("Can't list volume groups on the nodes")
11227
11228     for node in nodes:
11229       res = results[node]
11230       res.Raise("Error checking node %s" % node)
11231       if vgname not in res.payload:
11232         raise errors.OpExecError("Volume group '%s' not found on node %s" %
11233                                  (vgname, node))
11234
11235   def _CheckDisksExistence(self, nodes):
11236     # Check disk existence
11237     for idx, dev in enumerate(self.instance.disks):
11238       if idx not in self.disks:
11239         continue
11240
11241       for node in nodes:
11242         self.lu.LogInfo("Checking disk/%d on %s" % (idx, node))
11243         self.cfg.SetDiskID(dev, node)
11244
11245         result = _BlockdevFind(self, node, dev, self.instance)
11246
11247         msg = result.fail_msg
11248         if msg or not result.payload:
11249           if not msg:
11250             msg = "disk not found"
11251           raise errors.OpExecError("Can't find disk/%d on node %s: %s" %
11252                                    (idx, node, msg))
11253
11254   def _CheckDisksConsistency(self, node_name, on_primary, ldisk):
11255     for idx, dev in enumerate(self.instance.disks):
11256       if idx not in self.disks:
11257         continue
11258
11259       self.lu.LogInfo("Checking disk/%d consistency on node %s" %
11260                       (idx, node_name))
11261
11262       if not _CheckDiskConsistency(self.lu, self.instance, dev, node_name,
11263                                    on_primary, ldisk=ldisk):
11264         raise errors.OpExecError("Node %s has degraded storage, unsafe to"
11265                                  " replace disks for instance %s" %
11266                                  (node_name, self.instance.name))
11267
11268   def _CreateNewStorage(self, node_name):
11269     """Create new storage on the primary or secondary node.
11270
11271     This is only used for same-node replaces, not for changing the
11272     secondary node, hence we don't want to modify the existing disk.
11273
11274     """
11275     iv_names = {}
11276
11277     disks = _AnnotateDiskParams(self.instance, self.instance.disks, self.cfg)
11278     for idx, dev in enumerate(disks):
11279       if idx not in self.disks:
11280         continue
11281
11282       self.lu.LogInfo("Adding storage on %s for disk/%d" % (node_name, idx))
11283
11284       self.cfg.SetDiskID(dev, node_name)
11285
11286       lv_names = [".disk%d_%s" % (idx, suffix) for suffix in ["data", "meta"]]
11287       names = _GenerateUniqueNames(self.lu, lv_names)
11288
11289       (data_disk, meta_disk) = dev.children
11290       vg_data = data_disk.logical_id[0]
11291       lv_data = objects.Disk(dev_type=constants.LD_LV, size=dev.size,
11292                              logical_id=(vg_data, names[0]),
11293                              params=data_disk.params)
11294       vg_meta = meta_disk.logical_id[0]
11295       lv_meta = objects.Disk(dev_type=constants.LD_LV, size=DRBD_META_SIZE,
11296                              logical_id=(vg_meta, names[1]),
11297                              params=meta_disk.params)
11298
11299       new_lvs = [lv_data, lv_meta]
11300       old_lvs = [child.Copy() for child in dev.children]
11301       iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
11302
11303       # we pass force_create=True to force the LVM creation
11304       for new_lv in new_lvs:
11305         _CreateBlockDevInner(self.lu, node_name, self.instance, new_lv, True,
11306                              _GetInstanceInfoText(self.instance), False)
11307
11308     return iv_names
11309
11310   def _CheckDevices(self, node_name, iv_names):
11311     for name, (dev, _, _) in iv_names.iteritems():
11312       self.cfg.SetDiskID(dev, node_name)
11313
11314       result = _BlockdevFind(self, node_name, dev, self.instance)
11315
11316       msg = result.fail_msg
11317       if msg or not result.payload:
11318         if not msg:
11319           msg = "disk not found"
11320         raise errors.OpExecError("Can't find DRBD device %s: %s" %
11321                                  (name, msg))
11322
11323       if result.payload.is_degraded:
11324         raise errors.OpExecError("DRBD device %s is degraded!" % name)
11325
11326   def _RemoveOldStorage(self, node_name, iv_names):
11327     for name, (_, old_lvs, _) in iv_names.iteritems():
11328       self.lu.LogInfo("Remove logical volumes for %s" % name)
11329
11330       for lv in old_lvs:
11331         self.cfg.SetDiskID(lv, node_name)
11332
11333         msg = self.rpc.call_blockdev_remove(node_name, lv).fail_msg
11334         if msg:
11335           self.lu.LogWarning("Can't remove old LV: %s" % msg,
11336                              hint="remove unused LVs manually")
11337
11338   def _ExecDrbd8DiskOnly(self, feedback_fn): # pylint: disable=W0613
11339     """Replace a disk on the primary or secondary for DRBD 8.
11340
11341     The algorithm for replace is quite complicated:
11342
11343       1. for each disk to be replaced:
11344
11345         1. create new LVs on the target node with unique names
11346         1. detach old LVs from the drbd device
11347         1. rename old LVs to name_replaced.<time_t>
11348         1. rename new LVs to old LVs
11349         1. attach the new LVs (with the old names now) to the drbd device
11350
11351       1. wait for sync across all devices
11352
11353       1. for each modified disk:
11354
11355         1. remove old LVs (which have the name name_replaces.<time_t>)
11356
11357     Failures are not very well handled.
11358
11359     """
11360     steps_total = 6
11361
11362     # Step: check device activation
11363     self.lu.LogStep(1, steps_total, "Check device existence")
11364     self._CheckDisksExistence([self.other_node, self.target_node])
11365     self._CheckVolumeGroup([self.target_node, self.other_node])
11366
11367     # Step: check other node consistency
11368     self.lu.LogStep(2, steps_total, "Check peer consistency")
11369     self._CheckDisksConsistency(self.other_node,
11370                                 self.other_node == self.instance.primary_node,
11371                                 False)
11372
11373     # Step: create new storage
11374     self.lu.LogStep(3, steps_total, "Allocate new storage")
11375     iv_names = self._CreateNewStorage(self.target_node)
11376
11377     # Step: for each lv, detach+rename*2+attach
11378     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
11379     for dev, old_lvs, new_lvs in iv_names.itervalues():
11380       self.lu.LogInfo("Detaching %s drbd from local storage" % dev.iv_name)
11381
11382       result = self.rpc.call_blockdev_removechildren(self.target_node, dev,
11383                                                      old_lvs)
11384       result.Raise("Can't detach drbd from local storage on node"
11385                    " %s for device %s" % (self.target_node, dev.iv_name))
11386       #dev.children = []
11387       #cfg.Update(instance)
11388
11389       # ok, we created the new LVs, so now we know we have the needed
11390       # storage; as such, we proceed on the target node to rename
11391       # old_lv to _old, and new_lv to old_lv; note that we rename LVs
11392       # using the assumption that logical_id == physical_id (which in
11393       # turn is the unique_id on that node)
11394
11395       # FIXME(iustin): use a better name for the replaced LVs
11396       temp_suffix = int(time.time())
11397       ren_fn = lambda d, suff: (d.physical_id[0],
11398                                 d.physical_id[1] + "_replaced-%s" % suff)
11399
11400       # Build the rename list based on what LVs exist on the node
11401       rename_old_to_new = []
11402       for to_ren in old_lvs:
11403         result = self.rpc.call_blockdev_find(self.target_node, to_ren)
11404         if not result.fail_msg and result.payload:
11405           # device exists
11406           rename_old_to_new.append((to_ren, ren_fn(to_ren, temp_suffix)))
11407
11408       self.lu.LogInfo("Renaming the old LVs on the target node")
11409       result = self.rpc.call_blockdev_rename(self.target_node,
11410                                              rename_old_to_new)
11411       result.Raise("Can't rename old LVs on node %s" % self.target_node)
11412
11413       # Now we rename the new LVs to the old LVs
11414       self.lu.LogInfo("Renaming the new LVs on the target node")
11415       rename_new_to_old = [(new, old.physical_id)
11416                            for old, new in zip(old_lvs, new_lvs)]
11417       result = self.rpc.call_blockdev_rename(self.target_node,
11418                                              rename_new_to_old)
11419       result.Raise("Can't rename new LVs on node %s" % self.target_node)
11420
11421       # Intermediate steps of in memory modifications
11422       for old, new in zip(old_lvs, new_lvs):
11423         new.logical_id = old.logical_id
11424         self.cfg.SetDiskID(new, self.target_node)
11425
11426       # We need to modify old_lvs so that removal later removes the
11427       # right LVs, not the newly added ones; note that old_lvs is a
11428       # copy here
11429       for disk in old_lvs:
11430         disk.logical_id = ren_fn(disk, temp_suffix)
11431         self.cfg.SetDiskID(disk, self.target_node)
11432
11433       # Now that the new lvs have the old name, we can add them to the device
11434       self.lu.LogInfo("Adding new mirror component on %s" % self.target_node)
11435       result = self.rpc.call_blockdev_addchildren(self.target_node,
11436                                                   (dev, self.instance), new_lvs)
11437       msg = result.fail_msg
11438       if msg:
11439         for new_lv in new_lvs:
11440           msg2 = self.rpc.call_blockdev_remove(self.target_node,
11441                                                new_lv).fail_msg
11442           if msg2:
11443             self.lu.LogWarning("Can't rollback device %s: %s", dev, msg2,
11444                                hint=("cleanup manually the unused logical"
11445                                      "volumes"))
11446         raise errors.OpExecError("Can't add local storage to drbd: %s" % msg)
11447
11448     cstep = itertools.count(5)
11449
11450     if self.early_release:
11451       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11452       self._RemoveOldStorage(self.target_node, iv_names)
11453       # TODO: Check if releasing locks early still makes sense
11454       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
11455     else:
11456       # Release all resource locks except those used by the instance
11457       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES,
11458                     keep=self.node_secondary_ip.keys())
11459
11460     # Release all node locks while waiting for sync
11461     _ReleaseLocks(self.lu, locking.LEVEL_NODE)
11462
11463     # TODO: Can the instance lock be downgraded here? Take the optional disk
11464     # shutdown in the caller into consideration.
11465
11466     # Wait for sync
11467     # This can fail as the old devices are degraded and _WaitForSync
11468     # does a combined result over all disks, so we don't check its return value
11469     self.lu.LogStep(cstep.next(), steps_total, "Sync devices")
11470     _WaitForSync(self.lu, self.instance)
11471
11472     # Check all devices manually
11473     self._CheckDevices(self.instance.primary_node, iv_names)
11474
11475     # Step: remove old storage
11476     if not self.early_release:
11477       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11478       self._RemoveOldStorage(self.target_node, iv_names)
11479
11480   def _ExecDrbd8Secondary(self, feedback_fn):
11481     """Replace the secondary node for DRBD 8.
11482
11483     The algorithm for replace is quite complicated:
11484       - for all disks of the instance:
11485         - create new LVs on the new node with same names
11486         - shutdown the drbd device on the old secondary
11487         - disconnect the drbd network on the primary
11488         - create the drbd device on the new secondary
11489         - network attach the drbd on the primary, using an artifice:
11490           the drbd code for Attach() will connect to the network if it
11491           finds a device which is connected to the good local disks but
11492           not network enabled
11493       - wait for sync across all devices
11494       - remove all disks from the old secondary
11495
11496     Failures are not very well handled.
11497
11498     """
11499     steps_total = 6
11500
11501     pnode = self.instance.primary_node
11502
11503     # Step: check device activation
11504     self.lu.LogStep(1, steps_total, "Check device existence")
11505     self._CheckDisksExistence([self.instance.primary_node])
11506     self._CheckVolumeGroup([self.instance.primary_node])
11507
11508     # Step: check other node consistency
11509     self.lu.LogStep(2, steps_total, "Check peer consistency")
11510     self._CheckDisksConsistency(self.instance.primary_node, True, True)
11511
11512     # Step: create new storage
11513     self.lu.LogStep(3, steps_total, "Allocate new storage")
11514     disks = _AnnotateDiskParams(self.instance, self.instance.disks, self.cfg)
11515     for idx, dev in enumerate(disks):
11516       self.lu.LogInfo("Adding new local storage on %s for disk/%d" %
11517                       (self.new_node, idx))
11518       # we pass force_create=True to force LVM creation
11519       for new_lv in dev.children:
11520         _CreateBlockDevInner(self.lu, self.new_node, self.instance, new_lv,
11521                              True, _GetInstanceInfoText(self.instance), False)
11522
11523     # Step 4: dbrd minors and drbd setups changes
11524     # after this, we must manually remove the drbd minors on both the
11525     # error and the success paths
11526     self.lu.LogStep(4, steps_total, "Changing drbd configuration")
11527     minors = self.cfg.AllocateDRBDMinor([self.new_node
11528                                          for dev in self.instance.disks],
11529                                         self.instance.name)
11530     logging.debug("Allocated minors %r", minors)
11531
11532     iv_names = {}
11533     for idx, (dev, new_minor) in enumerate(zip(self.instance.disks, minors)):
11534       self.lu.LogInfo("activating a new drbd on %s for disk/%d" %
11535                       (self.new_node, idx))
11536       # create new devices on new_node; note that we create two IDs:
11537       # one without port, so the drbd will be activated without
11538       # networking information on the new node at this stage, and one
11539       # with network, for the latter activation in step 4
11540       (o_node1, o_node2, o_port, o_minor1, o_minor2, o_secret) = dev.logical_id
11541       if self.instance.primary_node == o_node1:
11542         p_minor = o_minor1
11543       else:
11544         assert self.instance.primary_node == o_node2, "Three-node instance?"
11545         p_minor = o_minor2
11546
11547       new_alone_id = (self.instance.primary_node, self.new_node, None,
11548                       p_minor, new_minor, o_secret)
11549       new_net_id = (self.instance.primary_node, self.new_node, o_port,
11550                     p_minor, new_minor, o_secret)
11551
11552       iv_names[idx] = (dev, dev.children, new_net_id)
11553       logging.debug("Allocated new_minor: %s, new_logical_id: %s", new_minor,
11554                     new_net_id)
11555       new_drbd = objects.Disk(dev_type=constants.LD_DRBD8,
11556                               logical_id=new_alone_id,
11557                               children=dev.children,
11558                               size=dev.size,
11559                               params={})
11560       (anno_new_drbd,) = _AnnotateDiskParams(self.instance, [new_drbd],
11561                                              self.cfg)
11562       try:
11563         _CreateSingleBlockDev(self.lu, self.new_node, self.instance,
11564                               anno_new_drbd,
11565                               _GetInstanceInfoText(self.instance), False)
11566       except errors.GenericError:
11567         self.cfg.ReleaseDRBDMinors(self.instance.name)
11568         raise
11569
11570     # We have new devices, shutdown the drbd on the old secondary
11571     for idx, dev in enumerate(self.instance.disks):
11572       self.lu.LogInfo("Shutting down drbd for disk/%d on old node" % idx)
11573       self.cfg.SetDiskID(dev, self.target_node)
11574       msg = self.rpc.call_blockdev_shutdown(self.target_node,
11575                                             (dev, self.instance)).fail_msg
11576       if msg:
11577         self.lu.LogWarning("Failed to shutdown drbd for disk/%d on old"
11578                            "node: %s" % (idx, msg),
11579                            hint=("Please cleanup this device manually as"
11580                                  " soon as possible"))
11581
11582     self.lu.LogInfo("Detaching primary drbds from the network (=> standalone)")
11583     result = self.rpc.call_drbd_disconnect_net([pnode], self.node_secondary_ip,
11584                                                self.instance.disks)[pnode]
11585
11586     msg = result.fail_msg
11587     if msg:
11588       # detaches didn't succeed (unlikely)
11589       self.cfg.ReleaseDRBDMinors(self.instance.name)
11590       raise errors.OpExecError("Can't detach the disks from the network on"
11591                                " old node: %s" % (msg,))
11592
11593     # if we managed to detach at least one, we update all the disks of
11594     # the instance to point to the new secondary
11595     self.lu.LogInfo("Updating instance configuration")
11596     for dev, _, new_logical_id in iv_names.itervalues():
11597       dev.logical_id = new_logical_id
11598       self.cfg.SetDiskID(dev, self.instance.primary_node)
11599
11600     self.cfg.Update(self.instance, feedback_fn)
11601
11602     # Release all node locks (the configuration has been updated)
11603     _ReleaseLocks(self.lu, locking.LEVEL_NODE)
11604
11605     # and now perform the drbd attach
11606     self.lu.LogInfo("Attaching primary drbds to new secondary"
11607                     " (standalone => connected)")
11608     result = self.rpc.call_drbd_attach_net([self.instance.primary_node,
11609                                             self.new_node],
11610                                            self.node_secondary_ip,
11611                                            (self.instance.disks, self.instance),
11612                                            self.instance.name,
11613                                            False)
11614     for to_node, to_result in result.items():
11615       msg = to_result.fail_msg
11616       if msg:
11617         self.lu.LogWarning("Can't attach drbd disks on node %s: %s",
11618                            to_node, msg,
11619                            hint=("please do a gnt-instance info to see the"
11620                                  " status of disks"))
11621
11622     cstep = itertools.count(5)
11623
11624     if self.early_release:
11625       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11626       self._RemoveOldStorage(self.target_node, iv_names)
11627       # TODO: Check if releasing locks early still makes sense
11628       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES)
11629     else:
11630       # Release all resource locks except those used by the instance
11631       _ReleaseLocks(self.lu, locking.LEVEL_NODE_RES,
11632                     keep=self.node_secondary_ip.keys())
11633
11634     # TODO: Can the instance lock be downgraded here? Take the optional disk
11635     # shutdown in the caller into consideration.
11636
11637     # Wait for sync
11638     # This can fail as the old devices are degraded and _WaitForSync
11639     # does a combined result over all disks, so we don't check its return value
11640     self.lu.LogStep(cstep.next(), steps_total, "Sync devices")
11641     _WaitForSync(self.lu, self.instance)
11642
11643     # Check all devices manually
11644     self._CheckDevices(self.instance.primary_node, iv_names)
11645
11646     # Step: remove old storage
11647     if not self.early_release:
11648       self.lu.LogStep(cstep.next(), steps_total, "Removing old storage")
11649       self._RemoveOldStorage(self.target_node, iv_names)
11650
11651
11652 class LURepairNodeStorage(NoHooksLU):
11653   """Repairs the volume group on a node.
11654
11655   """
11656   REQ_BGL = False
11657
11658   def CheckArguments(self):
11659     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
11660
11661     storage_type = self.op.storage_type
11662
11663     if (constants.SO_FIX_CONSISTENCY not in
11664         constants.VALID_STORAGE_OPERATIONS.get(storage_type, [])):
11665       raise errors.OpPrereqError("Storage units of type '%s' can not be"
11666                                  " repaired" % storage_type,
11667                                  errors.ECODE_INVAL)
11668
11669   def ExpandNames(self):
11670     self.needed_locks = {
11671       locking.LEVEL_NODE: [self.op.node_name],
11672       }
11673
11674   def _CheckFaultyDisks(self, instance, node_name):
11675     """Ensure faulty disks abort the opcode or at least warn."""
11676     try:
11677       if _FindFaultyInstanceDisks(self.cfg, self.rpc, instance,
11678                                   node_name, True):
11679         raise errors.OpPrereqError("Instance '%s' has faulty disks on"
11680                                    " node '%s'" % (instance.name, node_name),
11681                                    errors.ECODE_STATE)
11682     except errors.OpPrereqError, err:
11683       if self.op.ignore_consistency:
11684         self.proc.LogWarning(str(err.args[0]))
11685       else:
11686         raise
11687
11688   def CheckPrereq(self):
11689     """Check prerequisites.
11690
11691     """
11692     # Check whether any instance on this node has faulty disks
11693     for inst in _GetNodeInstances(self.cfg, self.op.node_name):
11694       if inst.admin_state != constants.ADMINST_UP:
11695         continue
11696       check_nodes = set(inst.all_nodes)
11697       check_nodes.discard(self.op.node_name)
11698       for inst_node_name in check_nodes:
11699         self._CheckFaultyDisks(inst, inst_node_name)
11700
11701   def Exec(self, feedback_fn):
11702     feedback_fn("Repairing storage unit '%s' on %s ..." %
11703                 (self.op.name, self.op.node_name))
11704
11705     st_args = _GetStorageTypeArgs(self.cfg, self.op.storage_type)
11706     result = self.rpc.call_storage_execute(self.op.node_name,
11707                                            self.op.storage_type, st_args,
11708                                            self.op.name,
11709                                            constants.SO_FIX_CONSISTENCY)
11710     result.Raise("Failed to repair storage unit '%s' on %s" %
11711                  (self.op.name, self.op.node_name))
11712
11713
11714 class LUNodeEvacuate(NoHooksLU):
11715   """Evacuates instances off a list of nodes.
11716
11717   """
11718   REQ_BGL = False
11719
11720   _MODE2IALLOCATOR = {
11721     constants.NODE_EVAC_PRI: constants.IALLOCATOR_NEVAC_PRI,
11722     constants.NODE_EVAC_SEC: constants.IALLOCATOR_NEVAC_SEC,
11723     constants.NODE_EVAC_ALL: constants.IALLOCATOR_NEVAC_ALL,
11724     }
11725   assert frozenset(_MODE2IALLOCATOR.keys()) == constants.NODE_EVAC_MODES
11726   assert (frozenset(_MODE2IALLOCATOR.values()) ==
11727           constants.IALLOCATOR_NEVAC_MODES)
11728
11729   def CheckArguments(self):
11730     _CheckIAllocatorOrNode(self, "iallocator", "remote_node")
11731
11732   def ExpandNames(self):
11733     self.op.node_name = _ExpandNodeName(self.cfg, self.op.node_name)
11734
11735     if self.op.remote_node is not None:
11736       self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
11737       assert self.op.remote_node
11738
11739       if self.op.remote_node == self.op.node_name:
11740         raise errors.OpPrereqError("Can not use evacuated node as a new"
11741                                    " secondary node", errors.ECODE_INVAL)
11742
11743       if self.op.mode != constants.NODE_EVAC_SEC:
11744         raise errors.OpPrereqError("Without the use of an iallocator only"
11745                                    " secondary instances can be evacuated",
11746                                    errors.ECODE_INVAL)
11747
11748     # Declare locks
11749     self.share_locks = _ShareAll()
11750     self.needed_locks = {
11751       locking.LEVEL_INSTANCE: [],
11752       locking.LEVEL_NODEGROUP: [],
11753       locking.LEVEL_NODE: [],
11754       }
11755
11756     # Determine nodes (via group) optimistically, needs verification once locks
11757     # have been acquired
11758     self.lock_nodes = self._DetermineNodes()
11759
11760   def _DetermineNodes(self):
11761     """Gets the list of nodes to operate on.
11762
11763     """
11764     if self.op.remote_node is None:
11765       # Iallocator will choose any node(s) in the same group
11766       group_nodes = self.cfg.GetNodeGroupMembersByNodes([self.op.node_name])
11767     else:
11768       group_nodes = frozenset([self.op.remote_node])
11769
11770     # Determine nodes to be locked
11771     return set([self.op.node_name]) | group_nodes
11772
11773   def _DetermineInstances(self):
11774     """Builds list of instances to operate on.
11775
11776     """
11777     assert self.op.mode in constants.NODE_EVAC_MODES
11778
11779     if self.op.mode == constants.NODE_EVAC_PRI:
11780       # Primary instances only
11781       inst_fn = _GetNodePrimaryInstances
11782       assert self.op.remote_node is None, \
11783         "Evacuating primary instances requires iallocator"
11784     elif self.op.mode == constants.NODE_EVAC_SEC:
11785       # Secondary instances only
11786       inst_fn = _GetNodeSecondaryInstances
11787     else:
11788       # All instances
11789       assert self.op.mode == constants.NODE_EVAC_ALL
11790       inst_fn = _GetNodeInstances
11791       # TODO: In 2.6, change the iallocator interface to take an evacuation mode
11792       # per instance
11793       raise errors.OpPrereqError("Due to an issue with the iallocator"
11794                                  " interface it is not possible to evacuate"
11795                                  " all instances at once; specify explicitly"
11796                                  " whether to evacuate primary or secondary"
11797                                  " instances",
11798                                  errors.ECODE_INVAL)
11799
11800     return inst_fn(self.cfg, self.op.node_name)
11801
11802   def DeclareLocks(self, level):
11803     if level == locking.LEVEL_INSTANCE:
11804       # Lock instances optimistically, needs verification once node and group
11805       # locks have been acquired
11806       self.needed_locks[locking.LEVEL_INSTANCE] = \
11807         set(i.name for i in self._DetermineInstances())
11808
11809     elif level == locking.LEVEL_NODEGROUP:
11810       # Lock node groups for all potential target nodes optimistically, needs
11811       # verification once nodes have been acquired
11812       self.needed_locks[locking.LEVEL_NODEGROUP] = \
11813         self.cfg.GetNodeGroupsFromNodes(self.lock_nodes)
11814
11815     elif level == locking.LEVEL_NODE:
11816       self.needed_locks[locking.LEVEL_NODE] = self.lock_nodes
11817
11818   def CheckPrereq(self):
11819     # Verify locks
11820     owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
11821     owned_nodes = self.owned_locks(locking.LEVEL_NODE)
11822     owned_groups = self.owned_locks(locking.LEVEL_NODEGROUP)
11823
11824     need_nodes = self._DetermineNodes()
11825
11826     if not owned_nodes.issuperset(need_nodes):
11827       raise errors.OpPrereqError("Nodes in same group as '%s' changed since"
11828                                  " locks were acquired, current nodes are"
11829                                  " are '%s', used to be '%s'; retry the"
11830                                  " operation" %
11831                                  (self.op.node_name,
11832                                   utils.CommaJoin(need_nodes),
11833                                   utils.CommaJoin(owned_nodes)),
11834                                  errors.ECODE_STATE)
11835
11836     wanted_groups = self.cfg.GetNodeGroupsFromNodes(owned_nodes)
11837     if owned_groups != wanted_groups:
11838       raise errors.OpExecError("Node groups changed since locks were acquired,"
11839                                " current groups are '%s', used to be '%s';"
11840                                " retry the operation" %
11841                                (utils.CommaJoin(wanted_groups),
11842                                 utils.CommaJoin(owned_groups)))
11843
11844     # Determine affected instances
11845     self.instances = self._DetermineInstances()
11846     self.instance_names = [i.name for i in self.instances]
11847
11848     if set(self.instance_names) != owned_instances:
11849       raise errors.OpExecError("Instances on node '%s' changed since locks"
11850                                " were acquired, current instances are '%s',"
11851                                " used to be '%s'; retry the operation" %
11852                                (self.op.node_name,
11853                                 utils.CommaJoin(self.instance_names),
11854                                 utils.CommaJoin(owned_instances)))
11855
11856     if self.instance_names:
11857       self.LogInfo("Evacuating instances from node '%s': %s",
11858                    self.op.node_name,
11859                    utils.CommaJoin(utils.NiceSort(self.instance_names)))
11860     else:
11861       self.LogInfo("No instances to evacuate from node '%s'",
11862                    self.op.node_name)
11863
11864     if self.op.remote_node is not None:
11865       for i in self.instances:
11866         if i.primary_node == self.op.remote_node:
11867           raise errors.OpPrereqError("Node %s is the primary node of"
11868                                      " instance %s, cannot use it as"
11869                                      " secondary" %
11870                                      (self.op.remote_node, i.name),
11871                                      errors.ECODE_INVAL)
11872
11873   def Exec(self, feedback_fn):
11874     assert (self.op.iallocator is not None) ^ (self.op.remote_node is not None)
11875
11876     if not self.instance_names:
11877       # No instances to evacuate
11878       jobs = []
11879
11880     elif self.op.iallocator is not None:
11881       # TODO: Implement relocation to other group
11882       ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_NODE_EVAC,
11883                        evac_mode=self._MODE2IALLOCATOR[self.op.mode],
11884                        instances=list(self.instance_names))
11885
11886       ial.Run(self.op.iallocator)
11887
11888       if not ial.success:
11889         raise errors.OpPrereqError("Can't compute node evacuation using"
11890                                    " iallocator '%s': %s" %
11891                                    (self.op.iallocator, ial.info),
11892                                    errors.ECODE_NORES)
11893
11894       jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, True)
11895
11896     elif self.op.remote_node is not None:
11897       assert self.op.mode == constants.NODE_EVAC_SEC
11898       jobs = [
11899         [opcodes.OpInstanceReplaceDisks(instance_name=instance_name,
11900                                         remote_node=self.op.remote_node,
11901                                         disks=[],
11902                                         mode=constants.REPLACE_DISK_CHG,
11903                                         early_release=self.op.early_release)]
11904         for instance_name in self.instance_names
11905         ]
11906
11907     else:
11908       raise errors.ProgrammerError("No iallocator or remote node")
11909
11910     return ResultWithJobs(jobs)
11911
11912
11913 def _SetOpEarlyRelease(early_release, op):
11914   """Sets C{early_release} flag on opcodes if available.
11915
11916   """
11917   try:
11918     op.early_release = early_release
11919   except AttributeError:
11920     assert not isinstance(op, opcodes.OpInstanceReplaceDisks)
11921
11922   return op
11923
11924
11925 def _NodeEvacDest(use_nodes, group, nodes):
11926   """Returns group or nodes depending on caller's choice.
11927
11928   """
11929   if use_nodes:
11930     return utils.CommaJoin(nodes)
11931   else:
11932     return group
11933
11934
11935 def _LoadNodeEvacResult(lu, alloc_result, early_release, use_nodes):
11936   """Unpacks the result of change-group and node-evacuate iallocator requests.
11937
11938   Iallocator modes L{constants.IALLOCATOR_MODE_NODE_EVAC} and
11939   L{constants.IALLOCATOR_MODE_CHG_GROUP}.
11940
11941   @type lu: L{LogicalUnit}
11942   @param lu: Logical unit instance
11943   @type alloc_result: tuple/list
11944   @param alloc_result: Result from iallocator
11945   @type early_release: bool
11946   @param early_release: Whether to release locks early if possible
11947   @type use_nodes: bool
11948   @param use_nodes: Whether to display node names instead of groups
11949
11950   """
11951   (moved, failed, jobs) = alloc_result
11952
11953   if failed:
11954     failreason = utils.CommaJoin("%s (%s)" % (name, reason)
11955                                  for (name, reason) in failed)
11956     lu.LogWarning("Unable to evacuate instances %s", failreason)
11957     raise errors.OpExecError("Unable to evacuate instances %s" % failreason)
11958
11959   if moved:
11960     lu.LogInfo("Instances to be moved: %s",
11961                utils.CommaJoin("%s (to %s)" %
11962                                (name, _NodeEvacDest(use_nodes, group, nodes))
11963                                for (name, group, nodes) in moved))
11964
11965   return [map(compat.partial(_SetOpEarlyRelease, early_release),
11966               map(opcodes.OpCode.LoadOpCode, ops))
11967           for ops in jobs]
11968
11969
11970 class LUInstanceGrowDisk(LogicalUnit):
11971   """Grow a disk of an instance.
11972
11973   """
11974   HPATH = "disk-grow"
11975   HTYPE = constants.HTYPE_INSTANCE
11976   REQ_BGL = False
11977
11978   def ExpandNames(self):
11979     self._ExpandAndLockInstance()
11980     self.needed_locks[locking.LEVEL_NODE] = []
11981     self.needed_locks[locking.LEVEL_NODE_RES] = []
11982     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
11983     self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
11984
11985   def DeclareLocks(self, level):
11986     if level == locking.LEVEL_NODE:
11987       self._LockInstancesNodes()
11988     elif level == locking.LEVEL_NODE_RES:
11989       # Copy node locks
11990       self.needed_locks[locking.LEVEL_NODE_RES] = \
11991         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
11992
11993   def BuildHooksEnv(self):
11994     """Build hooks env.
11995
11996     This runs on the master, the primary and all the secondaries.
11997
11998     """
11999     env = {
12000       "DISK": self.op.disk,
12001       "AMOUNT": self.op.amount,
12002       "ABSOLUTE": self.op.absolute,
12003       }
12004     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
12005     return env
12006
12007   def BuildHooksNodes(self):
12008     """Build hooks nodes.
12009
12010     """
12011     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
12012     return (nl, nl)
12013
12014   def CheckPrereq(self):
12015     """Check prerequisites.
12016
12017     This checks that the instance is in the cluster.
12018
12019     """
12020     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
12021     assert instance is not None, \
12022       "Cannot retrieve locked instance %s" % self.op.instance_name
12023     nodenames = list(instance.all_nodes)
12024     for node in nodenames:
12025       _CheckNodeOnline(self, node)
12026
12027     self.instance = instance
12028
12029     if instance.disk_template not in constants.DTS_GROWABLE:
12030       raise errors.OpPrereqError("Instance's disk layout does not support"
12031                                  " growing", errors.ECODE_INVAL)
12032
12033     self.disk = instance.FindDisk(self.op.disk)
12034
12035     if self.op.absolute:
12036       self.target = self.op.amount
12037       self.delta = self.target - self.disk.size
12038       if self.delta < 0:
12039         raise errors.OpPrereqError("Requested size (%s) is smaller than "
12040                                    "current disk size (%s)" %
12041                                    (utils.FormatUnit(self.target, "h"),
12042                                     utils.FormatUnit(self.disk.size, "h")),
12043                                    errors.ECODE_STATE)
12044     else:
12045       self.delta = self.op.amount
12046       self.target = self.disk.size + self.delta
12047       if self.delta < 0:
12048         raise errors.OpPrereqError("Requested increment (%s) is negative" %
12049                                    utils.FormatUnit(self.delta, "h"),
12050                                    errors.ECODE_INVAL)
12051
12052     if instance.disk_template not in (constants.DT_FILE,
12053                                       constants.DT_SHARED_FILE,
12054                                       constants.DT_RBD,
12055                                       constants.DT_EXT):
12056       # TODO: check the free disk space for file, when that feature will be
12057       # supported
12058       _CheckNodesFreeDiskPerVG(self, nodenames,
12059                                self.disk.ComputeGrowth(self.delta))
12060
12061   def Exec(self, feedback_fn):
12062     """Execute disk grow.
12063
12064     """
12065     instance = self.instance
12066     disk = self.disk
12067
12068     assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
12069     assert (self.owned_locks(locking.LEVEL_NODE) ==
12070             self.owned_locks(locking.LEVEL_NODE_RES))
12071
12072     disks_ok, _ = _AssembleInstanceDisks(self, self.instance, disks=[disk])
12073     if not disks_ok:
12074       raise errors.OpExecError("Cannot activate block device to grow")
12075
12076     feedback_fn("Growing disk %s of instance '%s' by %s to %s" %
12077                 (self.op.disk, instance.name,
12078                  utils.FormatUnit(self.delta, "h"),
12079                  utils.FormatUnit(self.target, "h")))
12080
12081     # First run all grow ops in dry-run mode
12082     for node in instance.all_nodes:
12083       self.cfg.SetDiskID(disk, node)
12084       result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
12085                                            True)
12086       result.Raise("Grow request failed to node %s" % node)
12087
12088     # We know that (as far as we can test) operations across different
12089     # nodes will succeed, time to run it for real
12090     for node in instance.all_nodes:
12091       self.cfg.SetDiskID(disk, node)
12092       result = self.rpc.call_blockdev_grow(node, (disk, instance), self.delta,
12093                                            False)
12094       result.Raise("Grow request failed to node %s" % node)
12095
12096       # TODO: Rewrite code to work properly
12097       # DRBD goes into sync mode for a short amount of time after executing the
12098       # "resize" command. DRBD 8.x below version 8.0.13 contains a bug whereby
12099       # calling "resize" in sync mode fails. Sleeping for a short amount of
12100       # time is a work-around.
12101       time.sleep(5)
12102
12103     disk.RecordGrow(self.delta)
12104     self.cfg.Update(instance, feedback_fn)
12105
12106     # Changes have been recorded, release node lock
12107     _ReleaseLocks(self, locking.LEVEL_NODE)
12108
12109     # Downgrade lock while waiting for sync
12110     self.glm.downgrade(locking.LEVEL_INSTANCE)
12111
12112     if self.op.wait_for_sync:
12113       disk_abort = not _WaitForSync(self, instance, disks=[disk])
12114       if disk_abort:
12115         self.proc.LogWarning("Disk sync-ing has not returned a good"
12116                              " status; please check the instance")
12117       if instance.admin_state != constants.ADMINST_UP:
12118         _SafeShutdownInstanceDisks(self, instance, disks=[disk])
12119     elif instance.admin_state != constants.ADMINST_UP:
12120       self.proc.LogWarning("Not shutting down the disk even if the instance is"
12121                            " not supposed to be running because no wait for"
12122                            " sync mode was requested")
12123
12124     assert self.owned_locks(locking.LEVEL_NODE_RES)
12125     assert set([instance.name]) == self.owned_locks(locking.LEVEL_INSTANCE)
12126
12127
12128 class LUInstanceQueryData(NoHooksLU):
12129   """Query runtime instance data.
12130
12131   """
12132   REQ_BGL = False
12133
12134   def ExpandNames(self):
12135     self.needed_locks = {}
12136
12137     # Use locking if requested or when non-static information is wanted
12138     if not (self.op.static or self.op.use_locking):
12139       self.LogWarning("Non-static data requested, locks need to be acquired")
12140       self.op.use_locking = True
12141
12142     if self.op.instances or not self.op.use_locking:
12143       # Expand instance names right here
12144       self.wanted_names = _GetWantedInstances(self, self.op.instances)
12145     else:
12146       # Will use acquired locks
12147       self.wanted_names = None
12148
12149     if self.op.use_locking:
12150       self.share_locks = _ShareAll()
12151
12152       if self.wanted_names is None:
12153         self.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET
12154       else:
12155         self.needed_locks[locking.LEVEL_INSTANCE] = self.wanted_names
12156
12157       self.needed_locks[locking.LEVEL_NODEGROUP] = []
12158       self.needed_locks[locking.LEVEL_NODE] = []
12159       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
12160
12161   def DeclareLocks(self, level):
12162     if self.op.use_locking:
12163       if level == locking.LEVEL_NODEGROUP:
12164         owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
12165
12166         # Lock all groups used by instances optimistically; this requires going
12167         # via the node before it's locked, requiring verification later on
12168         self.needed_locks[locking.LEVEL_NODEGROUP] = \
12169           frozenset(group_uuid
12170                     for instance_name in owned_instances
12171                     for group_uuid in
12172                       self.cfg.GetInstanceNodeGroups(instance_name))
12173
12174       elif level == locking.LEVEL_NODE:
12175         self._LockInstancesNodes()
12176
12177   def CheckPrereq(self):
12178     """Check prerequisites.
12179
12180     This only checks the optional instance list against the existing names.
12181
12182     """
12183     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
12184     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
12185     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
12186
12187     if self.wanted_names is None:
12188       assert self.op.use_locking, "Locking was not used"
12189       self.wanted_names = owned_instances
12190
12191     instances = dict(self.cfg.GetMultiInstanceInfo(self.wanted_names))
12192
12193     if self.op.use_locking:
12194       _CheckInstancesNodeGroups(self.cfg, instances, owned_groups, owned_nodes,
12195                                 None)
12196     else:
12197       assert not (owned_instances or owned_groups or owned_nodes)
12198
12199     self.wanted_instances = instances.values()
12200
12201   def _ComputeBlockdevStatus(self, node, instance, dev):
12202     """Returns the status of a block device
12203
12204     """
12205     if self.op.static or not node:
12206       return None
12207
12208     self.cfg.SetDiskID(dev, node)
12209
12210     result = self.rpc.call_blockdev_find(node, dev)
12211     if result.offline:
12212       return None
12213
12214     result.Raise("Can't compute disk status for %s" % instance.name)
12215
12216     status = result.payload
12217     if status is None:
12218       return None
12219
12220     return (status.dev_path, status.major, status.minor,
12221             status.sync_percent, status.estimated_time,
12222             status.is_degraded, status.ldisk_status)
12223
12224   def _ComputeDiskStatus(self, instance, snode, dev):
12225     """Compute block device status.
12226
12227     """
12228     (anno_dev,) = _AnnotateDiskParams(instance, [dev], self.cfg)
12229
12230     return self._ComputeDiskStatusInner(instance, snode, anno_dev)
12231
12232   def _ComputeDiskStatusInner(self, instance, snode, dev):
12233     """Compute block device status.
12234
12235     @attention: The device has to be annotated already.
12236
12237     """
12238     if dev.dev_type in constants.LDS_DRBD:
12239       # we change the snode then (otherwise we use the one passed in)
12240       if dev.logical_id[0] == instance.primary_node:
12241         snode = dev.logical_id[1]
12242       else:
12243         snode = dev.logical_id[0]
12244
12245     dev_pstatus = self._ComputeBlockdevStatus(instance.primary_node,
12246                                               instance, dev)
12247     dev_sstatus = self._ComputeBlockdevStatus(snode, instance, dev)
12248
12249     if dev.children:
12250       dev_children = map(compat.partial(self._ComputeDiskStatusInner,
12251                                         instance, snode),
12252                          dev.children)
12253     else:
12254       dev_children = []
12255
12256     return {
12257       "iv_name": dev.iv_name,
12258       "dev_type": dev.dev_type,
12259       "logical_id": dev.logical_id,
12260       "physical_id": dev.physical_id,
12261       "pstatus": dev_pstatus,
12262       "sstatus": dev_sstatus,
12263       "children": dev_children,
12264       "mode": dev.mode,
12265       "size": dev.size,
12266       }
12267
12268   def Exec(self, feedback_fn):
12269     """Gather and return data"""
12270     result = {}
12271
12272     cluster = self.cfg.GetClusterInfo()
12273
12274     node_names = itertools.chain(*(i.all_nodes for i in self.wanted_instances))
12275     nodes = dict(self.cfg.GetMultiNodeInfo(node_names))
12276
12277     groups = dict(self.cfg.GetMultiNodeGroupInfo(node.group
12278                                                  for node in nodes.values()))
12279
12280     group2name_fn = lambda uuid: groups[uuid].name
12281
12282     for instance in self.wanted_instances:
12283       pnode = nodes[instance.primary_node]
12284
12285       if self.op.static or pnode.offline:
12286         remote_state = None
12287         if pnode.offline:
12288           self.LogWarning("Primary node %s is marked offline, returning static"
12289                           " information only for instance %s" %
12290                           (pnode.name, instance.name))
12291       else:
12292         remote_info = self.rpc.call_instance_info(instance.primary_node,
12293                                                   instance.name,
12294                                                   instance.hypervisor)
12295         remote_info.Raise("Error checking node %s" % instance.primary_node)
12296         remote_info = remote_info.payload
12297         if remote_info and "state" in remote_info:
12298           remote_state = "up"
12299         else:
12300           if instance.admin_state == constants.ADMINST_UP:
12301             remote_state = "down"
12302           else:
12303             remote_state = instance.admin_state
12304
12305       disks = map(compat.partial(self._ComputeDiskStatus, instance, None),
12306                   instance.disks)
12307
12308       snodes_group_uuids = [nodes[snode_name].group
12309                             for snode_name in instance.secondary_nodes]
12310
12311       result[instance.name] = {
12312         "name": instance.name,
12313         "config_state": instance.admin_state,
12314         "run_state": remote_state,
12315         "pnode": instance.primary_node,
12316         "pnode_group_uuid": pnode.group,
12317         "pnode_group_name": group2name_fn(pnode.group),
12318         "snodes": instance.secondary_nodes,
12319         "snodes_group_uuids": snodes_group_uuids,
12320         "snodes_group_names": map(group2name_fn, snodes_group_uuids),
12321         "os": instance.os,
12322         # this happens to be the same format used for hooks
12323         "nics": _NICListToTuple(self, instance.nics),
12324         "disk_template": instance.disk_template,
12325         "disks": disks,
12326         "hypervisor": instance.hypervisor,
12327         "network_port": instance.network_port,
12328         "hv_instance": instance.hvparams,
12329         "hv_actual": cluster.FillHV(instance, skip_globals=True),
12330         "be_instance": instance.beparams,
12331         "be_actual": cluster.FillBE(instance),
12332         "os_instance": instance.osparams,
12333         "os_actual": cluster.SimpleFillOS(instance.os, instance.osparams),
12334         "serial_no": instance.serial_no,
12335         "mtime": instance.mtime,
12336         "ctime": instance.ctime,
12337         "uuid": instance.uuid,
12338         }
12339
12340     return result
12341
12342
12343 def PrepareContainerMods(mods, private_fn):
12344   """Prepares a list of container modifications by adding a private data field.
12345
12346   @type mods: list of tuples; (operation, index, parameters)
12347   @param mods: List of modifications
12348   @type private_fn: callable or None
12349   @param private_fn: Callable for constructing a private data field for a
12350     modification
12351   @rtype: list
12352
12353   """
12354   if private_fn is None:
12355     fn = lambda: None
12356   else:
12357     fn = private_fn
12358
12359   return [(op, idx, params, fn()) for (op, idx, params) in mods]
12360
12361
12362 #: Type description for changes as returned by L{ApplyContainerMods}'s
12363 #: callbacks
12364 _TApplyContModsCbChanges = \
12365   ht.TMaybeListOf(ht.TAnd(ht.TIsLength(2), ht.TItems([
12366     ht.TNonEmptyString,
12367     ht.TAny,
12368     ])))
12369
12370
12371 def ApplyContainerMods(kind, container, chgdesc, mods,
12372                        create_fn, modify_fn, remove_fn):
12373   """Applies descriptions in C{mods} to C{container}.
12374
12375   @type kind: string
12376   @param kind: One-word item description
12377   @type container: list
12378   @param container: Container to modify
12379   @type chgdesc: None or list
12380   @param chgdesc: List of applied changes
12381   @type mods: list
12382   @param mods: Modifications as returned by L{PrepareContainerMods}
12383   @type create_fn: callable
12384   @param create_fn: Callback for creating a new item (L{constants.DDM_ADD});
12385     receives absolute item index, parameters and private data object as added
12386     by L{PrepareContainerMods}, returns tuple containing new item and changes
12387     as list
12388   @type modify_fn: callable
12389   @param modify_fn: Callback for modifying an existing item
12390     (L{constants.DDM_MODIFY}); receives absolute item index, item, parameters
12391     and private data object as added by L{PrepareContainerMods}, returns
12392     changes as list
12393   @type remove_fn: callable
12394   @param remove_fn: Callback on removing item; receives absolute item index,
12395     item and private data object as added by L{PrepareContainerMods}
12396
12397   """
12398   for (op, idx, params, private) in mods:
12399     if idx == -1:
12400       # Append
12401       absidx = len(container) - 1
12402     elif idx < 0:
12403       raise IndexError("Not accepting negative indices other than -1")
12404     elif idx > len(container):
12405       raise IndexError("Got %s index %s, but there are only %s" %
12406                        (kind, idx, len(container)))
12407     else:
12408       absidx = idx
12409
12410     changes = None
12411
12412     if op == constants.DDM_ADD:
12413       # Calculate where item will be added
12414       if idx == -1:
12415         addidx = len(container)
12416       else:
12417         addidx = idx
12418
12419       if create_fn is None:
12420         item = params
12421       else:
12422         (item, changes) = create_fn(addidx, params, private)
12423
12424       if idx == -1:
12425         container.append(item)
12426       else:
12427         assert idx >= 0
12428         assert idx <= len(container)
12429         # list.insert does so before the specified index
12430         container.insert(idx, item)
12431     else:
12432       # Retrieve existing item
12433       try:
12434         item = container[absidx]
12435       except IndexError:
12436         raise IndexError("Invalid %s index %s" % (kind, idx))
12437
12438       if op == constants.DDM_REMOVE:
12439         assert not params
12440
12441         if remove_fn is not None:
12442           remove_fn(absidx, item, private)
12443
12444         #TODO: include a hotplugged msg in changes
12445         changes = [("%s/%s" % (kind, absidx), "remove")]
12446
12447         assert container[absidx] == item
12448         del container[absidx]
12449       elif op == constants.DDM_MODIFY:
12450         if modify_fn is not None:
12451           #TODO: include a hotplugged msg in changes
12452           changes = modify_fn(absidx, item, params, private)
12453
12454       else:
12455         raise errors.ProgrammerError("Unhandled operation '%s'" % op)
12456
12457     assert _TApplyContModsCbChanges(changes)
12458
12459     if not (chgdesc is None or changes is None):
12460       chgdesc.extend(changes)
12461
12462
12463 def _UpdateIvNames(base_index, disks):
12464   """Updates the C{iv_name} attribute of disks.
12465
12466   @type disks: list of L{objects.Disk}
12467
12468   """
12469   for (idx, disk) in enumerate(disks):
12470     disk.iv_name = "disk/%s" % (base_index + idx, )
12471
12472
12473 class _InstNicModPrivate:
12474   """Data structure for network interface modifications.
12475
12476   Used by L{LUInstanceSetParams}.
12477
12478   """
12479   def __init__(self):
12480     self.params = None
12481     self.filled = None
12482
12483
12484 class LUInstanceSetParams(LogicalUnit):
12485   """Modifies an instances's parameters.
12486
12487   """
12488   HPATH = "instance-modify"
12489   HTYPE = constants.HTYPE_INSTANCE
12490   REQ_BGL = False
12491
12492   @staticmethod
12493   def _UpgradeDiskNicMods(kind, mods, verify_fn):
12494     assert ht.TList(mods)
12495     assert not mods or len(mods[0]) in (2, 3)
12496
12497     if mods and len(mods[0]) == 2:
12498       result = []
12499
12500       addremove = 0
12501       for op, params in mods:
12502         if op in (constants.DDM_ADD, constants.DDM_REMOVE):
12503           result.append((op, -1, params))
12504           addremove += 1
12505
12506           if addremove > 1:
12507             raise errors.OpPrereqError("Only one %s add or remove operation is"
12508                                        " supported at a time" % kind,
12509                                        errors.ECODE_INVAL)
12510         else:
12511           result.append((constants.DDM_MODIFY, op, params))
12512
12513       assert verify_fn(result)
12514     else:
12515       result = mods
12516
12517     return result
12518
12519   @staticmethod
12520   def _CheckMods(kind, mods, key_types, item_fn):
12521     """Ensures requested disk/NIC modifications are valid.
12522
12523     """
12524     for (op, _, params) in mods:
12525       assert ht.TDict(params)
12526
12527       # If key_types is an empty dict, we assume we have an 'ext' template
12528       # and thus do not ForceDictType
12529       if key_types:
12530         utils.ForceDictType(params, key_types)
12531
12532       if op == constants.DDM_REMOVE:
12533         if params:
12534           raise errors.OpPrereqError("No settings should be passed when"
12535                                      " removing a %s" % kind,
12536                                      errors.ECODE_INVAL)
12537       elif op in (constants.DDM_ADD, constants.DDM_MODIFY):
12538         item_fn(op, params)
12539       else:
12540         raise errors.ProgrammerError("Unhandled operation '%s'" % op)
12541
12542   @staticmethod
12543   def _VerifyDiskModification(op, params):
12544     """Verifies a disk modification.
12545
12546     """
12547     if op == constants.DDM_ADD:
12548       mode = params.setdefault(constants.IDISK_MODE, constants.DISK_RDWR)
12549       if mode not in constants.DISK_ACCESS_SET:
12550         raise errors.OpPrereqError("Invalid disk access mode '%s'" % mode,
12551                                    errors.ECODE_INVAL)
12552
12553       size = params.get(constants.IDISK_SIZE, None)
12554       if size is None:
12555         raise errors.OpPrereqError("Required disk parameter '%s' missing" %
12556                                    constants.IDISK_SIZE, errors.ECODE_INVAL)
12557
12558       try:
12559         size = int(size)
12560       except (TypeError, ValueError), err:
12561         raise errors.OpPrereqError("Invalid disk size parameter: %s" % err,
12562                                    errors.ECODE_INVAL)
12563
12564       params[constants.IDISK_SIZE] = size
12565
12566     elif op == constants.DDM_MODIFY:
12567       if constants.IDISK_SIZE in params:
12568         raise errors.OpPrereqError("Disk size change not possible, use"
12569                                    " grow-disk", errors.ECODE_INVAL)
12570       if constants.IDISK_MODE not in params:
12571         raise errors.OpPrereqError("Disk 'mode' is the only kind of"
12572                                    " modification supported, but missing",
12573                                    errors.ECODE_NOENT)
12574       if len(params) > 1:
12575         raise errors.OpPrereqError("Disk modification doesn't support"
12576                                    " additional arbitrary parameters",
12577                                    errors.ECODE_INVAL)
12578
12579   @staticmethod
12580   def _VerifyNicModification(op, params):
12581     """Verifies a network interface modification.
12582
12583     """
12584     if op in (constants.DDM_ADD, constants.DDM_MODIFY):
12585       ip = params.get(constants.INIC_IP, None)
12586       req_net = params.get(constants.INIC_NETWORK, None)
12587       link = params.get(constants.NIC_LINK, None)
12588       mode = params.get(constants.NIC_MODE, None)
12589       if req_net is not None:
12590         if req_net.lower() == constants.VALUE_NONE:
12591           params[constants.INIC_NETWORK] = None
12592           req_net = None
12593         elif link is not None or mode is not None:
12594           raise errors.OpPrereqError("If network is given"
12595                                      " mode or link should not",
12596                                      errors.ECODE_INVAL)
12597
12598       if op == constants.DDM_ADD:
12599         macaddr = params.get(constants.INIC_MAC, None)
12600         if macaddr is None:
12601           params[constants.INIC_MAC] = constants.VALUE_AUTO
12602
12603       if ip is not None:
12604         if ip.lower() == constants.VALUE_NONE:
12605           params[constants.INIC_IP] = None
12606         else:
12607           if ip.lower() == constants.NIC_IP_POOL:
12608             if op == constants.DDM_ADD and req_net is None:
12609               raise errors.OpPrereqError("If ip=pool, parameter network"
12610                                          " cannot be none",
12611                                          errors.ECODE_INVAL)
12612           else:
12613             if not netutils.IPAddress.IsValid(ip):
12614               raise errors.OpPrereqError("Invalid IP address '%s'" % ip,
12615                                          errors.ECODE_INVAL)
12616
12617       if constants.INIC_MAC in params:
12618         macaddr = params[constants.INIC_MAC]
12619         if macaddr not in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
12620           macaddr = utils.NormalizeAndValidateMac(macaddr)
12621
12622         if op == constants.DDM_MODIFY and macaddr == constants.VALUE_AUTO:
12623           raise errors.OpPrereqError("'auto' is not a valid MAC address when"
12624                                      " modifying an existing NIC",
12625                                      errors.ECODE_INVAL)
12626
12627   def CheckArguments(self):
12628     if not (self.op.nics or self.op.disks or self.op.disk_template or
12629             self.op.hvparams or self.op.beparams or self.op.os_name or
12630             self.op.offline is not None or self.op.runtime_mem):
12631       raise errors.OpPrereqError("No changes submitted", errors.ECODE_INVAL)
12632
12633     if self.op.hvparams:
12634       _CheckGlobalHvParams(self.op.hvparams)
12635
12636     if self.op.allow_arbit_params:
12637       self.op.disks = \
12638         self._UpgradeDiskNicMods("disk", self.op.disks,
12639           opcodes.OpInstanceSetParams.TestExtDiskModifications)
12640     else:
12641       self.op.disks = \
12642         self._UpgradeDiskNicMods("disk", self.op.disks,
12643           opcodes.OpInstanceSetParams.TestDiskModifications)
12644
12645     self.op.nics = \
12646       self._UpgradeDiskNicMods("NIC", self.op.nics,
12647         opcodes.OpInstanceSetParams.TestNicModifications)
12648
12649     # Check disk modifications
12650     if self.op.allow_arbit_params:
12651       self._CheckMods("disk", self.op.disks, {},
12652                       self._VerifyDiskModification)
12653     else:
12654       self._CheckMods("disk", self.op.disks, constants.IDISK_PARAMS_TYPES,
12655                       self._VerifyDiskModification)
12656
12657     if self.op.disks and self.op.disk_template is not None:
12658       raise errors.OpPrereqError("Disk template conversion and other disk"
12659                                  " changes not supported at the same time",
12660                                  errors.ECODE_INVAL)
12661
12662     if (self.op.disk_template and
12663         self.op.disk_template in constants.DTS_INT_MIRROR and
12664         self.op.remote_node is None):
12665       raise errors.OpPrereqError("Changing the disk template to a mirrored"
12666                                  " one requires specifying a secondary node",
12667                                  errors.ECODE_INVAL)
12668
12669     # Check NIC modifications
12670     self._CheckMods("NIC", self.op.nics, constants.INIC_PARAMS_TYPES,
12671                     self._VerifyNicModification)
12672
12673   def ExpandNames(self):
12674     self._ExpandAndLockInstance()
12675     # Can't even acquire node locks in shared mode as upcoming changes in
12676     # Ganeti 2.6 will start to modify the node object on disk conversion
12677     self.needed_locks[locking.LEVEL_NODE] = []
12678     self.needed_locks[locking.LEVEL_NODE_RES] = []
12679     self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
12680
12681   def DeclareLocks(self, level):
12682     # TODO: Acquire group lock in shared mode (disk parameters)
12683     if level == locking.LEVEL_NODE:
12684       self._LockInstancesNodes()
12685       if self.op.disk_template and self.op.remote_node:
12686         self.op.remote_node = _ExpandNodeName(self.cfg, self.op.remote_node)
12687         self.needed_locks[locking.LEVEL_NODE].append(self.op.remote_node)
12688     elif level == locking.LEVEL_NODE_RES and self.op.disk_template:
12689       # Copy node locks
12690       self.needed_locks[locking.LEVEL_NODE_RES] = \
12691         _CopyLockList(self.needed_locks[locking.LEVEL_NODE])
12692
12693   def BuildHooksEnv(self):
12694     """Build hooks env.
12695
12696     This runs on the master, primary and secondaries.
12697
12698     """
12699     args = dict()
12700     if constants.BE_MINMEM in self.be_new:
12701       args["minmem"] = self.be_new[constants.BE_MINMEM]
12702     if constants.BE_MAXMEM in self.be_new:
12703       args["maxmem"] = self.be_new[constants.BE_MAXMEM]
12704     if constants.BE_VCPUS in self.be_new:
12705       args["vcpus"] = self.be_new[constants.BE_VCPUS]
12706     # TODO: export disk changes. Note: _BuildInstanceHookEnv* don't export disk
12707     # information at all.
12708
12709     if self._new_nics is not None:
12710       nics = []
12711
12712       for nic in self._new_nics:
12713         n = copy.deepcopy(nic)
12714         nicparams = self.cluster.SimpleFillNIC(n.nicparams)
12715         n.nicparams = nicparams
12716         nics.append(_NICToTuple(self, n))
12717
12718       args["nics"] = nics
12719
12720     env = _BuildInstanceHookEnvByObject(self, self.instance, override=args)
12721     if self.op.disk_template:
12722       env["NEW_DISK_TEMPLATE"] = self.op.disk_template
12723     if self.op.runtime_mem:
12724       env["RUNTIME_MEMORY"] = self.op.runtime_mem
12725
12726     return env
12727
12728   def BuildHooksNodes(self):
12729     """Build hooks nodes.
12730
12731     """
12732     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
12733     return (nl, nl)
12734
12735   def _PrepareNicModification(self, params, private, old_ip, old_net,
12736                               old_params, cluster, pnode):
12737
12738     update_params_dict = dict([(key, params[key])
12739                                for key in constants.NICS_PARAMETERS
12740                                if key in params])
12741
12742     req_link = update_params_dict.get(constants.NIC_LINK, None)
12743     req_mode = update_params_dict.get(constants.NIC_MODE, None)
12744
12745     new_net = params.get(constants.INIC_NETWORK, old_net)
12746     if new_net is not None:
12747       netparams = self.cfg.GetGroupNetParams(new_net, pnode)
12748       if netparams is None:
12749         raise errors.OpPrereqError("No netparams found for the network"
12750                                    " %s, propably not connected." % new_net,
12751                                    errors.ECODE_INVAL)
12752       new_params = dict(netparams)
12753     else:
12754       new_params = _GetUpdatedParams(old_params, update_params_dict)
12755
12756     utils.ForceDictType(new_params, constants.NICS_PARAMETER_TYPES)
12757
12758     new_filled_params = cluster.SimpleFillNIC(new_params)
12759     objects.NIC.CheckParameterSyntax(new_filled_params)
12760
12761     new_mode = new_filled_params[constants.NIC_MODE]
12762     if new_mode == constants.NIC_MODE_BRIDGED:
12763       bridge = new_filled_params[constants.NIC_LINK]
12764       msg = self.rpc.call_bridges_exist(pnode, [bridge]).fail_msg
12765       if msg:
12766         msg = "Error checking bridges on node '%s': %s" % (pnode, msg)
12767         if self.op.force:
12768           self.warn.append(msg)
12769         else:
12770           raise errors.OpPrereqError(msg, errors.ECODE_ENVIRON)
12771
12772     elif new_mode == constants.NIC_MODE_ROUTED:
12773       ip = params.get(constants.INIC_IP, old_ip)
12774       if ip is None:
12775         raise errors.OpPrereqError("Cannot set the NIC IP address to None"
12776                                    " on a routed NIC", errors.ECODE_INVAL)
12777
12778     if constants.INIC_MAC in params:
12779       mac = params[constants.INIC_MAC]
12780       if mac is None:
12781         raise errors.OpPrereqError("Cannot unset the NIC MAC address",
12782                                    errors.ECODE_INVAL)
12783       elif mac in (constants.VALUE_AUTO, constants.VALUE_GENERATE):
12784         # otherwise generate the MAC address
12785         params[constants.INIC_MAC] = \
12786           self.cfg.GenerateMAC(new_net, self.proc.GetECId())
12787       else:
12788         # or validate/reserve the current one
12789         try:
12790           self.cfg.ReserveMAC(mac, self.proc.GetECId())
12791         except errors.ReservationError:
12792           raise errors.OpPrereqError("MAC address '%s' already in use"
12793                                      " in cluster" % mac,
12794                                      errors.ECODE_NOTUNIQUE)
12795     elif new_net != old_net:
12796       def get_net_prefix(net):
12797         if net:
12798           uuid = self.cfg.LookupNetwork(net)
12799           if uuid:
12800             nobj = self.cfg.GetNetwork(uuid)
12801             return nobj.mac_prefix
12802         return None
12803       new_prefix = get_net_prefix(new_net)
12804       old_prefix = get_net_prefix(old_net)
12805       if old_prefix != new_prefix:
12806         params[constants.INIC_MAC] = \
12807           self.cfg.GenerateMAC(new_net, self.proc.GetECId())
12808
12809     #if there is a change in nic-network configuration
12810     new_ip = params.get(constants.INIC_IP, old_ip)
12811     if (new_ip, new_net) != (old_ip, old_net):
12812       if new_ip:
12813         if new_net:
12814           if new_ip.lower() == constants.NIC_IP_POOL:
12815             try:
12816               new_ip = self.cfg.GenerateIp(new_net, self.proc.GetECId())
12817             except errors.ReservationError:
12818               raise errors.OpPrereqError("Unable to get a free IP"
12819                                         " from the address pool",
12820                                          errors.ECODE_STATE)
12821             self.LogInfo("Chose IP %s from pool %s", new_ip, new_net)
12822             params[constants.INIC_IP] = new_ip
12823           elif new_ip != old_ip or new_net != old_net:
12824             try:
12825               self.LogInfo("Reserving IP %s in pool %s", new_ip, new_net)
12826               self.cfg.ReserveIp(new_net, new_ip, self.proc.GetECId())
12827             except errors.ReservationError:
12828               raise errors.OpPrereqError("IP %s not available in network %s" %
12829                                          (new_ip, new_net),
12830                                          errors.ECODE_NOTUNIQUE)
12831         elif new_ip.lower() == constants.NIC_IP_POOL:
12832           raise errors.OpPrereqError("ip=pool, but no network found",
12833                                      ECODEE_INVAL)
12834         else:
12835           # new net is None
12836           if self.op.conflicts_check:
12837             _CheckForConflictingIp(self, new_ip, pnode)
12838
12839       if old_ip:
12840         if old_net:
12841           try:
12842             self.cfg.ReleaseIp(old_net, old_ip, self.proc.GetECId())
12843           except errors.AddressPoolError:
12844             logging.warning("Release IP %s not contained in network %s",
12845                             old_ip, old_net)
12846
12847     # there are no changes in (net, ip) tuple
12848     elif (old_net is not None and
12849           (req_link is not None or req_mode is not None)):
12850       raise errors.OpPrereqError("Not allowed to change link or mode of"
12851                                  " a NIC that is connected to a network.",
12852                                  errors.ECODE_INVAL)
12853
12854     logging.info("new_params %s", new_params)
12855     logging.info("new_filled_params %s", new_filled_params)
12856     private.params = new_params
12857     private.filled = new_filled_params
12858
12859   def CheckPrereq(self):
12860     """Check prerequisites.
12861
12862     This only checks the instance list against the existing names.
12863
12864     """
12865     # checking the new params on the primary/secondary nodes
12866
12867     instance = self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
12868     cluster = self.cluster = self.cfg.GetClusterInfo()
12869     assert self.instance is not None, \
12870       "Cannot retrieve locked instance %s" % self.op.instance_name
12871     pnode = instance.primary_node
12872     nodelist = list(instance.all_nodes)
12873     pnode_info = self.cfg.GetNodeInfo(pnode)
12874     self.diskparams = self.cfg.GetInstanceDiskParams(instance)
12875
12876     # Prepare disk/NIC modifications
12877     self.diskmod = PrepareContainerMods(self.op.disks, None)
12878     self.nicmod = PrepareContainerMods(self.op.nics, _InstNicModPrivate)
12879     logging.info("nicmod %s", self.nicmod)
12880
12881     # Check the validity of the `provider' parameter
12882     if instance.disk_template in constants.DT_EXT:
12883       for mod in self.diskmod:
12884         ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
12885         if mod[0] == constants.DDM_ADD:
12886           if ext_provider is None:
12887             raise errors.OpPrereqError("Instance template is '%s' and parameter"
12888                                        " '%s' missing, during disk add" %
12889                                        (constants.DT_EXT,
12890                                         constants.IDISK_PROVIDER),
12891                                        errors.ECODE_NOENT)
12892         elif mod[0] == constants.DDM_MODIFY:
12893           if ext_provider:
12894             raise errors.OpPrereqError("Parameter '%s' is invalid during disk"
12895                                        " modification" % constants.IDISK_PROVIDER,
12896                                        errors.ECODE_INVAL)
12897     else:
12898       for mod in self.diskmod:
12899         ext_provider = mod[2].get(constants.IDISK_PROVIDER, None)
12900         if ext_provider is not None:
12901           raise errors.OpPrereqError("Parameter '%s' is only valid for instances"
12902                                      " of type '%s'" % (constants.IDISK_PROVIDER,
12903                                       constants.DT_EXT), errors.ECODE_INVAL)
12904
12905     # OS change
12906     if self.op.os_name and not self.op.force:
12907       _CheckNodeHasOS(self, instance.primary_node, self.op.os_name,
12908                       self.op.force_variant)
12909       instance_os = self.op.os_name
12910     else:
12911       instance_os = instance.os
12912
12913     assert not (self.op.disk_template and self.op.disks), \
12914       "Can't modify disk template and apply disk changes at the same time"
12915
12916     if self.op.disk_template:
12917       if instance.disk_template == self.op.disk_template:
12918         raise errors.OpPrereqError("Instance already has disk template %s" %
12919                                    instance.disk_template, errors.ECODE_INVAL)
12920
12921       if (instance.disk_template,
12922           self.op.disk_template) not in self._DISK_CONVERSIONS:
12923         raise errors.OpPrereqError("Unsupported disk template conversion from"
12924                                    " %s to %s" % (instance.disk_template,
12925                                                   self.op.disk_template),
12926                                    errors.ECODE_INVAL)
12927       _CheckInstanceState(self, instance, INSTANCE_DOWN,
12928                           msg="cannot change disk template")
12929       if self.op.disk_template in constants.DTS_INT_MIRROR:
12930         if self.op.remote_node == pnode:
12931           raise errors.OpPrereqError("Given new secondary node %s is the same"
12932                                      " as the primary node of the instance" %
12933                                      self.op.remote_node, errors.ECODE_STATE)
12934         _CheckNodeOnline(self, self.op.remote_node)
12935         _CheckNodeNotDrained(self, self.op.remote_node)
12936         # FIXME: here we assume that the old instance type is DT_PLAIN
12937         assert instance.disk_template == constants.DT_PLAIN
12938         disks = [{constants.IDISK_SIZE: d.size,
12939                   constants.IDISK_VG: d.logical_id[0]}
12940                  for d in instance.disks]
12941         required = _ComputeDiskSizePerVG(self.op.disk_template, disks)
12942         _CheckNodesFreeDiskPerVG(self, [self.op.remote_node], required)
12943
12944         snode_info = self.cfg.GetNodeInfo(self.op.remote_node)
12945         snode_group = self.cfg.GetNodeGroup(snode_info.group)
12946         ipolicy = _CalculateGroupIPolicy(cluster, snode_group)
12947         _CheckTargetNodeIPolicy(self, ipolicy, instance, snode_info,
12948                                 ignore=self.op.ignore_ipolicy)
12949         if pnode_info.group != snode_info.group:
12950           self.LogWarning("The primary and secondary nodes are in two"
12951                           " different node groups; the disk parameters"
12952                           " from the first disk's node group will be"
12953                           " used")
12954
12955     # hvparams processing
12956     if self.op.hvparams:
12957       hv_type = instance.hypervisor
12958       i_hvdict = _GetUpdatedParams(instance.hvparams, self.op.hvparams)
12959       utils.ForceDictType(i_hvdict, constants.HVS_PARAMETER_TYPES)
12960       hv_new = cluster.SimpleFillHV(hv_type, instance.os, i_hvdict)
12961
12962       # local check
12963       hypervisor.GetHypervisor(hv_type).CheckParameterSyntax(hv_new)
12964       _CheckHVParams(self, nodelist, instance.hypervisor, hv_new)
12965       self.hv_proposed = self.hv_new = hv_new # the new actual values
12966       self.hv_inst = i_hvdict # the new dict (without defaults)
12967     else:
12968       self.hv_proposed = cluster.SimpleFillHV(instance.hypervisor, instance.os,
12969                                               instance.hvparams)
12970       self.hv_new = self.hv_inst = {}
12971
12972     # beparams processing
12973     if self.op.beparams:
12974       i_bedict = _GetUpdatedParams(instance.beparams, self.op.beparams,
12975                                    use_none=True)
12976       objects.UpgradeBeParams(i_bedict)
12977       utils.ForceDictType(i_bedict, constants.BES_PARAMETER_TYPES)
12978       be_new = cluster.SimpleFillBE(i_bedict)
12979       self.be_proposed = self.be_new = be_new # the new actual values
12980       self.be_inst = i_bedict # the new dict (without defaults)
12981     else:
12982       self.be_new = self.be_inst = {}
12983       self.be_proposed = cluster.SimpleFillBE(instance.beparams)
12984     be_old = cluster.FillBE(instance)
12985
12986     # CPU param validation -- checking every time a parameter is
12987     # changed to cover all cases where either CPU mask or vcpus have
12988     # changed
12989     if (constants.BE_VCPUS in self.be_proposed and
12990         constants.HV_CPU_MASK in self.hv_proposed):
12991       cpu_list = \
12992         utils.ParseMultiCpuMask(self.hv_proposed[constants.HV_CPU_MASK])
12993       # Verify mask is consistent with number of vCPUs. Can skip this
12994       # test if only 1 entry in the CPU mask, which means same mask
12995       # is applied to all vCPUs.
12996       if (len(cpu_list) > 1 and
12997           len(cpu_list) != self.be_proposed[constants.BE_VCPUS]):
12998         raise errors.OpPrereqError("Number of vCPUs [%d] does not match the"
12999                                    " CPU mask [%s]" %
13000                                    (self.be_proposed[constants.BE_VCPUS],
13001                                     self.hv_proposed[constants.HV_CPU_MASK]),
13002                                    errors.ECODE_INVAL)
13003
13004       # Only perform this test if a new CPU mask is given
13005       if constants.HV_CPU_MASK in self.hv_new:
13006         # Calculate the largest CPU number requested
13007         max_requested_cpu = max(map(max, cpu_list))
13008         # Check that all of the instance's nodes have enough physical CPUs to
13009         # satisfy the requested CPU mask
13010         _CheckNodesPhysicalCPUs(self, instance.all_nodes,
13011                                 max_requested_cpu + 1, instance.hypervisor)
13012
13013     # osparams processing
13014     if self.op.osparams:
13015       i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
13016       _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
13017       self.os_inst = i_osdict # the new dict (without defaults)
13018     else:
13019       self.os_inst = {}
13020
13021     self.warn = []
13022
13023     #TODO(dynmem): do the appropriate check involving MINMEM
13024     if (constants.BE_MAXMEM in self.op.beparams and not self.op.force and
13025         be_new[constants.BE_MAXMEM] > be_old[constants.BE_MAXMEM]):
13026       mem_check_list = [pnode]
13027       if be_new[constants.BE_AUTO_BALANCE]:
13028         # either we changed auto_balance to yes or it was from before
13029         mem_check_list.extend(instance.secondary_nodes)
13030       instance_info = self.rpc.call_instance_info(pnode, instance.name,
13031                                                   instance.hypervisor)
13032       nodeinfo = self.rpc.call_node_info(mem_check_list, None,
13033                                          [instance.hypervisor])
13034       pninfo = nodeinfo[pnode]
13035       msg = pninfo.fail_msg
13036       if msg:
13037         # Assume the primary node is unreachable and go ahead
13038         self.warn.append("Can't get info from primary node %s: %s" %
13039                          (pnode, msg))
13040       else:
13041         (_, _, (pnhvinfo, )) = pninfo.payload
13042         if not isinstance(pnhvinfo.get("memory_free", None), int):
13043           self.warn.append("Node data from primary node %s doesn't contain"
13044                            " free memory information" % pnode)
13045         elif instance_info.fail_msg:
13046           self.warn.append("Can't get instance runtime information: %s" %
13047                           instance_info.fail_msg)
13048         else:
13049           if instance_info.payload:
13050             current_mem = int(instance_info.payload["memory"])
13051           else:
13052             # Assume instance not running
13053             # (there is a slight race condition here, but it's not very
13054             # probable, and we have no other way to check)
13055             # TODO: Describe race condition
13056             current_mem = 0
13057           #TODO(dynmem): do the appropriate check involving MINMEM
13058           miss_mem = (be_new[constants.BE_MAXMEM] - current_mem -
13059                       pnhvinfo["memory_free"])
13060           if miss_mem > 0:
13061             raise errors.OpPrereqError("This change will prevent the instance"
13062                                        " from starting, due to %d MB of memory"
13063                                        " missing on its primary node" %
13064                                        miss_mem,
13065                                        errors.ECODE_NORES)
13066
13067       if be_new[constants.BE_AUTO_BALANCE]:
13068         for node, nres in nodeinfo.items():
13069           if node not in instance.secondary_nodes:
13070             continue
13071           nres.Raise("Can't get info from secondary node %s" % node,
13072                      prereq=True, ecode=errors.ECODE_STATE)
13073           (_, _, (nhvinfo, )) = nres.payload
13074           if not isinstance(nhvinfo.get("memory_free", None), int):
13075             raise errors.OpPrereqError("Secondary node %s didn't return free"
13076                                        " memory information" % node,
13077                                        errors.ECODE_STATE)
13078           #TODO(dynmem): do the appropriate check involving MINMEM
13079           elif be_new[constants.BE_MAXMEM] > nhvinfo["memory_free"]:
13080             raise errors.OpPrereqError("This change will prevent the instance"
13081                                        " from failover to its secondary node"
13082                                        " %s, due to not enough memory" % node,
13083                                        errors.ECODE_STATE)
13084
13085     if self.op.runtime_mem:
13086       remote_info = self.rpc.call_instance_info(instance.primary_node,
13087                                                 instance.name,
13088                                                 instance.hypervisor)
13089       remote_info.Raise("Error checking node %s" % instance.primary_node)
13090       if not remote_info.payload: # not running already
13091         raise errors.OpPrereqError("Instance %s is not running" % instance.name,
13092                                    errors.ECODE_STATE)
13093
13094       current_memory = remote_info.payload["memory"]
13095       if (not self.op.force and
13096            (self.op.runtime_mem > self.be_proposed[constants.BE_MAXMEM] or
13097             self.op.runtime_mem < self.be_proposed[constants.BE_MINMEM])):
13098         raise errors.OpPrereqError("Instance %s must have memory between %d"
13099                                    " and %d MB of memory unless --force is"
13100                                    " given" % (instance.name,
13101                                     self.be_proposed[constants.BE_MINMEM],
13102                                     self.be_proposed[constants.BE_MAXMEM]),
13103                                    errors.ECODE_INVAL)
13104
13105       delta = self.op.runtime_mem - current_memory
13106       if delta > 0:
13107         _CheckNodeFreeMemory(self, instance.primary_node,
13108                              "ballooning memory for instance %s" %
13109                              instance.name, delta, instance.hypervisor)
13110
13111     if self.op.disks and instance.disk_template == constants.DT_DISKLESS:
13112       raise errors.OpPrereqError("Disk operations not supported for"
13113                                  " diskless instances",
13114                                  errors.ECODE_INVAL)
13115
13116     def _PrepareNicCreate(_, params, private):
13117       self._PrepareNicModification(params, private, None, None,
13118                                    {}, cluster, pnode)
13119       return (None, None)
13120
13121     def _PrepareNicMod(_, nic, params, private):
13122       self._PrepareNicModification(params, private, nic.ip, nic.network,
13123                                    nic.nicparams, cluster, pnode)
13124       return None
13125
13126     def _PrepareNicRemove(_, params, private):
13127       ip = params.ip
13128       net = params.network
13129       if net is not None and ip is not None:
13130         self.cfg.ReleaseIp(net, ip, self.proc.GetECId())
13131
13132     # Verify NIC changes (operating on copy)
13133     nics = instance.nics[:]
13134     ApplyContainerMods("NIC", nics, None, self.nicmod,
13135                        _PrepareNicCreate, _PrepareNicMod, _PrepareNicRemove)
13136     if len(nics) > constants.MAX_NICS:
13137       raise errors.OpPrereqError("Instance has too many network interfaces"
13138                                  " (%d), cannot add more" % constants.MAX_NICS,
13139                                  errors.ECODE_STATE)
13140
13141
13142     # Verify disk changes (operating on a copy)
13143     disks = instance.disks[:]
13144     ApplyContainerMods("disk", disks, None, self.diskmod,
13145                        None, None, None)
13146     if len(disks) > constants.MAX_DISKS:
13147       raise errors.OpPrereqError("Instance has too many disks (%d), cannot add"
13148                                  " more" % constants.MAX_DISKS,
13149                                  errors.ECODE_STATE)
13150
13151     if self.op.offline is not None:
13152       if self.op.offline:
13153         msg = "can't change to offline"
13154       else:
13155         msg = "can't change to online"
13156       _CheckInstanceState(self, instance, CAN_CHANGE_INSTANCE_OFFLINE, msg=msg)
13157
13158     # Pre-compute NIC changes (necessary to use result in hooks)
13159     self._nic_chgdesc = []
13160     if self.nicmod:
13161       # Operate on copies as this is still in prereq
13162       nics = [nic.Copy() for nic in instance.nics]
13163       ApplyContainerMods("NIC", nics, self._nic_chgdesc, self.nicmod,
13164                          self._CreateNewNic, self._ApplyNicMods,
13165                          self._RemoveNic)
13166       self._new_nics = nics
13167     else:
13168       self._new_nics = None
13169
13170
13171   def _ConvertPlainToDrbd(self, feedback_fn):
13172     """Converts an instance from plain to drbd.
13173
13174     """
13175     feedback_fn("Converting template to drbd")
13176     instance = self.instance
13177     pnode = instance.primary_node
13178     snode = self.op.remote_node
13179
13180     assert instance.disk_template == constants.DT_PLAIN
13181
13182     # create a fake disk info for _GenerateDiskTemplate
13183     disk_info = [{constants.IDISK_SIZE: d.size, constants.IDISK_MODE: d.mode,
13184                   constants.IDISK_VG: d.logical_id[0]}
13185                  for d in instance.disks]
13186     new_disks = _GenerateDiskTemplate(self, self.op.disk_template,
13187                                       instance.name, pnode, [snode],
13188                                       disk_info, None, None, 0, feedback_fn,
13189                                       self.diskparams)
13190     anno_disks = rpc.AnnotateDiskParams(constants.DT_DRBD8, new_disks,
13191                                         self.diskparams)
13192     info = _GetInstanceInfoText(instance)
13193     feedback_fn("Creating additional volumes...")
13194     # first, create the missing data and meta devices
13195     for disk in anno_disks:
13196       # unfortunately this is... not too nice
13197       _CreateSingleBlockDev(self, pnode, instance, disk.children[1],
13198                             info, True)
13199       for child in disk.children:
13200         _CreateSingleBlockDev(self, snode, instance, child, info, True)
13201     # at this stage, all new LVs have been created, we can rename the
13202     # old ones
13203     feedback_fn("Renaming original volumes...")
13204     rename_list = [(o, n.children[0].logical_id)
13205                    for (o, n) in zip(instance.disks, new_disks)]
13206     result = self.rpc.call_blockdev_rename(pnode, rename_list)
13207     result.Raise("Failed to rename original LVs")
13208
13209     feedback_fn("Initializing DRBD devices...")
13210     # all child devices are in place, we can now create the DRBD devices
13211     for disk in anno_disks:
13212       for node in [pnode, snode]:
13213         f_create = node == pnode
13214         _CreateSingleBlockDev(self, node, instance, disk, info, f_create)
13215
13216     # at this point, the instance has been modified
13217     instance.disk_template = constants.DT_DRBD8
13218     instance.disks = new_disks
13219     self.cfg.Update(instance, feedback_fn)
13220
13221     # Release node locks while waiting for sync
13222     _ReleaseLocks(self, locking.LEVEL_NODE)
13223
13224     # disks are created, waiting for sync
13225     disk_abort = not _WaitForSync(self, instance,
13226                                   oneshot=not self.op.wait_for_sync)
13227     if disk_abort:
13228       raise errors.OpExecError("There are some degraded disks for"
13229                                " this instance, please cleanup manually")
13230
13231     # Node resource locks will be released by caller
13232
13233   def _ConvertDrbdToPlain(self, feedback_fn):
13234     """Converts an instance from drbd to plain.
13235
13236     """
13237     instance = self.instance
13238
13239     assert len(instance.secondary_nodes) == 1
13240     assert instance.disk_template == constants.DT_DRBD8
13241
13242     pnode = instance.primary_node
13243     snode = instance.secondary_nodes[0]
13244     feedback_fn("Converting template to plain")
13245
13246     old_disks = _AnnotateDiskParams(instance, instance.disks, self.cfg)
13247     new_disks = [d.children[0] for d in instance.disks]
13248
13249     # copy over size and mode
13250     for parent, child in zip(old_disks, new_disks):
13251       child.size = parent.size
13252       child.mode = parent.mode
13253
13254     # this is a DRBD disk, return its port to the pool
13255     # NOTE: this must be done right before the call to cfg.Update!
13256     for disk in old_disks:
13257       tcp_port = disk.logical_id[2]
13258       self.cfg.AddTcpUdpPort(tcp_port)
13259
13260     # update instance structure
13261     instance.disks = new_disks
13262     instance.disk_template = constants.DT_PLAIN
13263     self.cfg.Update(instance, feedback_fn)
13264
13265     # Release locks in case removing disks takes a while
13266     _ReleaseLocks(self, locking.LEVEL_NODE)
13267
13268     feedback_fn("Removing volumes on the secondary node...")
13269     for disk in old_disks:
13270       self.cfg.SetDiskID(disk, snode)
13271       msg = self.rpc.call_blockdev_remove(snode, disk).fail_msg
13272       if msg:
13273         self.LogWarning("Could not remove block device %s on node %s,"
13274                         " continuing anyway: %s", disk.iv_name, snode, msg)
13275
13276     feedback_fn("Removing unneeded volumes on the primary node...")
13277     for idx, disk in enumerate(old_disks):
13278       meta = disk.children[1]
13279       self.cfg.SetDiskID(meta, pnode)
13280       msg = self.rpc.call_blockdev_remove(pnode, meta).fail_msg
13281       if msg:
13282         self.LogWarning("Could not remove metadata for disk %d on node %s,"
13283                         " continuing anyway: %s", idx, pnode, msg)
13284
13285   def _CreateNewDisk(self, idx, params, _):
13286     """Creates a new disk.
13287
13288     """
13289     instance = self.instance
13290
13291     # add a new disk
13292     if instance.disk_template in constants.DTS_FILEBASED:
13293       (file_driver, file_path) = instance.disks[0].logical_id
13294       file_path = os.path.dirname(file_path)
13295     else:
13296       file_driver = file_path = None
13297
13298     disk = \
13299       _GenerateDiskTemplate(self, instance.disk_template, instance.name,
13300                             instance.primary_node, instance.secondary_nodes,
13301                             [params], file_path, file_driver, idx,
13302                             self.Log, self.diskparams)[0]
13303
13304     info = _GetInstanceInfoText(instance)
13305
13306     logging.info("Creating volume %s for instance %s",
13307                  disk.iv_name, instance.name)
13308     # Note: this needs to be kept in sync with _CreateDisks
13309     #HARDCODE
13310     for node in instance.all_nodes:
13311       f_create = (node == instance.primary_node)
13312       try:
13313         _CreateBlockDev(self, node, instance, disk, f_create, info, f_create)
13314       except errors.OpExecError, err:
13315         self.LogWarning("Failed to create volume %s (%s) on node '%s': %s",
13316                         disk.iv_name, disk, node, err)
13317
13318     if self.op.hotplug and disk.pci and _InstanceRunning(self, self.instance):
13319       self.LogInfo("Trying to hotplug device.")
13320       _, device_info = _AssembleInstanceDisks(self, self.instance,
13321                                               [disk], check=False)
13322       _, _, dev_path = device_info[0]
13323       #TODO: handle result
13324       self.rpc.call_hot_add_disk(self.instance.primary_node,
13325                                  self.instance, disk, dev_path, idx)
13326     return (disk, [
13327       ("disk/%d" % idx, "add:size=%s,mode=%s" % (disk.size, disk.mode)),
13328       ])
13329
13330   @staticmethod
13331   def _ModifyDisk(idx, disk, params, _):
13332     """Modifies a disk.
13333
13334     """
13335     disk.mode = params[constants.IDISK_MODE]
13336
13337     return [
13338       ("disk.mode/%d" % idx, disk.mode),
13339       ]
13340
13341   def _RemoveDisk(self, idx, root, _):
13342     """Removes a disk.
13343
13344     """
13345     #TODO: log warning in case hotplug is not possible
13346     #      handle errors
13347     if root.pci and not self.op.hotplug:
13348       raise errors.OpPrereqError("Cannot remove a disk that has"
13349                                  " been hotplugged"
13350                                  " without removing it with hotplug",
13351                                  errors.ECODE_INVAL)
13352     if self.op.hotplug and root.pci:
13353       if _InstanceRunning(self, self.instance):
13354         self.LogInfo("Trying to hotplug device.")
13355         self.rpc.call_hot_del_disk(self.instance.primary_node,
13356                                    self.instance, root, idx)
13357         _ShutdownInstanceDisks(self, self.instance, [root])
13358       self.cfg.UpdatePCIInfo(self.instance, root.pci)
13359
13360     (anno_disk,) = _AnnotateDiskParams(self.instance, [root], self.cfg)
13361     for node, disk in anno_disk.ComputeNodeTree(self.instance.primary_node):
13362       self.cfg.SetDiskID(disk, node)
13363       msg = self.rpc.call_blockdev_remove(node, disk).fail_msg
13364       if msg:
13365         self.LogWarning("Could not remove disk/%d on node '%s': %s,"
13366                         " continuing anyway", idx, node, msg)
13367
13368     # if this is a DRBD disk, return its port to the pool
13369     if root.dev_type in constants.LDS_DRBD:
13370       self.cfg.AddTcpUdpPort(root.logical_id[2])
13371
13372   def _CreateNewNic(self, idx, params, private):
13373     """Creates data structure for a new network interface.
13374
13375     """
13376     mac = params[constants.INIC_MAC]
13377     ip = params.get(constants.INIC_IP, None)
13378     network = params.get(constants.INIC_NETWORK, None)
13379     #TODO: not private.filled?? can a nic have no nicparams??
13380     nicparams = private.filled
13381
13382     nic = objects.NIC(mac=mac, ip=ip, network=network, nicparams=nicparams)
13383
13384     #TODO: log warning in case hotplug is not possible
13385     #      handle errors
13386     #      return changes
13387     if self.op.hotplug:
13388       nic.idx, nic.pci = _GetPCIInfo(self, 'nics')
13389       if nic.pci is not None and _InstanceRunning(self, self.instance):
13390         self.rpc.call_hot_add_nic(self.instance.primary_node,
13391                                   self.instance, nic, idx)
13392     desc =  [
13393       ("nic.%d" % idx,
13394        "add:mac=%s,ip=%s,mode=%s,link=%s,network=%s" %
13395        (mac, ip, private.filled[constants.NIC_MODE],
13396        private.filled[constants.NIC_LINK],
13397        network)),
13398       ]
13399     return (nic, desc)
13400
13401   def _ApplyNicMods(self, idx, nic, params, private):
13402     """Modifies a network interface.
13403
13404     """
13405     changes = []
13406
13407     for key in [constants.INIC_MAC, constants.INIC_IP, constants.INIC_NETWORK]:
13408       if key in params:
13409         changes.append(("nic.%s/%d" % (key, idx), params[key]))
13410         setattr(nic, key, params[key])
13411
13412     if private.filled:
13413       nic.nicparams = private.filled
13414
13415       for (key, val) in nic.nicparams.items():
13416         changes.append(("nic.%s/%d" % (key, idx), val))
13417
13418     #TODO: log warning in case hotplug is not possible
13419     #      handle errors
13420     if self.op.hotplug and nic.pci and _InstanceRunning(self, self.instance):
13421       self.LogInfo("Trying to hotplug device.")
13422       self.rpc.call_hot_del_nic(self.instance.primary_node,
13423                                 self.instance, nic, idx)
13424       self.rpc.call_hot_add_nic(self.instance.primary_node,
13425                                 self.instance, nic, idx)
13426     return changes
13427
13428   def _RemoveNic(self, idx, nic, _):
13429     if nic.pci and not self.op.hotplug:
13430       raise errors.OpPrereqError("Cannot remove a nic that has been hotplugged"
13431                                  " without removing it with hotplug",
13432                                  errors.ECODE_INVAL)
13433     #TODO: log warning in case hotplug is not possible
13434     #      handle errors
13435     if self.op.hotplug and nic.pci:
13436       if _InstanceRunning(self, self.instance):
13437         self.LogInfo("Trying to hotplug device.")
13438         self.rpc.call_hot_del_nic(self.instance.primary_node,
13439                                   self.instance, nic, idx)
13440       self.cfg.UpdatePCIInfo(self.instance, nic.pci)
13441
13442
13443   def Exec(self, feedback_fn):
13444     """Modifies an instance.
13445
13446     All parameters take effect only at the next restart of the instance.
13447
13448     """
13449     # Process here the warnings from CheckPrereq, as we don't have a
13450     # feedback_fn there.
13451     # TODO: Replace with self.LogWarning
13452     for warn in self.warn:
13453       feedback_fn("WARNING: %s" % warn)
13454
13455     assert ((self.op.disk_template is None) ^
13456             bool(self.owned_locks(locking.LEVEL_NODE_RES))), \
13457       "Not owning any node resource locks"
13458
13459     result = []
13460     instance = self.instance
13461
13462     # runtime memory
13463     if self.op.runtime_mem:
13464       rpcres = self.rpc.call_instance_balloon_memory(instance.primary_node,
13465                                                      instance,
13466                                                      self.op.runtime_mem)
13467       rpcres.Raise("Cannot modify instance runtime memory")
13468       result.append(("runtime_memory", self.op.runtime_mem))
13469
13470     # Apply disk changes
13471     ApplyContainerMods("disk", instance.disks, result, self.diskmod,
13472                        self._CreateNewDisk, self._ModifyDisk, self._RemoveDisk)
13473     _UpdateIvNames(0, instance.disks)
13474
13475     if self.op.disk_template:
13476       if __debug__:
13477         check_nodes = set(instance.all_nodes)
13478         if self.op.remote_node:
13479           check_nodes.add(self.op.remote_node)
13480         for level in [locking.LEVEL_NODE, locking.LEVEL_NODE_RES]:
13481           owned = self.owned_locks(level)
13482           assert not (check_nodes - owned), \
13483             ("Not owning the correct locks, owning %r, expected at least %r" %
13484              (owned, check_nodes))
13485
13486       r_shut = _ShutdownInstanceDisks(self, instance)
13487       if not r_shut:
13488         raise errors.OpExecError("Cannot shutdown instance disks, unable to"
13489                                  " proceed with disk template conversion")
13490       mode = (instance.disk_template, self.op.disk_template)
13491       try:
13492         self._DISK_CONVERSIONS[mode](self, feedback_fn)
13493       except:
13494         self.cfg.ReleaseDRBDMinors(instance.name)
13495         raise
13496       result.append(("disk_template", self.op.disk_template))
13497
13498       assert instance.disk_template == self.op.disk_template, \
13499         ("Expected disk template '%s', found '%s'" %
13500          (self.op.disk_template, instance.disk_template))
13501
13502     # Release node and resource locks if there are any (they might already have
13503     # been released during disk conversion)
13504     _ReleaseLocks(self, locking.LEVEL_NODE)
13505     _ReleaseLocks(self, locking.LEVEL_NODE_RES)
13506
13507     # Apply NIC changes
13508     if self._new_nics is not None:
13509       instance.nics = self._new_nics
13510       result.extend(self._nic_chgdesc)
13511
13512     # hvparams changes
13513     if self.op.hvparams:
13514       instance.hvparams = self.hv_inst
13515       for key, val in self.op.hvparams.iteritems():
13516         result.append(("hv/%s" % key, val))
13517
13518     # beparams changes
13519     if self.op.beparams:
13520       instance.beparams = self.be_inst
13521       for key, val in self.op.beparams.iteritems():
13522         result.append(("be/%s" % key, val))
13523
13524     # OS change
13525     if self.op.os_name:
13526       instance.os = self.op.os_name
13527
13528     # osparams changes
13529     if self.op.osparams:
13530       instance.osparams = self.os_inst
13531       for key, val in self.op.osparams.iteritems():
13532         result.append(("os/%s" % key, val))
13533
13534     if self.op.offline is None:
13535       # Ignore
13536       pass
13537     elif self.op.offline:
13538       # Mark instance as offline
13539       self.cfg.MarkInstanceOffline(instance.name)
13540       result.append(("admin_state", constants.ADMINST_OFFLINE))
13541     else:
13542       # Mark instance as online, but stopped
13543       self.cfg.MarkInstanceDown(instance.name)
13544       result.append(("admin_state", constants.ADMINST_DOWN))
13545
13546     self.cfg.Update(instance, feedback_fn, self.proc.GetECId())
13547
13548     assert not (self.owned_locks(locking.LEVEL_NODE_RES) or
13549                 self.owned_locks(locking.LEVEL_NODE)), \
13550       "All node locks should have been released by now"
13551
13552     return result
13553
13554   _DISK_CONVERSIONS = {
13555     (constants.DT_PLAIN, constants.DT_DRBD8): _ConvertPlainToDrbd,
13556     (constants.DT_DRBD8, constants.DT_PLAIN): _ConvertDrbdToPlain,
13557     }
13558
13559
13560 class LUInstanceChangeGroup(LogicalUnit):
13561   HPATH = "instance-change-group"
13562   HTYPE = constants.HTYPE_INSTANCE
13563   REQ_BGL = False
13564
13565   def ExpandNames(self):
13566     self.share_locks = _ShareAll()
13567     self.needed_locks = {
13568       locking.LEVEL_NODEGROUP: [],
13569       locking.LEVEL_NODE: [],
13570       }
13571
13572     self._ExpandAndLockInstance()
13573
13574     if self.op.target_groups:
13575       self.req_target_uuids = map(self.cfg.LookupNodeGroup,
13576                                   self.op.target_groups)
13577     else:
13578       self.req_target_uuids = None
13579
13580     self.op.iallocator = _GetDefaultIAllocator(self.cfg, self.op.iallocator)
13581
13582   def DeclareLocks(self, level):
13583     if level == locking.LEVEL_NODEGROUP:
13584       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
13585
13586       if self.req_target_uuids:
13587         lock_groups = set(self.req_target_uuids)
13588
13589         # Lock all groups used by instance optimistically; this requires going
13590         # via the node before it's locked, requiring verification later on
13591         instance_groups = self.cfg.GetInstanceNodeGroups(self.op.instance_name)
13592         lock_groups.update(instance_groups)
13593       else:
13594         # No target groups, need to lock all of them
13595         lock_groups = locking.ALL_SET
13596
13597       self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
13598
13599     elif level == locking.LEVEL_NODE:
13600       if self.req_target_uuids:
13601         # Lock all nodes used by instances
13602         self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
13603         self._LockInstancesNodes()
13604
13605         # Lock all nodes in all potential target groups
13606         lock_groups = (frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) -
13607                        self.cfg.GetInstanceNodeGroups(self.op.instance_name))
13608         member_nodes = [node_name
13609                         for group in lock_groups
13610                         for node_name in self.cfg.GetNodeGroup(group).members]
13611         self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
13612       else:
13613         # Lock all nodes as all groups are potential targets
13614         self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13615
13616   def CheckPrereq(self):
13617     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
13618     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
13619     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
13620
13621     assert (self.req_target_uuids is None or
13622             owned_groups.issuperset(self.req_target_uuids))
13623     assert owned_instances == set([self.op.instance_name])
13624
13625     # Get instance information
13626     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
13627
13628     # Check if node groups for locked instance are still correct
13629     assert owned_nodes.issuperset(self.instance.all_nodes), \
13630       ("Instance %s's nodes changed while we kept the lock" %
13631        self.op.instance_name)
13632
13633     inst_groups = _CheckInstanceNodeGroups(self.cfg, self.op.instance_name,
13634                                            owned_groups)
13635
13636     if self.req_target_uuids:
13637       # User requested specific target groups
13638       self.target_uuids = frozenset(self.req_target_uuids)
13639     else:
13640       # All groups except those used by the instance are potential targets
13641       self.target_uuids = owned_groups - inst_groups
13642
13643     conflicting_groups = self.target_uuids & inst_groups
13644     if conflicting_groups:
13645       raise errors.OpPrereqError("Can't use group(s) '%s' as targets, they are"
13646                                  " used by the instance '%s'" %
13647                                  (utils.CommaJoin(conflicting_groups),
13648                                   self.op.instance_name),
13649                                  errors.ECODE_INVAL)
13650
13651     if not self.target_uuids:
13652       raise errors.OpPrereqError("There are no possible target groups",
13653                                  errors.ECODE_INVAL)
13654
13655   def BuildHooksEnv(self):
13656     """Build hooks env.
13657
13658     """
13659     assert self.target_uuids
13660
13661     env = {
13662       "TARGET_GROUPS": " ".join(self.target_uuids),
13663       }
13664
13665     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
13666
13667     return env
13668
13669   def BuildHooksNodes(self):
13670     """Build hooks nodes.
13671
13672     """
13673     mn = self.cfg.GetMasterNode()
13674     return ([mn], [mn])
13675
13676   def Exec(self, feedback_fn):
13677     instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
13678
13679     assert instances == [self.op.instance_name], "Instance not locked"
13680
13681     ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_CHG_GROUP,
13682                      instances=instances, target_groups=list(self.target_uuids))
13683
13684     ial.Run(self.op.iallocator)
13685
13686     if not ial.success:
13687       raise errors.OpPrereqError("Can't compute solution for changing group of"
13688                                  " instance '%s' using iallocator '%s': %s" %
13689                                  (self.op.instance_name, self.op.iallocator,
13690                                   ial.info),
13691                                  errors.ECODE_NORES)
13692
13693     jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
13694
13695     self.LogInfo("Iallocator returned %s job(s) for changing group of"
13696                  " instance '%s'", len(jobs), self.op.instance_name)
13697
13698     return ResultWithJobs(jobs)
13699
13700
13701 class LUBackupQuery(NoHooksLU):
13702   """Query the exports list
13703
13704   """
13705   REQ_BGL = False
13706
13707   def CheckArguments(self):
13708     self.expq = _ExportQuery(qlang.MakeSimpleFilter("node", self.op.nodes),
13709                              ["node", "export"], self.op.use_locking)
13710
13711   def ExpandNames(self):
13712     self.expq.ExpandNames(self)
13713
13714   def DeclareLocks(self, level):
13715     self.expq.DeclareLocks(self, level)
13716
13717   def Exec(self, feedback_fn):
13718     result = {}
13719
13720     for (node, expname) in self.expq.OldStyleQuery(self):
13721       if expname is None:
13722         result[node] = False
13723       else:
13724         result.setdefault(node, []).append(expname)
13725
13726     return result
13727
13728
13729 class _ExportQuery(_QueryBase):
13730   FIELDS = query.EXPORT_FIELDS
13731
13732   #: The node name is not a unique key for this query
13733   SORT_FIELD = "node"
13734
13735   def ExpandNames(self, lu):
13736     lu.needed_locks = {}
13737
13738     # The following variables interact with _QueryBase._GetNames
13739     if self.names:
13740       self.wanted = _GetWantedNodes(lu, self.names)
13741     else:
13742       self.wanted = locking.ALL_SET
13743
13744     self.do_locking = self.use_locking
13745
13746     if self.do_locking:
13747       lu.share_locks = _ShareAll()
13748       lu.needed_locks = {
13749         locking.LEVEL_NODE: self.wanted,
13750         }
13751
13752   def DeclareLocks(self, lu, level):
13753     pass
13754
13755   def _GetQueryData(self, lu):
13756     """Computes the list of nodes and their attributes.
13757
13758     """
13759     # Locking is not used
13760     # TODO
13761     assert not (compat.any(lu.glm.is_owned(level)
13762                            for level in locking.LEVELS
13763                            if level != locking.LEVEL_CLUSTER) or
13764                 self.do_locking or self.use_locking)
13765
13766     nodes = self._GetNames(lu, lu.cfg.GetNodeList(), locking.LEVEL_NODE)
13767
13768     result = []
13769
13770     for (node, nres) in lu.rpc.call_export_list(nodes).items():
13771       if nres.fail_msg:
13772         result.append((node, None))
13773       else:
13774         result.extend((node, expname) for expname in nres.payload)
13775
13776     return result
13777
13778
13779 class LUBackupPrepare(NoHooksLU):
13780   """Prepares an instance for an export and returns useful information.
13781
13782   """
13783   REQ_BGL = False
13784
13785   def ExpandNames(self):
13786     self._ExpandAndLockInstance()
13787
13788   def CheckPrereq(self):
13789     """Check prerequisites.
13790
13791     """
13792     instance_name = self.op.instance_name
13793
13794     self.instance = self.cfg.GetInstanceInfo(instance_name)
13795     assert self.instance is not None, \
13796           "Cannot retrieve locked instance %s" % self.op.instance_name
13797     _CheckNodeOnline(self, self.instance.primary_node)
13798
13799     self._cds = _GetClusterDomainSecret()
13800
13801   def Exec(self, feedback_fn):
13802     """Prepares an instance for an export.
13803
13804     """
13805     instance = self.instance
13806
13807     if self.op.mode == constants.EXPORT_MODE_REMOTE:
13808       salt = utils.GenerateSecret(8)
13809
13810       feedback_fn("Generating X509 certificate on %s" % instance.primary_node)
13811       result = self.rpc.call_x509_cert_create(instance.primary_node,
13812                                               constants.RIE_CERT_VALIDITY)
13813       result.Raise("Can't create X509 key and certificate on %s" % result.node)
13814
13815       (name, cert_pem) = result.payload
13816
13817       cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
13818                                              cert_pem)
13819
13820       return {
13821         "handshake": masterd.instance.ComputeRemoteExportHandshake(self._cds),
13822         "x509_key_name": (name, utils.Sha1Hmac(self._cds, name, salt=salt),
13823                           salt),
13824         "x509_ca": utils.SignX509Certificate(cert, self._cds, salt),
13825         }
13826
13827     return None
13828
13829
13830 class LUBackupExport(LogicalUnit):
13831   """Export an instance to an image in the cluster.
13832
13833   """
13834   HPATH = "instance-export"
13835   HTYPE = constants.HTYPE_INSTANCE
13836   REQ_BGL = False
13837
13838   def CheckArguments(self):
13839     """Check the arguments.
13840
13841     """
13842     self.x509_key_name = self.op.x509_key_name
13843     self.dest_x509_ca_pem = self.op.destination_x509_ca
13844
13845     if self.op.mode == constants.EXPORT_MODE_REMOTE:
13846       if not self.x509_key_name:
13847         raise errors.OpPrereqError("Missing X509 key name for encryption",
13848                                    errors.ECODE_INVAL)
13849
13850       if not self.dest_x509_ca_pem:
13851         raise errors.OpPrereqError("Missing destination X509 CA",
13852                                    errors.ECODE_INVAL)
13853
13854   def ExpandNames(self):
13855     self._ExpandAndLockInstance()
13856
13857     # Lock all nodes for local exports
13858     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13859       # FIXME: lock only instance primary and destination node
13860       #
13861       # Sad but true, for now we have do lock all nodes, as we don't know where
13862       # the previous export might be, and in this LU we search for it and
13863       # remove it from its current node. In the future we could fix this by:
13864       #  - making a tasklet to search (share-lock all), then create the
13865       #    new one, then one to remove, after
13866       #  - removing the removal operation altogether
13867       self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
13868
13869   def DeclareLocks(self, level):
13870     """Last minute lock declaration."""
13871     # All nodes are locked anyway, so nothing to do here.
13872
13873   def BuildHooksEnv(self):
13874     """Build hooks env.
13875
13876     This will run on the master, primary node and target node.
13877
13878     """
13879     env = {
13880       "EXPORT_MODE": self.op.mode,
13881       "EXPORT_NODE": self.op.target_node,
13882       "EXPORT_DO_SHUTDOWN": self.op.shutdown,
13883       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
13884       # TODO: Generic function for boolean env variables
13885       "REMOVE_INSTANCE": str(bool(self.op.remove_instance)),
13886       }
13887
13888     env.update(_BuildInstanceHookEnvByObject(self, self.instance))
13889
13890     return env
13891
13892   def BuildHooksNodes(self):
13893     """Build hooks nodes.
13894
13895     """
13896     nl = [self.cfg.GetMasterNode(), self.instance.primary_node]
13897
13898     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13899       nl.append(self.op.target_node)
13900
13901     return (nl, nl)
13902
13903   def CheckPrereq(self):
13904     """Check prerequisites.
13905
13906     This checks that the instance and node names are valid.
13907
13908     """
13909     instance_name = self.op.instance_name
13910
13911     self.instance = self.cfg.GetInstanceInfo(instance_name)
13912     assert self.instance is not None, \
13913           "Cannot retrieve locked instance %s" % self.op.instance_name
13914     _CheckNodeOnline(self, self.instance.primary_node)
13915
13916     if (self.op.remove_instance and
13917         self.instance.admin_state == constants.ADMINST_UP and
13918         not self.op.shutdown):
13919       raise errors.OpPrereqError("Can not remove instance without shutting it"
13920                                  " down before")
13921
13922     if self.op.mode == constants.EXPORT_MODE_LOCAL:
13923       self.op.target_node = _ExpandNodeName(self.cfg, self.op.target_node)
13924       self.dst_node = self.cfg.GetNodeInfo(self.op.target_node)
13925       assert self.dst_node is not None
13926
13927       _CheckNodeOnline(self, self.dst_node.name)
13928       _CheckNodeNotDrained(self, self.dst_node.name)
13929
13930       self._cds = None
13931       self.dest_disk_info = None
13932       self.dest_x509_ca = None
13933
13934     elif self.op.mode == constants.EXPORT_MODE_REMOTE:
13935       self.dst_node = None
13936
13937       if len(self.op.target_node) != len(self.instance.disks):
13938         raise errors.OpPrereqError(("Received destination information for %s"
13939                                     " disks, but instance %s has %s disks") %
13940                                    (len(self.op.target_node), instance_name,
13941                                     len(self.instance.disks)),
13942                                    errors.ECODE_INVAL)
13943
13944       cds = _GetClusterDomainSecret()
13945
13946       # Check X509 key name
13947       try:
13948         (key_name, hmac_digest, hmac_salt) = self.x509_key_name
13949       except (TypeError, ValueError), err:
13950         raise errors.OpPrereqError("Invalid data for X509 key name: %s" % err)
13951
13952       if not utils.VerifySha1Hmac(cds, key_name, hmac_digest, salt=hmac_salt):
13953         raise errors.OpPrereqError("HMAC for X509 key name is wrong",
13954                                    errors.ECODE_INVAL)
13955
13956       # Load and verify CA
13957       try:
13958         (cert, _) = utils.LoadSignedX509Certificate(self.dest_x509_ca_pem, cds)
13959       except OpenSSL.crypto.Error, err:
13960         raise errors.OpPrereqError("Unable to load destination X509 CA (%s)" %
13961                                    (err, ), errors.ECODE_INVAL)
13962
13963       (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
13964       if errcode is not None:
13965         raise errors.OpPrereqError("Invalid destination X509 CA (%s)" %
13966                                    (msg, ), errors.ECODE_INVAL)
13967
13968       self.dest_x509_ca = cert
13969
13970       # Verify target information
13971       disk_info = []
13972       for idx, disk_data in enumerate(self.op.target_node):
13973         try:
13974           (host, port, magic) = \
13975             masterd.instance.CheckRemoteExportDiskInfo(cds, idx, disk_data)
13976         except errors.GenericError, err:
13977           raise errors.OpPrereqError("Target info for disk %s: %s" %
13978                                      (idx, err), errors.ECODE_INVAL)
13979
13980         disk_info.append((host, port, magic))
13981
13982       assert len(disk_info) == len(self.op.target_node)
13983       self.dest_disk_info = disk_info
13984
13985     else:
13986       raise errors.ProgrammerError("Unhandled export mode %r" %
13987                                    self.op.mode)
13988
13989     # instance disk type verification
13990     # TODO: Implement export support for file-based disks
13991     for disk in self.instance.disks:
13992       if disk.dev_type == constants.LD_FILE:
13993         raise errors.OpPrereqError("Export not supported for instances with"
13994                                    " file-based disks", errors.ECODE_INVAL)
13995
13996   def _CleanupExports(self, feedback_fn):
13997     """Removes exports of current instance from all other nodes.
13998
13999     If an instance in a cluster with nodes A..D was exported to node C, its
14000     exports will be removed from the nodes A, B and D.
14001
14002     """
14003     assert self.op.mode != constants.EXPORT_MODE_REMOTE
14004
14005     nodelist = self.cfg.GetNodeList()
14006     nodelist.remove(self.dst_node.name)
14007
14008     # on one-node clusters nodelist will be empty after the removal
14009     # if we proceed the backup would be removed because OpBackupQuery
14010     # substitutes an empty list with the full cluster node list.
14011     iname = self.instance.name
14012     if nodelist:
14013       feedback_fn("Removing old exports for instance %s" % iname)
14014       exportlist = self.rpc.call_export_list(nodelist)
14015       for node in exportlist:
14016         if exportlist[node].fail_msg:
14017           continue
14018         if iname in exportlist[node].payload:
14019           msg = self.rpc.call_export_remove(node, iname).fail_msg
14020           if msg:
14021             self.LogWarning("Could not remove older export for instance %s"
14022                             " on node %s: %s", iname, node, msg)
14023
14024   def Exec(self, feedback_fn):
14025     """Export an instance to an image in the cluster.
14026
14027     """
14028     assert self.op.mode in constants.EXPORT_MODES
14029
14030     instance = self.instance
14031     src_node = instance.primary_node
14032
14033     if self.op.shutdown:
14034       # shutdown the instance, but not the disks
14035       feedback_fn("Shutting down instance %s" % instance.name)
14036       result = self.rpc.call_instance_shutdown(src_node, instance,
14037                                                self.op.shutdown_timeout)
14038       # TODO: Maybe ignore failures if ignore_remove_failures is set
14039       result.Raise("Could not shutdown instance %s on"
14040                    " node %s" % (instance.name, src_node))
14041
14042     # set the disks ID correctly since call_instance_start needs the
14043     # correct drbd minor to create the symlinks
14044     for disk in instance.disks:
14045       self.cfg.SetDiskID(disk, src_node)
14046
14047     activate_disks = (instance.admin_state != constants.ADMINST_UP)
14048
14049     if activate_disks:
14050       # Activate the instance disks if we'exporting a stopped instance
14051       feedback_fn("Activating disks for %s" % instance.name)
14052       _StartInstanceDisks(self, instance, None)
14053
14054     try:
14055       helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
14056                                                      instance)
14057
14058       helper.CreateSnapshots()
14059       try:
14060         if (self.op.shutdown and
14061             instance.admin_state == constants.ADMINST_UP and
14062             not self.op.remove_instance):
14063           assert not activate_disks
14064           feedback_fn("Starting instance %s" % instance.name)
14065           result = self.rpc.call_instance_start(src_node,
14066                                                 (instance, None, None), False)
14067           msg = result.fail_msg
14068           if msg:
14069             feedback_fn("Failed to start instance: %s" % msg)
14070             _ShutdownInstanceDisks(self, instance)
14071             raise errors.OpExecError("Could not start instance: %s" % msg)
14072
14073         if self.op.mode == constants.EXPORT_MODE_LOCAL:
14074           (fin_resu, dresults) = helper.LocalExport(self.dst_node)
14075         elif self.op.mode == constants.EXPORT_MODE_REMOTE:
14076           connect_timeout = constants.RIE_CONNECT_TIMEOUT
14077           timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
14078
14079           (key_name, _, _) = self.x509_key_name
14080
14081           dest_ca_pem = \
14082             OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
14083                                             self.dest_x509_ca)
14084
14085           (fin_resu, dresults) = helper.RemoteExport(self.dest_disk_info,
14086                                                      key_name, dest_ca_pem,
14087                                                      timeouts)
14088       finally:
14089         helper.Cleanup()
14090
14091       # Check for backwards compatibility
14092       assert len(dresults) == len(instance.disks)
14093       assert compat.all(isinstance(i, bool) for i in dresults), \
14094              "Not all results are boolean: %r" % dresults
14095
14096     finally:
14097       if activate_disks:
14098         feedback_fn("Deactivating disks for %s" % instance.name)
14099         _ShutdownInstanceDisks(self, instance)
14100
14101     if not (compat.all(dresults) and fin_resu):
14102       failures = []
14103       if not fin_resu:
14104         failures.append("export finalization")
14105       if not compat.all(dresults):
14106         fdsk = utils.CommaJoin(idx for (idx, dsk) in enumerate(dresults)
14107                                if not dsk)
14108         failures.append("disk export: disk(s) %s" % fdsk)
14109
14110       raise errors.OpExecError("Export failed, errors in %s" %
14111                                utils.CommaJoin(failures))
14112
14113     # At this point, the export was successful, we can cleanup/finish
14114
14115     # Remove instance if requested
14116     if self.op.remove_instance:
14117       feedback_fn("Removing instance %s" % instance.name)
14118       _RemoveInstance(self, feedback_fn, instance,
14119                       self.op.ignore_remove_failures)
14120
14121     if self.op.mode == constants.EXPORT_MODE_LOCAL:
14122       self._CleanupExports(feedback_fn)
14123
14124     return fin_resu, dresults
14125
14126
14127 class LUBackupRemove(NoHooksLU):
14128   """Remove exports related to the named instance.
14129
14130   """
14131   REQ_BGL = False
14132
14133   def ExpandNames(self):
14134     self.needed_locks = {}
14135     # We need all nodes to be locked in order for RemoveExport to work, but we
14136     # don't need to lock the instance itself, as nothing will happen to it (and
14137     # we can remove exports also for a removed instance)
14138     self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
14139
14140   def Exec(self, feedback_fn):
14141     """Remove any export.
14142
14143     """
14144     instance_name = self.cfg.ExpandInstanceName(self.op.instance_name)
14145     # If the instance was not found we'll try with the name that was passed in.
14146     # This will only work if it was an FQDN, though.
14147     fqdn_warn = False
14148     if not instance_name:
14149       fqdn_warn = True
14150       instance_name = self.op.instance_name
14151
14152     locked_nodes = self.owned_locks(locking.LEVEL_NODE)
14153     exportlist = self.rpc.call_export_list(locked_nodes)
14154     found = False
14155     for node in exportlist:
14156       msg = exportlist[node].fail_msg
14157       if msg:
14158         self.LogWarning("Failed to query node %s (continuing): %s", node, msg)
14159         continue
14160       if instance_name in exportlist[node].payload:
14161         found = True
14162         result = self.rpc.call_export_remove(node, instance_name)
14163         msg = result.fail_msg
14164         if msg:
14165           logging.error("Could not remove export for instance %s"
14166                         " on node %s: %s", instance_name, node, msg)
14167
14168     if fqdn_warn and not found:
14169       feedback_fn("Export not found. If trying to remove an export belonging"
14170                   " to a deleted instance please use its Fully Qualified"
14171                   " Domain Name.")
14172
14173
14174 class LUGroupAdd(LogicalUnit):
14175   """Logical unit for creating node groups.
14176
14177   """
14178   HPATH = "group-add"
14179   HTYPE = constants.HTYPE_GROUP
14180   REQ_BGL = False
14181
14182   def ExpandNames(self):
14183     # We need the new group's UUID here so that we can create and acquire the
14184     # corresponding lock. Later, in Exec(), we'll indicate to cfg.AddNodeGroup
14185     # that it should not check whether the UUID exists in the configuration.
14186     self.group_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
14187     self.needed_locks = {}
14188     self.add_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
14189
14190   def CheckPrereq(self):
14191     """Check prerequisites.
14192
14193     This checks that the given group name is not an existing node group
14194     already.
14195
14196     """
14197     try:
14198       existing_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14199     except errors.OpPrereqError:
14200       pass
14201     else:
14202       raise errors.OpPrereqError("Desired group name '%s' already exists as a"
14203                                  " node group (UUID: %s)" %
14204                                  (self.op.group_name, existing_uuid),
14205                                  errors.ECODE_EXISTS)
14206
14207     if self.op.ndparams:
14208       utils.ForceDictType(self.op.ndparams, constants.NDS_PARAMETER_TYPES)
14209
14210     if self.op.hv_state:
14211       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state, None)
14212     else:
14213       self.new_hv_state = None
14214
14215     if self.op.disk_state:
14216       self.new_disk_state = _MergeAndVerifyDiskState(self.op.disk_state, None)
14217     else:
14218       self.new_disk_state = None
14219
14220     if self.op.diskparams:
14221       for templ in constants.DISK_TEMPLATES:
14222         if templ in self.op.diskparams:
14223           utils.ForceDictType(self.op.diskparams[templ],
14224                               constants.DISK_DT_TYPES)
14225       self.new_diskparams = self.op.diskparams
14226       try:
14227         utils.VerifyDictOptions(self.new_diskparams, constants.DISK_DT_DEFAULTS)
14228       except errors.OpPrereqError, err:
14229         raise errors.OpPrereqError("While verify diskparams options: %s" % err,
14230                                    errors.ECODE_INVAL)
14231     else:
14232       self.new_diskparams = {}
14233
14234     if self.op.ipolicy:
14235       cluster = self.cfg.GetClusterInfo()
14236       full_ipolicy = cluster.SimpleFillIPolicy(self.op.ipolicy)
14237       try:
14238         objects.InstancePolicy.CheckParameterSyntax(full_ipolicy, False)
14239       except errors.ConfigurationError, err:
14240         raise errors.OpPrereqError("Invalid instance policy: %s" % err,
14241                                    errors.ECODE_INVAL)
14242
14243   def BuildHooksEnv(self):
14244     """Build hooks env.
14245
14246     """
14247     return {
14248       "GROUP_NAME": self.op.group_name,
14249       }
14250
14251   def BuildHooksNodes(self):
14252     """Build hooks nodes.
14253
14254     """
14255     mn = self.cfg.GetMasterNode()
14256     return ([mn], [mn])
14257
14258   def Exec(self, feedback_fn):
14259     """Add the node group to the cluster.
14260
14261     """
14262     group_obj = objects.NodeGroup(name=self.op.group_name, members=[],
14263                                   uuid=self.group_uuid,
14264                                   alloc_policy=self.op.alloc_policy,
14265                                   ndparams=self.op.ndparams,
14266                                   diskparams=self.new_diskparams,
14267                                   ipolicy=self.op.ipolicy,
14268                                   hv_state_static=self.new_hv_state,
14269                                   disk_state_static=self.new_disk_state)
14270
14271     self.cfg.AddNodeGroup(group_obj, self.proc.GetECId(), check_uuid=False)
14272     del self.remove_locks[locking.LEVEL_NODEGROUP]
14273
14274
14275 class LUGroupAssignNodes(NoHooksLU):
14276   """Logical unit for assigning nodes to groups.
14277
14278   """
14279   REQ_BGL = False
14280
14281   def ExpandNames(self):
14282     # These raise errors.OpPrereqError on their own:
14283     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14284     self.op.nodes = _GetWantedNodes(self, self.op.nodes)
14285
14286     # We want to lock all the affected nodes and groups. We have readily
14287     # available the list of nodes, and the *destination* group. To gather the
14288     # list of "source" groups, we need to fetch node information later on.
14289     self.needed_locks = {
14290       locking.LEVEL_NODEGROUP: set([self.group_uuid]),
14291       locking.LEVEL_NODE: self.op.nodes,
14292       }
14293
14294   def DeclareLocks(self, level):
14295     if level == locking.LEVEL_NODEGROUP:
14296       assert len(self.needed_locks[locking.LEVEL_NODEGROUP]) == 1
14297
14298       # Try to get all affected nodes' groups without having the group or node
14299       # lock yet. Needs verification later in the code flow.
14300       groups = self.cfg.GetNodeGroupsFromNodes(self.op.nodes)
14301
14302       self.needed_locks[locking.LEVEL_NODEGROUP].update(groups)
14303
14304   def CheckPrereq(self):
14305     """Check prerequisites.
14306
14307     """
14308     assert self.needed_locks[locking.LEVEL_NODEGROUP]
14309     assert (frozenset(self.owned_locks(locking.LEVEL_NODE)) ==
14310             frozenset(self.op.nodes))
14311
14312     expected_locks = (set([self.group_uuid]) |
14313                       self.cfg.GetNodeGroupsFromNodes(self.op.nodes))
14314     actual_locks = self.owned_locks(locking.LEVEL_NODEGROUP)
14315     if actual_locks != expected_locks:
14316       raise errors.OpExecError("Nodes changed groups since locks were acquired,"
14317                                " current groups are '%s', used to be '%s'" %
14318                                (utils.CommaJoin(expected_locks),
14319                                 utils.CommaJoin(actual_locks)))
14320
14321     self.node_data = self.cfg.GetAllNodesInfo()
14322     self.group = self.cfg.GetNodeGroup(self.group_uuid)
14323     instance_data = self.cfg.GetAllInstancesInfo()
14324
14325     if self.group is None:
14326       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
14327                                (self.op.group_name, self.group_uuid))
14328
14329     (new_splits, previous_splits) = \
14330       self.CheckAssignmentForSplitInstances([(node, self.group_uuid)
14331                                              for node in self.op.nodes],
14332                                             self.node_data, instance_data)
14333
14334     if new_splits:
14335       fmt_new_splits = utils.CommaJoin(utils.NiceSort(new_splits))
14336
14337       if not self.op.force:
14338         raise errors.OpExecError("The following instances get split by this"
14339                                  " change and --force was not given: %s" %
14340                                  fmt_new_splits)
14341       else:
14342         self.LogWarning("This operation will split the following instances: %s",
14343                         fmt_new_splits)
14344
14345         if previous_splits:
14346           self.LogWarning("In addition, these already-split instances continue"
14347                           " to be split across groups: %s",
14348                           utils.CommaJoin(utils.NiceSort(previous_splits)))
14349
14350   def Exec(self, feedback_fn):
14351     """Assign nodes to a new group.
14352
14353     """
14354     mods = [(node_name, self.group_uuid) for node_name in self.op.nodes]
14355
14356     self.cfg.AssignGroupNodes(mods)
14357
14358   @staticmethod
14359   def CheckAssignmentForSplitInstances(changes, node_data, instance_data):
14360     """Check for split instances after a node assignment.
14361
14362     This method considers a series of node assignments as an atomic operation,
14363     and returns information about split instances after applying the set of
14364     changes.
14365
14366     In particular, it returns information about newly split instances, and
14367     instances that were already split, and remain so after the change.
14368
14369     Only instances whose disk template is listed in constants.DTS_INT_MIRROR are
14370     considered.
14371
14372     @type changes: list of (node_name, new_group_uuid) pairs.
14373     @param changes: list of node assignments to consider.
14374     @param node_data: a dict with data for all nodes
14375     @param instance_data: a dict with all instances to consider
14376     @rtype: a two-tuple
14377     @return: a list of instances that were previously okay and result split as a
14378       consequence of this change, and a list of instances that were previously
14379       split and this change does not fix.
14380
14381     """
14382     changed_nodes = dict((node, group) for node, group in changes
14383                          if node_data[node].group != group)
14384
14385     all_split_instances = set()
14386     previously_split_instances = set()
14387
14388     def InstanceNodes(instance):
14389       return [instance.primary_node] + list(instance.secondary_nodes)
14390
14391     for inst in instance_data.values():
14392       if inst.disk_template not in constants.DTS_INT_MIRROR:
14393         continue
14394
14395       instance_nodes = InstanceNodes(inst)
14396
14397       if len(set(node_data[node].group for node in instance_nodes)) > 1:
14398         previously_split_instances.add(inst.name)
14399
14400       if len(set(changed_nodes.get(node, node_data[node].group)
14401                  for node in instance_nodes)) > 1:
14402         all_split_instances.add(inst.name)
14403
14404     return (list(all_split_instances - previously_split_instances),
14405             list(previously_split_instances & all_split_instances))
14406
14407
14408 class _GroupQuery(_QueryBase):
14409   FIELDS = query.GROUP_FIELDS
14410
14411   def ExpandNames(self, lu):
14412     lu.needed_locks = {}
14413
14414     self._all_groups = lu.cfg.GetAllNodeGroupsInfo()
14415     self._cluster = lu.cfg.GetClusterInfo()
14416     name_to_uuid = dict((g.name, g.uuid) for g in self._all_groups.values())
14417
14418     if not self.names:
14419       self.wanted = [name_to_uuid[name]
14420                      for name in utils.NiceSort(name_to_uuid.keys())]
14421     else:
14422       # Accept names to be either names or UUIDs.
14423       missing = []
14424       self.wanted = []
14425       all_uuid = frozenset(self._all_groups.keys())
14426
14427       for name in self.names:
14428         if name in all_uuid:
14429           self.wanted.append(name)
14430         elif name in name_to_uuid:
14431           self.wanted.append(name_to_uuid[name])
14432         else:
14433           missing.append(name)
14434
14435       if missing:
14436         raise errors.OpPrereqError("Some groups do not exist: %s" %
14437                                    utils.CommaJoin(missing),
14438                                    errors.ECODE_NOENT)
14439
14440   def DeclareLocks(self, lu, level):
14441     pass
14442
14443   def _GetQueryData(self, lu):
14444     """Computes the list of node groups and their attributes.
14445
14446     """
14447     do_nodes = query.GQ_NODE in self.requested_data
14448     do_instances = query.GQ_INST in self.requested_data
14449
14450     group_to_nodes = None
14451     group_to_instances = None
14452
14453     # For GQ_NODE, we need to map group->[nodes], and group->[instances] for
14454     # GQ_INST. The former is attainable with just GetAllNodesInfo(), but for the
14455     # latter GetAllInstancesInfo() is not enough, for we have to go through
14456     # instance->node. Hence, we will need to process nodes even if we only need
14457     # instance information.
14458     if do_nodes or do_instances:
14459       all_nodes = lu.cfg.GetAllNodesInfo()
14460       group_to_nodes = dict((uuid, []) for uuid in self.wanted)
14461       node_to_group = {}
14462
14463       for node in all_nodes.values():
14464         if node.group in group_to_nodes:
14465           group_to_nodes[node.group].append(node.name)
14466           node_to_group[node.name] = node.group
14467
14468       if do_instances:
14469         all_instances = lu.cfg.GetAllInstancesInfo()
14470         group_to_instances = dict((uuid, []) for uuid in self.wanted)
14471
14472         for instance in all_instances.values():
14473           node = instance.primary_node
14474           if node in node_to_group:
14475             group_to_instances[node_to_group[node]].append(instance.name)
14476
14477         if not do_nodes:
14478           # Do not pass on node information if it was not requested.
14479           group_to_nodes = None
14480
14481     return query.GroupQueryData(self._cluster,
14482                                 [self._all_groups[uuid]
14483                                  for uuid in self.wanted],
14484                                 group_to_nodes, group_to_instances,
14485                                 query.GQ_DISKPARAMS in self.requested_data)
14486
14487
14488 class LUGroupQuery(NoHooksLU):
14489   """Logical unit for querying node groups.
14490
14491   """
14492   REQ_BGL = False
14493
14494   def CheckArguments(self):
14495     self.gq = _GroupQuery(qlang.MakeSimpleFilter("name", self.op.names),
14496                           self.op.output_fields, False)
14497
14498   def ExpandNames(self):
14499     self.gq.ExpandNames(self)
14500
14501   def DeclareLocks(self, level):
14502     self.gq.DeclareLocks(self, level)
14503
14504   def Exec(self, feedback_fn):
14505     return self.gq.OldStyleQuery(self)
14506
14507
14508 class LUGroupSetParams(LogicalUnit):
14509   """Modifies the parameters of a node group.
14510
14511   """
14512   HPATH = "group-modify"
14513   HTYPE = constants.HTYPE_GROUP
14514   REQ_BGL = False
14515
14516   def CheckArguments(self):
14517     all_changes = [
14518       self.op.ndparams,
14519       self.op.diskparams,
14520       self.op.alloc_policy,
14521       self.op.hv_state,
14522       self.op.disk_state,
14523       self.op.ipolicy,
14524       ]
14525
14526     if all_changes.count(None) == len(all_changes):
14527       raise errors.OpPrereqError("Please pass at least one modification",
14528                                  errors.ECODE_INVAL)
14529
14530   def ExpandNames(self):
14531     # This raises errors.OpPrereqError on its own:
14532     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14533
14534     self.needed_locks = {
14535       locking.LEVEL_INSTANCE: [],
14536       locking.LEVEL_NODEGROUP: [self.group_uuid],
14537       }
14538
14539     self.share_locks[locking.LEVEL_INSTANCE] = 1
14540
14541   def DeclareLocks(self, level):
14542     if level == locking.LEVEL_INSTANCE:
14543       assert not self.needed_locks[locking.LEVEL_INSTANCE]
14544
14545       # Lock instances optimistically, needs verification once group lock has
14546       # been acquired
14547       self.needed_locks[locking.LEVEL_INSTANCE] = \
14548           self.cfg.GetNodeGroupInstances(self.group_uuid)
14549
14550   @staticmethod
14551   def _UpdateAndVerifyDiskParams(old, new):
14552     """Updates and verifies disk parameters.
14553
14554     """
14555     new_params = _GetUpdatedParams(old, new)
14556     utils.ForceDictType(new_params, constants.DISK_DT_TYPES)
14557     return new_params
14558
14559   def CheckPrereq(self):
14560     """Check prerequisites.
14561
14562     """
14563     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
14564
14565     # Check if locked instances are still correct
14566     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
14567
14568     self.group = self.cfg.GetNodeGroup(self.group_uuid)
14569     cluster = self.cfg.GetClusterInfo()
14570
14571     if self.group is None:
14572       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
14573                                (self.op.group_name, self.group_uuid))
14574
14575     if self.op.ndparams:
14576       new_ndparams = _GetUpdatedParams(self.group.ndparams, self.op.ndparams)
14577       utils.ForceDictType(new_ndparams, constants.NDS_PARAMETER_TYPES)
14578       self.new_ndparams = new_ndparams
14579
14580     if self.op.diskparams:
14581       diskparams = self.group.diskparams
14582       uavdp = self._UpdateAndVerifyDiskParams
14583       # For each disktemplate subdict update and verify the values
14584       new_diskparams = dict((dt,
14585                              uavdp(diskparams.get(dt, {}),
14586                                    self.op.diskparams[dt]))
14587                             for dt in constants.DISK_TEMPLATES
14588                             if dt in self.op.diskparams)
14589       # As we've all subdicts of diskparams ready, lets merge the actual
14590       # dict with all updated subdicts
14591       self.new_diskparams = objects.FillDict(diskparams, new_diskparams)
14592       try:
14593         utils.VerifyDictOptions(self.new_diskparams, constants.DISK_DT_DEFAULTS)
14594       except errors.OpPrereqError, err:
14595         raise errors.OpPrereqError("While verify diskparams options: %s" % err,
14596                                    errors.ECODE_INVAL)
14597
14598     if self.op.hv_state:
14599       self.new_hv_state = _MergeAndVerifyHvState(self.op.hv_state,
14600                                                  self.group.hv_state_static)
14601
14602     if self.op.disk_state:
14603       self.new_disk_state = \
14604         _MergeAndVerifyDiskState(self.op.disk_state,
14605                                  self.group.disk_state_static)
14606
14607     if self.op.ipolicy:
14608       self.new_ipolicy = _GetUpdatedIPolicy(self.group.ipolicy,
14609                                             self.op.ipolicy,
14610                                             group_policy=True)
14611
14612       new_ipolicy = cluster.SimpleFillIPolicy(self.new_ipolicy)
14613       inst_filter = lambda inst: inst.name in owned_instances
14614       instances = self.cfg.GetInstancesInfoByFilter(inst_filter).values()
14615       violations = \
14616           _ComputeNewInstanceViolations(_CalculateGroupIPolicy(cluster,
14617                                                                self.group),
14618                                         new_ipolicy, instances)
14619
14620       if violations:
14621         self.LogWarning("After the ipolicy change the following instances"
14622                         " violate them: %s",
14623                         utils.CommaJoin(violations))
14624
14625   def BuildHooksEnv(self):
14626     """Build hooks env.
14627
14628     """
14629     return {
14630       "GROUP_NAME": self.op.group_name,
14631       "NEW_ALLOC_POLICY": self.op.alloc_policy,
14632       }
14633
14634   def BuildHooksNodes(self):
14635     """Build hooks nodes.
14636
14637     """
14638     mn = self.cfg.GetMasterNode()
14639     return ([mn], [mn])
14640
14641   def Exec(self, feedback_fn):
14642     """Modifies the node group.
14643
14644     """
14645     result = []
14646
14647     if self.op.ndparams:
14648       self.group.ndparams = self.new_ndparams
14649       result.append(("ndparams", str(self.group.ndparams)))
14650
14651     if self.op.diskparams:
14652       self.group.diskparams = self.new_diskparams
14653       result.append(("diskparams", str(self.group.diskparams)))
14654
14655     if self.op.alloc_policy:
14656       self.group.alloc_policy = self.op.alloc_policy
14657
14658     if self.op.hv_state:
14659       self.group.hv_state_static = self.new_hv_state
14660
14661     if self.op.disk_state:
14662       self.group.disk_state_static = self.new_disk_state
14663
14664     if self.op.ipolicy:
14665       self.group.ipolicy = self.new_ipolicy
14666
14667     self.cfg.Update(self.group, feedback_fn)
14668     return result
14669
14670
14671 class LUGroupRemove(LogicalUnit):
14672   HPATH = "group-remove"
14673   HTYPE = constants.HTYPE_GROUP
14674   REQ_BGL = False
14675
14676   def ExpandNames(self):
14677     # This will raises errors.OpPrereqError on its own:
14678     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14679     self.needed_locks = {
14680       locking.LEVEL_NODEGROUP: [self.group_uuid],
14681       }
14682
14683   def CheckPrereq(self):
14684     """Check prerequisites.
14685
14686     This checks that the given group name exists as a node group, that is
14687     empty (i.e., contains no nodes), and that is not the last group of the
14688     cluster.
14689
14690     """
14691     # Verify that the group is empty.
14692     group_nodes = [node.name
14693                    for node in self.cfg.GetAllNodesInfo().values()
14694                    if node.group == self.group_uuid]
14695
14696     if group_nodes:
14697       raise errors.OpPrereqError("Group '%s' not empty, has the following"
14698                                  " nodes: %s" %
14699                                  (self.op.group_name,
14700                                   utils.CommaJoin(utils.NiceSort(group_nodes))),
14701                                  errors.ECODE_STATE)
14702
14703     # Verify the cluster would not be left group-less.
14704     if len(self.cfg.GetNodeGroupList()) == 1:
14705       raise errors.OpPrereqError("Group '%s' is the only group,"
14706                                  " cannot be removed" %
14707                                  self.op.group_name,
14708                                  errors.ECODE_STATE)
14709
14710   def BuildHooksEnv(self):
14711     """Build hooks env.
14712
14713     """
14714     return {
14715       "GROUP_NAME": self.op.group_name,
14716       }
14717
14718   def BuildHooksNodes(self):
14719     """Build hooks nodes.
14720
14721     """
14722     mn = self.cfg.GetMasterNode()
14723     return ([mn], [mn])
14724
14725   def Exec(self, feedback_fn):
14726     """Remove the node group.
14727
14728     """
14729     try:
14730       self.cfg.RemoveNodeGroup(self.group_uuid)
14731     except errors.ConfigurationError:
14732       raise errors.OpExecError("Group '%s' with UUID %s disappeared" %
14733                                (self.op.group_name, self.group_uuid))
14734
14735     self.remove_locks[locking.LEVEL_NODEGROUP] = self.group_uuid
14736
14737
14738 class LUGroupRename(LogicalUnit):
14739   HPATH = "group-rename"
14740   HTYPE = constants.HTYPE_GROUP
14741   REQ_BGL = False
14742
14743   def ExpandNames(self):
14744     # This raises errors.OpPrereqError on its own:
14745     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14746
14747     self.needed_locks = {
14748       locking.LEVEL_NODEGROUP: [self.group_uuid],
14749       }
14750
14751   def CheckPrereq(self):
14752     """Check prerequisites.
14753
14754     Ensures requested new name is not yet used.
14755
14756     """
14757     try:
14758       new_name_uuid = self.cfg.LookupNodeGroup(self.op.new_name)
14759     except errors.OpPrereqError:
14760       pass
14761     else:
14762       raise errors.OpPrereqError("Desired new name '%s' clashes with existing"
14763                                  " node group (UUID: %s)" %
14764                                  (self.op.new_name, new_name_uuid),
14765                                  errors.ECODE_EXISTS)
14766
14767   def BuildHooksEnv(self):
14768     """Build hooks env.
14769
14770     """
14771     return {
14772       "OLD_NAME": self.op.group_name,
14773       "NEW_NAME": self.op.new_name,
14774       }
14775
14776   def BuildHooksNodes(self):
14777     """Build hooks nodes.
14778
14779     """
14780     mn = self.cfg.GetMasterNode()
14781
14782     all_nodes = self.cfg.GetAllNodesInfo()
14783     all_nodes.pop(mn, None)
14784
14785     run_nodes = [mn]
14786     run_nodes.extend(node.name for node in all_nodes.values()
14787                      if node.group == self.group_uuid)
14788
14789     return (run_nodes, run_nodes)
14790
14791   def Exec(self, feedback_fn):
14792     """Rename the node group.
14793
14794     """
14795     group = self.cfg.GetNodeGroup(self.group_uuid)
14796
14797     if group is None:
14798       raise errors.OpExecError("Could not retrieve group '%s' (UUID: %s)" %
14799                                (self.op.group_name, self.group_uuid))
14800
14801     group.name = self.op.new_name
14802     self.cfg.Update(group, feedback_fn)
14803
14804     return self.op.new_name
14805
14806
14807 class LUGroupEvacuate(LogicalUnit):
14808   HPATH = "group-evacuate"
14809   HTYPE = constants.HTYPE_GROUP
14810   REQ_BGL = False
14811
14812   def ExpandNames(self):
14813     # This raises errors.OpPrereqError on its own:
14814     self.group_uuid = self.cfg.LookupNodeGroup(self.op.group_name)
14815
14816     if self.op.target_groups:
14817       self.req_target_uuids = map(self.cfg.LookupNodeGroup,
14818                                   self.op.target_groups)
14819     else:
14820       self.req_target_uuids = []
14821
14822     if self.group_uuid in self.req_target_uuids:
14823       raise errors.OpPrereqError("Group to be evacuated (%s) can not be used"
14824                                  " as a target group (targets are %s)" %
14825                                  (self.group_uuid,
14826                                   utils.CommaJoin(self.req_target_uuids)),
14827                                  errors.ECODE_INVAL)
14828
14829     self.op.iallocator = _GetDefaultIAllocator(self.cfg, self.op.iallocator)
14830
14831     self.share_locks = _ShareAll()
14832     self.needed_locks = {
14833       locking.LEVEL_INSTANCE: [],
14834       locking.LEVEL_NODEGROUP: [],
14835       locking.LEVEL_NODE: [],
14836       }
14837
14838   def DeclareLocks(self, level):
14839     if level == locking.LEVEL_INSTANCE:
14840       assert not self.needed_locks[locking.LEVEL_INSTANCE]
14841
14842       # Lock instances optimistically, needs verification once node and group
14843       # locks have been acquired
14844       self.needed_locks[locking.LEVEL_INSTANCE] = \
14845         self.cfg.GetNodeGroupInstances(self.group_uuid)
14846
14847     elif level == locking.LEVEL_NODEGROUP:
14848       assert not self.needed_locks[locking.LEVEL_NODEGROUP]
14849
14850       if self.req_target_uuids:
14851         lock_groups = set([self.group_uuid] + self.req_target_uuids)
14852
14853         # Lock all groups used by instances optimistically; this requires going
14854         # via the node before it's locked, requiring verification later on
14855         lock_groups.update(group_uuid
14856                            for instance_name in
14857                              self.owned_locks(locking.LEVEL_INSTANCE)
14858                            for group_uuid in
14859                              self.cfg.GetInstanceNodeGroups(instance_name))
14860       else:
14861         # No target groups, need to lock all of them
14862         lock_groups = locking.ALL_SET
14863
14864       self.needed_locks[locking.LEVEL_NODEGROUP] = lock_groups
14865
14866     elif level == locking.LEVEL_NODE:
14867       # This will only lock the nodes in the group to be evacuated which
14868       # contain actual instances
14869       self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_APPEND
14870       self._LockInstancesNodes()
14871
14872       # Lock all nodes in group to be evacuated and target groups
14873       owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
14874       assert self.group_uuid in owned_groups
14875       member_nodes = [node_name
14876                       for group in owned_groups
14877                       for node_name in self.cfg.GetNodeGroup(group).members]
14878       self.needed_locks[locking.LEVEL_NODE].extend(member_nodes)
14879
14880   def CheckPrereq(self):
14881     owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
14882     owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
14883     owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
14884
14885     assert owned_groups.issuperset(self.req_target_uuids)
14886     assert self.group_uuid in owned_groups
14887
14888     # Check if locked instances are still correct
14889     _CheckNodeGroupInstances(self.cfg, self.group_uuid, owned_instances)
14890
14891     # Get instance information
14892     self.instances = dict(self.cfg.GetMultiInstanceInfo(owned_instances))
14893
14894     # Check if node groups for locked instances are still correct
14895     _CheckInstancesNodeGroups(self.cfg, self.instances,
14896                               owned_groups, owned_nodes, self.group_uuid)
14897
14898     if self.req_target_uuids:
14899       # User requested specific target groups
14900       self.target_uuids = self.req_target_uuids
14901     else:
14902       # All groups except the one to be evacuated are potential targets
14903       self.target_uuids = [group_uuid for group_uuid in owned_groups
14904                            if group_uuid != self.group_uuid]
14905
14906       if not self.target_uuids:
14907         raise errors.OpPrereqError("There are no possible target groups",
14908                                    errors.ECODE_INVAL)
14909
14910   def BuildHooksEnv(self):
14911     """Build hooks env.
14912
14913     """
14914     return {
14915       "GROUP_NAME": self.op.group_name,
14916       "TARGET_GROUPS": " ".join(self.target_uuids),
14917       }
14918
14919   def BuildHooksNodes(self):
14920     """Build hooks nodes.
14921
14922     """
14923     mn = self.cfg.GetMasterNode()
14924
14925     assert self.group_uuid in self.owned_locks(locking.LEVEL_NODEGROUP)
14926
14927     run_nodes = [mn] + self.cfg.GetNodeGroup(self.group_uuid).members
14928
14929     return (run_nodes, run_nodes)
14930
14931   def Exec(self, feedback_fn):
14932     instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
14933
14934     assert self.group_uuid not in self.target_uuids
14935
14936     ial = IAllocator(self.cfg, self.rpc, constants.IALLOCATOR_MODE_CHG_GROUP,
14937                      instances=instances, target_groups=self.target_uuids)
14938
14939     ial.Run(self.op.iallocator)
14940
14941     if not ial.success:
14942       raise errors.OpPrereqError("Can't compute group evacuation using"
14943                                  " iallocator '%s': %s" %
14944                                  (self.op.iallocator, ial.info),
14945                                  errors.ECODE_NORES)
14946
14947     jobs = _LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
14948
14949     self.LogInfo("Iallocator returned %s job(s) for evacuating node group %s",
14950                  len(jobs), self.op.group_name)
14951
14952     return ResultWithJobs(jobs)
14953
14954
14955 class TagsLU(NoHooksLU): # pylint: disable=W0223
14956   """Generic tags LU.
14957
14958   This is an abstract class which is the parent of all the other tags LUs.
14959
14960   """
14961   def ExpandNames(self):
14962     self.group_uuid = None
14963     self.needed_locks = {}
14964
14965     if self.op.kind == constants.TAG_NODE:
14966       self.op.name = _ExpandNodeName(self.cfg, self.op.name)
14967       lock_level = locking.LEVEL_NODE
14968       lock_name = self.op.name
14969     elif self.op.kind == constants.TAG_INSTANCE:
14970       self.op.name = _ExpandInstanceName(self.cfg, self.op.name)
14971       lock_level = locking.LEVEL_INSTANCE
14972       lock_name = self.op.name
14973     elif self.op.kind == constants.TAG_NODEGROUP:
14974       self.group_uuid = self.cfg.LookupNodeGroup(self.op.name)
14975       lock_level = locking.LEVEL_NODEGROUP
14976       lock_name = self.group_uuid
14977     elif self.op.kind == constants.TAG_NETWORK:
14978       self.network_uuid = self.cfg.LookupNetwork(self.op.name)
14979       lock_level = locking.LEVEL_NETWORK
14980       lock_name = self.network_uuid
14981     else:
14982       lock_level = None
14983       lock_name = None
14984
14985     if lock_level and getattr(self.op, "use_locking", True):
14986       self.needed_locks[lock_level] = lock_name
14987
14988     # FIXME: Acquire BGL for cluster tag operations (as of this writing it's
14989     # not possible to acquire the BGL based on opcode parameters)
14990
14991   def CheckPrereq(self):
14992     """Check prerequisites.
14993
14994     """
14995     if self.op.kind == constants.TAG_CLUSTER:
14996       self.target = self.cfg.GetClusterInfo()
14997     elif self.op.kind == constants.TAG_NODE:
14998       self.target = self.cfg.GetNodeInfo(self.op.name)
14999     elif self.op.kind == constants.TAG_INSTANCE:
15000       self.target = self.cfg.GetInstanceInfo(self.op.name)
15001     elif self.op.kind == constants.TAG_NODEGROUP:
15002       self.target = self.cfg.GetNodeGroup(self.group_uuid)
15003     elif self.op.kind == constants.TAG_NETWORK:
15004       self.target = self.cfg.GetNetwork(self.network_uuid)
15005     else:
15006       raise errors.OpPrereqError("Wrong tag type requested (%s)" %
15007                                  str(self.op.kind), errors.ECODE_INVAL)
15008
15009
15010 class LUTagsGet(TagsLU):
15011   """Returns the tags of a given object.
15012
15013   """
15014   REQ_BGL = False
15015
15016   def ExpandNames(self):
15017     TagsLU.ExpandNames(self)
15018
15019     # Share locks as this is only a read operation
15020     self.share_locks = _ShareAll()
15021
15022   def Exec(self, feedback_fn):
15023     """Returns the tag list.
15024
15025     """
15026     return list(self.target.GetTags())
15027
15028
15029 class LUTagsSearch(NoHooksLU):
15030   """Searches the tags for a given pattern.
15031
15032   """
15033   REQ_BGL = False
15034
15035   def ExpandNames(self):
15036     self.needed_locks = {}
15037
15038   def CheckPrereq(self):
15039     """Check prerequisites.
15040
15041     This checks the pattern passed for validity by compiling it.
15042
15043     """
15044     try:
15045       self.re = re.compile(self.op.pattern)
15046     except re.error, err:
15047       raise errors.OpPrereqError("Invalid search pattern '%s': %s" %
15048                                  (self.op.pattern, err), errors.ECODE_INVAL)
15049
15050   def Exec(self, feedback_fn):
15051     """Returns the tag list.
15052
15053     """
15054     cfg = self.cfg
15055     tgts = [("/cluster", cfg.GetClusterInfo())]
15056     ilist = cfg.GetAllInstancesInfo().values()
15057     tgts.extend([("/instances/%s" % i.name, i) for i in ilist])
15058     nlist = cfg.GetAllNodesInfo().values()
15059     tgts.extend([("/nodes/%s" % n.name, n) for n in nlist])
15060     tgts.extend(("/nodegroup/%s" % n.name, n)
15061                 for n in cfg.GetAllNodeGroupsInfo().values())
15062     results = []
15063     for path, target in tgts:
15064       for tag in target.GetTags():
15065         if self.re.search(tag):
15066           results.append((path, tag))
15067     return results
15068
15069
15070 class LUTagsSet(TagsLU):
15071   """Sets a tag on a given object.
15072
15073   """
15074   REQ_BGL = False
15075
15076   def CheckPrereq(self):
15077     """Check prerequisites.
15078
15079     This checks the type and length of the tag name and value.
15080
15081     """
15082     TagsLU.CheckPrereq(self)
15083     for tag in self.op.tags:
15084       objects.TaggableObject.ValidateTag(tag)
15085
15086   def Exec(self, feedback_fn):
15087     """Sets the tag.
15088
15089     """
15090     try:
15091       for tag in self.op.tags:
15092         self.target.AddTag(tag)
15093     except errors.TagError, err:
15094       raise errors.OpExecError("Error while setting tag: %s" % str(err))
15095     self.cfg.Update(self.target, feedback_fn)
15096
15097
15098 class LUTagsDel(TagsLU):
15099   """Delete a list of tags from a given object.
15100
15101   """
15102   REQ_BGL = False
15103
15104   def CheckPrereq(self):
15105     """Check prerequisites.
15106
15107     This checks that we have the given tag.
15108
15109     """
15110     TagsLU.CheckPrereq(self)
15111     for tag in self.op.tags:
15112       objects.TaggableObject.ValidateTag(tag)
15113     del_tags = frozenset(self.op.tags)
15114     cur_tags = self.target.GetTags()
15115
15116     diff_tags = del_tags - cur_tags
15117     if diff_tags:
15118       diff_names = ("'%s'" % i for i in sorted(diff_tags))
15119       raise errors.OpPrereqError("Tag(s) %s not found" %
15120                                  (utils.CommaJoin(diff_names), ),
15121                                  errors.ECODE_NOENT)
15122
15123   def Exec(self, feedback_fn):
15124     """Remove the tag from the object.
15125
15126     """
15127     for tag in self.op.tags:
15128       self.target.RemoveTag(tag)
15129     self.cfg.Update(self.target, feedback_fn)
15130
15131
15132 class LUTestDelay(NoHooksLU):
15133   """Sleep for a specified amount of time.
15134
15135   This LU sleeps on the master and/or nodes for a specified amount of
15136   time.
15137
15138   """
15139   REQ_BGL = False
15140
15141   def ExpandNames(self):
15142     """Expand names and set required locks.
15143
15144     This expands the node list, if any.
15145
15146     """
15147     self.needed_locks = {}
15148     if self.op.on_nodes:
15149       # _GetWantedNodes can be used here, but is not always appropriate to use
15150       # this way in ExpandNames. Check LogicalUnit.ExpandNames docstring for
15151       # more information.
15152       self.op.on_nodes = _GetWantedNodes(self, self.op.on_nodes)
15153       self.needed_locks[locking.LEVEL_NODE] = self.op.on_nodes
15154
15155   def _TestDelay(self):
15156     """Do the actual sleep.
15157
15158     """
15159     if self.op.on_master:
15160       if not utils.TestDelay(self.op.duration):
15161         raise errors.OpExecError("Error during master delay test")
15162     if self.op.on_nodes:
15163       result = self.rpc.call_test_delay(self.op.on_nodes, self.op.duration)
15164       for node, node_result in result.items():
15165         node_result.Raise("Failure during rpc call to node %s" % node)
15166
15167   def Exec(self, feedback_fn):
15168     """Execute the test delay opcode, with the wanted repetitions.
15169
15170     """
15171     if self.op.repeat == 0:
15172       self._TestDelay()
15173     else:
15174       top_value = self.op.repeat - 1
15175       for i in range(self.op.repeat):
15176         self.LogInfo("Test delay iteration %d/%d" % (i, top_value))
15177         self._TestDelay()
15178
15179
15180 class LUTestJqueue(NoHooksLU):
15181   """Utility LU to test some aspects of the job queue.
15182
15183   """
15184   REQ_BGL = False
15185
15186   # Must be lower than default timeout for WaitForJobChange to see whether it
15187   # notices changed jobs
15188   _CLIENT_CONNECT_TIMEOUT = 20.0
15189   _CLIENT_CONFIRM_TIMEOUT = 60.0
15190
15191   @classmethod
15192   def _NotifyUsingSocket(cls, cb, errcls):
15193     """Opens a Unix socket and waits for another program to connect.
15194
15195     @type cb: callable
15196     @param cb: Callback to send socket name to client
15197     @type errcls: class
15198     @param errcls: Exception class to use for errors
15199
15200     """
15201     # Using a temporary directory as there's no easy way to create temporary
15202     # sockets without writing a custom loop around tempfile.mktemp and
15203     # socket.bind
15204     tmpdir = tempfile.mkdtemp()
15205     try:
15206       tmpsock = utils.PathJoin(tmpdir, "sock")
15207
15208       logging.debug("Creating temporary socket at %s", tmpsock)
15209       sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
15210       try:
15211         sock.bind(tmpsock)
15212         sock.listen(1)
15213
15214         # Send details to client
15215         cb(tmpsock)
15216
15217         # Wait for client to connect before continuing
15218         sock.settimeout(cls._CLIENT_CONNECT_TIMEOUT)
15219         try:
15220           (conn, _) = sock.accept()
15221         except socket.error, err:
15222           raise errcls("Client didn't connect in time (%s)" % err)
15223       finally:
15224         sock.close()
15225     finally:
15226       # Remove as soon as client is connected
15227       shutil.rmtree(tmpdir)
15228
15229     # Wait for client to close
15230     try:
15231       try:
15232         # pylint: disable=E1101
15233         # Instance of '_socketobject' has no ... member
15234         conn.settimeout(cls._CLIENT_CONFIRM_TIMEOUT)
15235         conn.recv(1)
15236       except socket.error, err:
15237         raise errcls("Client failed to confirm notification (%s)" % err)
15238     finally:
15239       conn.close()
15240
15241   def _SendNotification(self, test, arg, sockname):
15242     """Sends a notification to the client.
15243
15244     @type test: string
15245     @param test: Test name
15246     @param arg: Test argument (depends on test)
15247     @type sockname: string
15248     @param sockname: Socket path
15249
15250     """
15251     self.Log(constants.ELOG_JQUEUE_TEST, (sockname, test, arg))
15252
15253   def _Notify(self, prereq, test, arg):
15254     """Notifies the client of a test.
15255
15256     @type prereq: bool
15257     @param prereq: Whether this is a prereq-phase test
15258     @type test: string
15259     @param test: Test name
15260     @param arg: Test argument (depends on test)
15261
15262     """
15263     if prereq:
15264       errcls = errors.OpPrereqError
15265     else:
15266       errcls = errors.OpExecError
15267
15268     return self._NotifyUsingSocket(compat.partial(self._SendNotification,
15269                                                   test, arg),
15270                                    errcls)
15271
15272   def CheckArguments(self):
15273     self.checkargs_calls = getattr(self, "checkargs_calls", 0) + 1
15274     self.expandnames_calls = 0
15275
15276   def ExpandNames(self):
15277     checkargs_calls = getattr(self, "checkargs_calls", 0)
15278     if checkargs_calls < 1:
15279       raise errors.ProgrammerError("CheckArguments was not called")
15280
15281     self.expandnames_calls += 1
15282
15283     if self.op.notify_waitlock:
15284       self._Notify(True, constants.JQT_EXPANDNAMES, None)
15285
15286     self.LogInfo("Expanding names")
15287
15288     # Get lock on master node (just to get a lock, not for a particular reason)
15289     self.needed_locks = {
15290       locking.LEVEL_NODE: self.cfg.GetMasterNode(),
15291       }
15292
15293   def Exec(self, feedback_fn):
15294     if self.expandnames_calls < 1:
15295       raise errors.ProgrammerError("ExpandNames was not called")
15296
15297     if self.op.notify_exec:
15298       self._Notify(False, constants.JQT_EXEC, None)
15299
15300     self.LogInfo("Executing")
15301
15302     if self.op.log_messages:
15303       self._Notify(False, constants.JQT_STARTMSG, len(self.op.log_messages))
15304       for idx, msg in enumerate(self.op.log_messages):
15305         self.LogInfo("Sending log message %s", idx + 1)
15306         feedback_fn(constants.JQT_MSGPREFIX + msg)
15307         # Report how many test messages have been sent
15308         self._Notify(False, constants.JQT_LOGMSG, idx + 1)
15309
15310     if self.op.fail:
15311       raise errors.OpExecError("Opcode failure was requested")
15312
15313     return True
15314
15315
15316 class IAllocator(object):
15317   """IAllocator framework.
15318
15319   An IAllocator instance has three sets of attributes:
15320     - cfg that is needed to query the cluster
15321     - input data (all members of the _KEYS class attribute are required)
15322     - four buffer attributes (in|out_data|text), that represent the
15323       input (to the external script) in text and data structure format,
15324       and the output from it, again in two formats
15325     - the result variables from the script (success, info, nodes) for
15326       easy usage
15327
15328   """
15329   # pylint: disable=R0902
15330   # lots of instance attributes
15331
15332   def __init__(self, cfg, rpc_runner, mode, **kwargs):
15333     self.cfg = cfg
15334     self.rpc = rpc_runner
15335     # init buffer variables
15336     self.in_text = self.out_text = self.in_data = self.out_data = None
15337     # init all input fields so that pylint is happy
15338     self.mode = mode
15339     self.memory = self.disks = self.disk_template = self.spindle_use = None
15340     self.os = self.tags = self.nics = self.vcpus = None
15341     self.hypervisor = None
15342     self.relocate_from = None
15343     self.name = None
15344     self.instances = None
15345     self.evac_mode = None
15346     self.target_groups = []
15347     # computed fields
15348     self.required_nodes = None
15349     # init result fields
15350     self.success = self.info = self.result = None
15351
15352     try:
15353       (fn, keydata, self._result_check) = self._MODE_DATA[self.mode]
15354     except KeyError:
15355       raise errors.ProgrammerError("Unknown mode '%s' passed to the"
15356                                    " IAllocator" % self.mode)
15357
15358     keyset = [n for (n, _) in keydata]
15359
15360     for key in kwargs:
15361       if key not in keyset:
15362         raise errors.ProgrammerError("Invalid input parameter '%s' to"
15363                                      " IAllocator" % key)
15364       setattr(self, key, kwargs[key])
15365
15366     for key in keyset:
15367       if key not in kwargs:
15368         raise errors.ProgrammerError("Missing input parameter '%s' to"
15369                                      " IAllocator" % key)
15370     self._BuildInputData(compat.partial(fn, self), keydata)
15371
15372   def _ComputeClusterData(self):
15373     """Compute the generic allocator input data.
15374
15375     This is the data that is independent of the actual operation.
15376
15377     """
15378     cfg = self.cfg
15379     cluster_info = cfg.GetClusterInfo()
15380     # cluster data
15381     data = {
15382       "version": constants.IALLOCATOR_VERSION,
15383       "cluster_name": cfg.GetClusterName(),
15384       "cluster_tags": list(cluster_info.GetTags()),
15385       "enabled_hypervisors": list(cluster_info.enabled_hypervisors),
15386       "ipolicy": cluster_info.ipolicy,
15387       }
15388     ninfo = cfg.GetAllNodesInfo()
15389     iinfo = cfg.GetAllInstancesInfo().values()
15390     i_list = [(inst, cluster_info.FillBE(inst)) for inst in iinfo]
15391
15392     # node data
15393     node_list = [n.name for n in ninfo.values() if n.vm_capable]
15394
15395     if self.mode == constants.IALLOCATOR_MODE_ALLOC:
15396       hypervisor_name = self.hypervisor
15397     elif self.mode == constants.IALLOCATOR_MODE_RELOC:
15398       hypervisor_name = cfg.GetInstanceInfo(self.name).hypervisor
15399     else:
15400       hypervisor_name = cluster_info.primary_hypervisor
15401
15402     node_data = self.rpc.call_node_info(node_list, [cfg.GetVGName()],
15403                                         [hypervisor_name])
15404     node_iinfo = \
15405       self.rpc.call_all_instances_info(node_list,
15406                                        cluster_info.enabled_hypervisors)
15407
15408     data["nodegroups"] = self._ComputeNodeGroupData(cfg)
15409
15410     config_ndata = self._ComputeBasicNodeData(cfg, ninfo)
15411     data["nodes"] = self._ComputeDynamicNodeData(ninfo, node_data, node_iinfo,
15412                                                  i_list, config_ndata)
15413     assert len(data["nodes"]) == len(ninfo), \
15414         "Incomplete node data computed"
15415
15416     data["instances"] = self._ComputeInstanceData(cluster_info, i_list)
15417
15418     self.in_data = data
15419
15420   @staticmethod
15421   def _ComputeNodeGroupData(cfg):
15422     """Compute node groups data.
15423
15424     """
15425     cluster = cfg.GetClusterInfo()
15426     ng = dict((guuid, {
15427       "name": gdata.name,
15428       "alloc_policy": gdata.alloc_policy,
15429       "ipolicy": _CalculateGroupIPolicy(cluster, gdata),
15430       })
15431       for guuid, gdata in cfg.GetAllNodeGroupsInfo().items())
15432
15433     return ng
15434
15435   @staticmethod
15436   def _ComputeBasicNodeData(cfg, node_cfg):
15437     """Compute global node data.
15438
15439     @rtype: dict
15440     @returns: a dict of name: (node dict, node config)
15441
15442     """
15443     # fill in static (config-based) values
15444     node_results = dict((ninfo.name, {
15445       "tags": list(ninfo.GetTags()),
15446       "primary_ip": ninfo.primary_ip,
15447       "secondary_ip": ninfo.secondary_ip,
15448       "offline": ninfo.offline,
15449       "drained": ninfo.drained,
15450       "master_candidate": ninfo.master_candidate,
15451       "group": ninfo.group,
15452       "master_capable": ninfo.master_capable,
15453       "vm_capable": ninfo.vm_capable,
15454       "ndparams": cfg.GetNdParams(ninfo),
15455       })
15456       for ninfo in node_cfg.values())
15457
15458     return node_results
15459
15460   @staticmethod
15461   def _ComputeDynamicNodeData(node_cfg, node_data, node_iinfo, i_list,
15462                               node_results):
15463     """Compute global node data.
15464
15465     @param node_results: the basic node structures as filled from the config
15466
15467     """
15468     #TODO(dynmem): compute the right data on MAX and MIN memory
15469     # make a copy of the current dict
15470     node_results = dict(node_results)
15471     for nname, nresult in node_data.items():
15472       assert nname in node_results, "Missing basic data for node %s" % nname
15473       ninfo = node_cfg[nname]
15474
15475       if not (ninfo.offline or ninfo.drained):
15476         nresult.Raise("Can't get data for node %s" % nname)
15477         node_iinfo[nname].Raise("Can't get node instance info from node %s" %
15478                                 nname)
15479         remote_info = _MakeLegacyNodeInfo(nresult.payload)
15480
15481         for attr in ["memory_total", "memory_free", "memory_dom0",
15482                      "vg_size", "vg_free", "cpu_total"]:
15483           if attr not in remote_info:
15484             raise errors.OpExecError("Node '%s' didn't return attribute"
15485                                      " '%s'" % (nname, attr))
15486           if not isinstance(remote_info[attr], int):
15487             raise errors.OpExecError("Node '%s' returned invalid value"
15488                                      " for '%s': %s" %
15489                                      (nname, attr, remote_info[attr]))
15490         # compute memory used by primary instances
15491         i_p_mem = i_p_up_mem = 0
15492         for iinfo, beinfo in i_list:
15493           if iinfo.primary_node == nname:
15494             i_p_mem += beinfo[constants.BE_MAXMEM]
15495             if iinfo.name not in node_iinfo[nname].payload:
15496               i_used_mem = 0
15497             else:
15498               i_used_mem = int(node_iinfo[nname].payload[iinfo.name]["memory"])
15499             i_mem_diff = beinfo[constants.BE_MAXMEM] - i_used_mem
15500             remote_info["memory_free"] -= max(0, i_mem_diff)
15501
15502             if iinfo.admin_state == constants.ADMINST_UP:
15503               i_p_up_mem += beinfo[constants.BE_MAXMEM]
15504
15505         # compute memory used by instances
15506         pnr_dyn = {
15507           "total_memory": remote_info["memory_total"],
15508           "reserved_memory": remote_info["memory_dom0"],
15509           "free_memory": remote_info["memory_free"],
15510           "total_disk": remote_info["vg_size"],
15511           "free_disk": remote_info["vg_free"],
15512           "total_cpus": remote_info["cpu_total"],
15513           "i_pri_memory": i_p_mem,
15514           "i_pri_up_memory": i_p_up_mem,
15515           }
15516         pnr_dyn.update(node_results[nname])
15517         node_results[nname] = pnr_dyn
15518
15519     return node_results
15520
15521   @staticmethod
15522   def _ComputeInstanceData(cluster_info, i_list):
15523     """Compute global instance data.
15524
15525     """
15526     instance_data = {}
15527     for iinfo, beinfo in i_list:
15528       nic_data = []
15529       for nic in iinfo.nics:
15530         filled_params = cluster_info.SimpleFillNIC(nic.nicparams)
15531         nic_dict = {
15532           "mac": nic.mac,
15533           "ip": nic.ip,
15534           "mode": filled_params[constants.NIC_MODE],
15535           "link": filled_params[constants.NIC_LINK],
15536           "network": nic.network,
15537           }
15538         if filled_params[constants.NIC_MODE] == constants.NIC_MODE_BRIDGED:
15539           nic_dict["bridge"] = filled_params[constants.NIC_LINK]
15540         nic_data.append(nic_dict)
15541       pir = {
15542         "tags": list(iinfo.GetTags()),
15543         "admin_state": iinfo.admin_state,
15544         "vcpus": beinfo[constants.BE_VCPUS],
15545         "memory": beinfo[constants.BE_MAXMEM],
15546         "spindle_use": beinfo[constants.BE_SPINDLE_USE],
15547         "os": iinfo.os,
15548         "nodes": [iinfo.primary_node] + list(iinfo.secondary_nodes),
15549         "nics": nic_data,
15550         "disks": [{constants.IDISK_SIZE: dsk.size,
15551                    constants.IDISK_MODE: dsk.mode}
15552                   for dsk in iinfo.disks],
15553         "disk_template": iinfo.disk_template,
15554         "hypervisor": iinfo.hypervisor,
15555         }
15556       pir["disk_space_total"] = _ComputeDiskSize(iinfo.disk_template,
15557                                                  pir["disks"])
15558       instance_data[iinfo.name] = pir
15559
15560     return instance_data
15561
15562   def _AddNewInstance(self):
15563     """Add new instance data to allocator structure.
15564
15565     This in combination with _AllocatorGetClusterData will create the
15566     correct structure needed as input for the allocator.
15567
15568     The checks for the completeness of the opcode must have already been
15569     done.
15570
15571     """
15572     disk_space = _ComputeDiskSize(self.disk_template, self.disks)
15573
15574     if self.disk_template in constants.DTS_INT_MIRROR:
15575       self.required_nodes = 2
15576     else:
15577       self.required_nodes = 1
15578
15579     request = {
15580       "name": self.name,
15581       "disk_template": self.disk_template,
15582       "tags": self.tags,
15583       "os": self.os,
15584       "vcpus": self.vcpus,
15585       "memory": self.memory,
15586       "spindle_use": self.spindle_use,
15587       "disks": self.disks,
15588       "disk_space_total": disk_space,
15589       "nics": self.nics,
15590       "required_nodes": self.required_nodes,
15591       "hypervisor": self.hypervisor,
15592       }
15593
15594     return request
15595
15596   def _AddRelocateInstance(self):
15597     """Add relocate instance data to allocator structure.
15598
15599     This in combination with _IAllocatorGetClusterData will create the
15600     correct structure needed as input for the allocator.
15601
15602     The checks for the completeness of the opcode must have already been
15603     done.
15604
15605     """
15606     instance = self.cfg.GetInstanceInfo(self.name)
15607     if instance is None:
15608       raise errors.ProgrammerError("Unknown instance '%s' passed to"
15609                                    " IAllocator" % self.name)
15610
15611     if instance.disk_template not in constants.DTS_MIRRORED:
15612       raise errors.OpPrereqError("Can't relocate non-mirrored instances",
15613                                  errors.ECODE_INVAL)
15614
15615     if instance.disk_template in constants.DTS_INT_MIRROR and \
15616         len(instance.secondary_nodes) != 1:
15617       raise errors.OpPrereqError("Instance has not exactly one secondary node",
15618                                  errors.ECODE_STATE)
15619
15620     self.required_nodes = 1
15621     disk_sizes = [{constants.IDISK_SIZE: disk.size} for disk in instance.disks]
15622     disk_space = _ComputeDiskSize(instance.disk_template, disk_sizes)
15623
15624     request = {
15625       "name": self.name,
15626       "disk_space_total": disk_space,
15627       "required_nodes": self.required_nodes,
15628       "relocate_from": self.relocate_from,
15629       }
15630     return request
15631
15632   def _AddNodeEvacuate(self):
15633     """Get data for node-evacuate requests.
15634
15635     """
15636     return {
15637       "instances": self.instances,
15638       "evac_mode": self.evac_mode,
15639       }
15640
15641   def _AddChangeGroup(self):
15642     """Get data for node-evacuate requests.
15643
15644     """
15645     return {
15646       "instances": self.instances,
15647       "target_groups": self.target_groups,
15648       }
15649
15650   def _BuildInputData(self, fn, keydata):
15651     """Build input data structures.
15652
15653     """
15654     self._ComputeClusterData()
15655
15656     request = fn()
15657     request["type"] = self.mode
15658     for keyname, keytype in keydata:
15659       if keyname not in request:
15660         raise errors.ProgrammerError("Request parameter %s is missing" %
15661                                      keyname)
15662       val = request[keyname]
15663       if not keytype(val):
15664         raise errors.ProgrammerError("Request parameter %s doesn't pass"
15665                                      " validation, value %s, expected"
15666                                      " type %s" % (keyname, val, keytype))
15667     self.in_data["request"] = request
15668
15669     self.in_text = serializer.Dump(self.in_data)
15670
15671   _STRING_LIST = ht.TListOf(ht.TString)
15672   _JOB_LIST = ht.TListOf(ht.TListOf(ht.TStrictDict(True, False, {
15673      # pylint: disable=E1101
15674      # Class '...' has no 'OP_ID' member
15675      "OP_ID": ht.TElemOf([opcodes.OpInstanceFailover.OP_ID,
15676                           opcodes.OpInstanceMigrate.OP_ID,
15677                           opcodes.OpInstanceReplaceDisks.OP_ID])
15678      })))
15679
15680   _NEVAC_MOVED = \
15681     ht.TListOf(ht.TAnd(ht.TIsLength(3),
15682                        ht.TItems([ht.TNonEmptyString,
15683                                   ht.TNonEmptyString,
15684                                   ht.TListOf(ht.TNonEmptyString),
15685                                  ])))
15686   _NEVAC_FAILED = \
15687     ht.TListOf(ht.TAnd(ht.TIsLength(2),
15688                        ht.TItems([ht.TNonEmptyString,
15689                                   ht.TMaybeString,
15690                                  ])))
15691   _NEVAC_RESULT = ht.TAnd(ht.TIsLength(3),
15692                           ht.TItems([_NEVAC_MOVED, _NEVAC_FAILED, _JOB_LIST]))
15693
15694   _MODE_DATA = {
15695     constants.IALLOCATOR_MODE_ALLOC:
15696       (_AddNewInstance,
15697        [
15698         ("name", ht.TString),
15699         ("memory", ht.TInt),
15700         ("spindle_use", ht.TInt),
15701         ("disks", ht.TListOf(ht.TDict)),
15702         ("disk_template", ht.TString),
15703         ("os", ht.TString),
15704         ("tags", _STRING_LIST),
15705         ("nics", ht.TListOf(ht.TDict)),
15706         ("vcpus", ht.TInt),
15707         ("hypervisor", ht.TString),
15708         ], ht.TList),
15709     constants.IALLOCATOR_MODE_RELOC:
15710       (_AddRelocateInstance,
15711        [("name", ht.TString), ("relocate_from", _STRING_LIST)],
15712        ht.TList),
15713      constants.IALLOCATOR_MODE_NODE_EVAC:
15714       (_AddNodeEvacuate, [
15715         ("instances", _STRING_LIST),
15716         ("evac_mode", ht.TElemOf(constants.IALLOCATOR_NEVAC_MODES)),
15717         ], _NEVAC_RESULT),
15718      constants.IALLOCATOR_MODE_CHG_GROUP:
15719       (_AddChangeGroup, [
15720         ("instances", _STRING_LIST),
15721         ("target_groups", _STRING_LIST),
15722         ], _NEVAC_RESULT),
15723     }
15724
15725   def Run(self, name, validate=True, call_fn=None):
15726     """Run an instance allocator and return the results.
15727
15728     """
15729     if call_fn is None:
15730       call_fn = self.rpc.call_iallocator_runner
15731
15732     result = call_fn(self.cfg.GetMasterNode(), name, self.in_text)
15733     result.Raise("Failure while running the iallocator script")
15734
15735     self.out_text = result.payload
15736     if validate:
15737       self._ValidateResult()
15738
15739   def _ValidateResult(self):
15740     """Process the allocator results.
15741
15742     This will process and if successful save the result in
15743     self.out_data and the other parameters.
15744
15745     """
15746     try:
15747       rdict = serializer.Load(self.out_text)
15748     except Exception, err:
15749       raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
15750
15751     if not isinstance(rdict, dict):
15752       raise errors.OpExecError("Can't parse iallocator results: not a dict")
15753
15754     # TODO: remove backwards compatiblity in later versions
15755     if "nodes" in rdict and "result" not in rdict:
15756       rdict["result"] = rdict["nodes"]
15757       del rdict["nodes"]
15758
15759     for key in "success", "info", "result":
15760       if key not in rdict:
15761         raise errors.OpExecError("Can't parse iallocator results:"
15762                                  " missing key '%s'" % key)
15763       setattr(self, key, rdict[key])
15764
15765     if not self._result_check(self.result):
15766       raise errors.OpExecError("Iallocator returned invalid result,"
15767                                " expected %s, got %s" %
15768                                (self._result_check, self.result),
15769                                errors.ECODE_INVAL)
15770
15771     if self.mode == constants.IALLOCATOR_MODE_RELOC:
15772       assert self.relocate_from is not None
15773       assert self.required_nodes == 1
15774
15775       node2group = dict((name, ndata["group"])
15776                         for (name, ndata) in self.in_data["nodes"].items())
15777
15778       fn = compat.partial(self._NodesToGroups, node2group,
15779                           self.in_data["nodegroups"])
15780
15781       instance = self.cfg.GetInstanceInfo(self.name)
15782       request_groups = fn(self.relocate_from + [instance.primary_node])
15783       result_groups = fn(rdict["result"] + [instance.primary_node])
15784
15785       if self.success and not set(result_groups).issubset(request_groups):
15786         raise errors.OpExecError("Groups of nodes returned by iallocator (%s)"
15787                                  " differ from original groups (%s)" %
15788                                  (utils.CommaJoin(result_groups),
15789                                   utils.CommaJoin(request_groups)))
15790
15791     elif self.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
15792       assert self.evac_mode in constants.IALLOCATOR_NEVAC_MODES
15793
15794     self.out_data = rdict
15795
15796   @staticmethod
15797   def _NodesToGroups(node2group, groups, nodes):
15798     """Returns a list of unique group names for a list of nodes.
15799
15800     @type node2group: dict
15801     @param node2group: Map from node name to group UUID
15802     @type groups: dict
15803     @param groups: Group information
15804     @type nodes: list
15805     @param nodes: Node names
15806
15807     """
15808     result = set()
15809
15810     for node in nodes:
15811       try:
15812         group_uuid = node2group[node]
15813       except KeyError:
15814         # Ignore unknown node
15815         pass
15816       else:
15817         try:
15818           group = groups[group_uuid]
15819         except KeyError:
15820           # Can't find group, let's use UUID
15821           group_name = group_uuid
15822         else:
15823           group_name = group["name"]
15824
15825         result.add(group_name)
15826
15827     return sorted(result)
15828
15829
15830 class LUTestAllocator(NoHooksLU):
15831   """Run allocator tests.
15832
15833   This LU runs the allocator tests
15834
15835   """
15836   def CheckPrereq(self):
15837     """Check prerequisites.
15838
15839     This checks the opcode parameters depending on the director and mode test.
15840
15841     """
15842     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
15843       for attr in ["memory", "disks", "disk_template",
15844                    "os", "tags", "nics", "vcpus"]:
15845         if not hasattr(self.op, attr):
15846           raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
15847                                      attr, errors.ECODE_INVAL)
15848       iname = self.cfg.ExpandInstanceName(self.op.name)
15849       if iname is not None:
15850         raise errors.OpPrereqError("Instance '%s' already in the cluster" %
15851                                    iname, errors.ECODE_EXISTS)
15852       if not isinstance(self.op.nics, list):
15853         raise errors.OpPrereqError("Invalid parameter 'nics'",
15854                                    errors.ECODE_INVAL)
15855       if not isinstance(self.op.disks, list):
15856         raise errors.OpPrereqError("Invalid parameter 'disks'",
15857                                    errors.ECODE_INVAL)
15858       for row in self.op.disks:
15859         if (not isinstance(row, dict) or
15860             constants.IDISK_SIZE not in row or
15861             not isinstance(row[constants.IDISK_SIZE], int) or
15862             constants.IDISK_MODE not in row or
15863             row[constants.IDISK_MODE] not in constants.DISK_ACCESS_SET):
15864           raise errors.OpPrereqError("Invalid contents of the 'disks'"
15865                                      " parameter", errors.ECODE_INVAL)
15866       if self.op.hypervisor is None:
15867         self.op.hypervisor = self.cfg.GetHypervisorType()
15868     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
15869       fname = _ExpandInstanceName(self.cfg, self.op.name)
15870       self.op.name = fname
15871       self.relocate_from = \
15872           list(self.cfg.GetInstanceInfo(fname).secondary_nodes)
15873     elif self.op.mode in (constants.IALLOCATOR_MODE_CHG_GROUP,
15874                           constants.IALLOCATOR_MODE_NODE_EVAC):
15875       if not self.op.instances:
15876         raise errors.OpPrereqError("Missing instances", errors.ECODE_INVAL)
15877       self.op.instances = _GetWantedInstances(self, self.op.instances)
15878     else:
15879       raise errors.OpPrereqError("Invalid test allocator mode '%s'" %
15880                                  self.op.mode, errors.ECODE_INVAL)
15881
15882     if self.op.direction == constants.IALLOCATOR_DIR_OUT:
15883       if self.op.allocator is None:
15884         raise errors.OpPrereqError("Missing allocator name",
15885                                    errors.ECODE_INVAL)
15886     elif self.op.direction != constants.IALLOCATOR_DIR_IN:
15887       raise errors.OpPrereqError("Wrong allocator test '%s'" %
15888                                  self.op.direction, errors.ECODE_INVAL)
15889
15890   def Exec(self, feedback_fn):
15891     """Run the allocator test.
15892
15893     """
15894     if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
15895       ial = IAllocator(self.cfg, self.rpc,
15896                        mode=self.op.mode,
15897                        name=self.op.name,
15898                        memory=self.op.memory,
15899                        disks=self.op.disks,
15900                        disk_template=self.op.disk_template,
15901                        os=self.op.os,
15902                        tags=self.op.tags,
15903                        nics=self.op.nics,
15904                        vcpus=self.op.vcpus,
15905                        hypervisor=self.op.hypervisor,
15906                        spindle_use=self.op.spindle_use,
15907                        )
15908     elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
15909       ial = IAllocator(self.cfg, self.rpc,
15910                        mode=self.op.mode,
15911                        name=self.op.name,
15912                        relocate_from=list(self.relocate_from),
15913                        )
15914     elif self.op.mode == constants.IALLOCATOR_MODE_CHG_GROUP:
15915       ial = IAllocator(self.cfg, self.rpc,
15916                        mode=self.op.mode,
15917                        instances=self.op.instances,
15918                        target_groups=self.op.target_groups)
15919     elif self.op.mode == constants.IALLOCATOR_MODE_NODE_EVAC:
15920       ial = IAllocator(self.cfg, self.rpc,
15921                        mode=self.op.mode,
15922                        instances=self.op.instances,
15923                        evac_mode=self.op.evac_mode)
15924     else:
15925       raise errors.ProgrammerError("Uncatched mode %s in"
15926                                    " LUTestAllocator.Exec", self.op.mode)
15927
15928     if self.op.direction == constants.IALLOCATOR_DIR_IN:
15929       result = ial.in_text
15930     else:
15931       ial.Run(self.op.allocator, validate=False)
15932       result = ial.out_text
15933     return result
15934
15935 # Network LUs
15936 class LUNetworkAdd(LogicalUnit):
15937   """Logical unit for creating networks.
15938
15939   """
15940   HPATH = "network-add"
15941   HTYPE = constants.HTYPE_NETWORK
15942   REQ_BGL = False
15943
15944   def BuildHooksNodes(self):
15945     """Build hooks nodes.
15946
15947     """
15948     mn = self.cfg.GetMasterNode()
15949     return ([mn], [mn])
15950
15951   def ExpandNames(self):
15952     self.network_uuid = self.cfg.GenerateUniqueID(self.proc.GetECId())
15953     self.needed_locks = {}
15954     if self.op.conflicts_check:
15955       self.needed_locks = {
15956         locking.LEVEL_NODE: locking.ALL_SET,
15957         }
15958       self.share_locks[locking.LEVEL_NODE] = 1
15959     self.add_locks[locking.LEVEL_NETWORK] = self.network_uuid
15960
15961   def CheckPrereq(self):
15962     """Check prerequisites.
15963
15964     This checks that the given group name is not an existing node group
15965     already.
15966
15967     """
15968     if self.op.network is None:
15969       raise errors.OpPrereqError("Network must be given",
15970                                  errors.ECODE_INVAL)
15971
15972     uuid = self.cfg.LookupNetwork(self.op.network_name)
15973
15974     if uuid:
15975       raise errors.OpPrereqError("Network '%s' already defined" %
15976                                  self.op.network, errors.ECODE_EXISTS)
15977
15978     if self.op.mac_prefix:
15979       utils.NormalizeAndValidateMac(self.op.mac_prefix+":00:00:00")
15980
15981     # Check tag validity
15982     for tag in self.op.tags:
15983       objects.TaggableObject.ValidateTag(tag)
15984
15985
15986   def BuildHooksEnv(self):
15987     """Build hooks env.
15988
15989     """
15990     args = {
15991       "name": self.op.network_name,
15992       "network": self.op.network,
15993       "gateway": self.op.gateway,
15994       "network6": self.op.network6,
15995       "gateway6": self.op.gateway6,
15996       "mac_prefix": self.op.mac_prefix,
15997       "network_type": self.op.network_type,
15998       "tags": self.op.tags,
15999       "serial_no": 1,
16000       }
16001     return _BuildNetworkHookEnv(**args)
16002
16003   def Exec(self, feedback_fn):
16004     """Add the ip pool to the cluster.
16005
16006     """
16007     nobj = objects.Network(name=self.op.network_name,
16008                            network=self.op.network,
16009                            gateway=self.op.gateway,
16010                            network6=self.op.network6,
16011                            gateway6=self.op.gateway6,
16012                            mac_prefix=self.op.mac_prefix,
16013                            network_type=self.op.network_type,
16014                            uuid=self.network_uuid,
16015                            family=4)
16016     # Initialize the associated address pool
16017     try:
16018       pool = network.AddressPool.InitializeNetwork(nobj)
16019     except errors.AddressPoolError, e:
16020       raise errors.OpExecError("Cannot create IP pool for this network. %s" % e)
16021
16022     # Check if we need to reserve the nodes and the cluster master IP
16023     # These may not be allocated to any instances in routed mode, as
16024     # they wouldn't function anyway.
16025     if self.op.conflicts_check:
16026       for node in self.cfg.GetAllNodesInfo().values():
16027         for ip in [node.primary_ip, node.secondary_ip]:
16028           try:
16029             pool.Reserve(ip)
16030             self.LogInfo("Reserved node %s's IP (%s)", node.name, ip)
16031
16032           except errors.AddressPoolError:
16033             pass
16034
16035       master_ip = self.cfg.GetClusterInfo().master_ip
16036       try:
16037         pool.Reserve(master_ip)
16038         self.LogInfo("Reserved cluster master IP (%s)", master_ip)
16039       except errors.AddressPoolError:
16040         pass
16041
16042     if self.op.add_reserved_ips:
16043       for ip in self.op.add_reserved_ips:
16044         try:
16045           pool.Reserve(ip, external=True)
16046         except errors.AddressPoolError, e:
16047           raise errors.OpExecError("Cannot reserve IP %s. %s " % (ip, e))
16048
16049     if self.op.tags:
16050       for tag in self.op.tags:
16051         nobj.AddTag(tag)
16052
16053     self.cfg.AddNetwork(nobj, self.proc.GetECId(), check_uuid=False)
16054     del self.remove_locks[locking.LEVEL_NETWORK]
16055
16056
16057 class LUNetworkRemove(LogicalUnit):
16058   HPATH = "network-remove"
16059   HTYPE = constants.HTYPE_NETWORK
16060   REQ_BGL = False
16061
16062   def ExpandNames(self):
16063     self.network_uuid = self.cfg.LookupNetwork(self.op.network_name)
16064
16065     if not self.network_uuid:
16066       raise errors.OpPrereqError("Network %s not found" % self.op.network_name,
16067                                  errors.ECODE_INVAL)
16068     self.needed_locks = {
16069       locking.LEVEL_NETWORK: [self.network_uuid],
16070       locking.LEVEL_NODEGROUP: locking.ALL_SET,
16071       }
16072     self.share_locks[locking.LEVEL_NODEGROUP] = 1
16073
16074   def CheckPrereq(self):
16075     """Check prerequisites.
16076
16077     This checks that the given network name exists as a network, that is
16078     empty (i.e., contains no nodes), and that is not the last group of the
16079     cluster.
16080
16081     """
16082
16083     # Verify that the network is not conncted.
16084     node_groups = [group.name
16085                    for group in self.cfg.GetAllNodeGroupsInfo().values()
16086                    for network in group.networks.keys()
16087                    if network == self.network_uuid]
16088
16089     if node_groups:
16090       self.LogWarning("Nework '%s' is connected to the following"
16091                       " node groups: %s" % (self.op.network_name,
16092                       utils.CommaJoin(utils.NiceSort(node_groups))))
16093       raise errors.OpPrereqError("Network still connected",
16094                                  errors.ECODE_STATE)
16095
16096   def BuildHooksEnv(self):
16097     """Build hooks env.
16098
16099     """
16100     return {
16101       "NETWORK_NAME": self.op.network_name,
16102       }
16103
16104   def BuildHooksNodes(self):
16105     """Build hooks nodes.
16106
16107     """
16108     mn = self.cfg.GetMasterNode()
16109     return ([mn], [mn])
16110
16111   def Exec(self, feedback_fn):
16112     """Remove the network.
16113
16114     """
16115     try:
16116       self.cfg.RemoveNetwork(self.network_uuid)
16117     except errors.ConfigurationError:
16118       raise errors.OpExecError("Network '%s' with UUID %s disappeared" %
16119                                (self.op.network_name, self.network_uuid))
16120
16121
16122 class LUNetworkSetParams(LogicalUnit):
16123   """Modifies the parameters of a network.
16124
16125   """
16126   HPATH = "network-modify"
16127   HTYPE = constants.HTYPE_NETWORK
16128   REQ_BGL = False
16129
16130   def CheckArguments(self):
16131     if (self.op.gateway and
16132         (self.op.add_reserved_ips or self.op.remove_reserved_ips)):
16133       raise errors.OpPrereqError("Cannot modify gateway and reserved ips"
16134                                  " at once", errors.ECODE_INVAL)
16135
16136
16137   def ExpandNames(self):
16138     self.network_uuid = self.cfg.LookupNetwork(self.op.network_name)
16139     self.network = self.cfg.GetNetwork(self.network_uuid)
16140     if self.network is None:
16141       raise errors.OpPrereqError("Could not retrieve network '%s' (UUID: %s)" %
16142                                  (self.op.network_name, self.network_uuid),
16143                                  errors.ECODE_INVAL)
16144     self.needed_locks = {
16145       locking.LEVEL_NETWORK: [self.network_uuid],
16146       }
16147
16148   def CheckPrereq(self):
16149     """Check prerequisites.
16150
16151     """
16152     self.gateway = self.network.gateway
16153     self.network_type = self.network.network_type
16154     self.mac_prefix = self.network.mac_prefix
16155     self.network6 = self.network.network6
16156     self.gateway6 = self.network.gateway6
16157     self.tags = self.network.tags
16158
16159     self.pool = network.AddressPool(self.network)
16160
16161     if self.op.gateway:
16162       if self.op.gateway == constants.VALUE_NONE:
16163         self.gateway = None
16164       else:
16165         self.gateway = self.op.gateway
16166         if self.pool.IsReserved(self.gateway):
16167           raise errors.OpPrereqError("%s is already reserved" %
16168                                      self.gateway, errors.ECODE_INVAL)
16169
16170     if self.op.network_type:
16171       if self.op.network_type == constants.VALUE_NONE:
16172         self.network_type = None
16173       else:
16174         self.network_type = self.op.network_type
16175
16176     if self.op.mac_prefix:
16177       if self.op.mac_prefix == constants.VALUE_NONE:
16178         self.mac_prefix = None
16179       else:
16180         utils.NormalizeAndValidateMac(self.op.mac_prefix+":00:00:00")
16181         self.mac_prefix = self.op.mac_prefix
16182
16183     if self.op.gateway6:
16184       if self.op.gateway6 == constants.VALUE_NONE:
16185         self.gateway6 = None
16186       else:
16187         self.gateway6 = self.op.gateway6
16188
16189     if self.op.network6:
16190       if self.op.network6 == constants.VALUE_NONE:
16191         self.network6 = None
16192       else:
16193         self.network6 = self.op.network6
16194
16195
16196
16197   def BuildHooksEnv(self):
16198     """Build hooks env.
16199
16200     """
16201     args = {
16202       "name": self.op.network_name,
16203       "network": self.network.network,
16204       "gateway": self.gateway,
16205       "network6": self.network6,
16206       "gateway6": self.gateway6,
16207       "mac_prefix": self.mac_prefix,
16208       "network_type": self.network_type,
16209       "tags": self.tags,
16210       "serial_no": self.network.serial_no,
16211       }
16212     return _BuildNetworkHookEnv(**args)
16213
16214   def BuildHooksNodes(self):
16215     """Build hooks nodes.
16216
16217     """
16218     mn = self.cfg.GetMasterNode()
16219     return ([mn], [mn])
16220
16221   def Exec(self, feedback_fn):
16222     """Modifies the network.
16223
16224     """
16225     #TODO: reserve/release via temporary reservation manager
16226     #      extend cfg.ReserveIp/ReleaseIp with the external flag
16227     if self.op.gateway:
16228       if self.gateway == self.network.gateway:
16229         self.LogWarning("Gateway is already %s" % self.gateway)
16230       else:
16231         if self.gateway:
16232           self.pool.Reserve(self.gateway, external=True)
16233         if self.network.gateway:
16234           self.pool.Release(self.network.gateway, external=True)
16235         self.network.gateway = self.gateway
16236
16237     if self.op.add_reserved_ips:
16238       for ip in self.op.add_reserved_ips:
16239         try:
16240           if self.pool.IsReserved(ip):
16241             self.LogWarning("IP %s is already reserved" % ip)
16242           else:
16243             self.pool.Reserve(ip, external=True)
16244         except errors.AddressPoolError, e:
16245           self.LogWarning("Cannot reserve ip %s. %s" % (ip, e))
16246
16247     if self.op.remove_reserved_ips:
16248       for ip in self.op.remove_reserved_ips:
16249         if ip == self.network.gateway:
16250           self.LogWarning("Cannot unreserve Gateway's IP")
16251           continue
16252         try:
16253           if not self.pool.IsReserved(ip):
16254             self.LogWarning("IP %s is already unreserved" % ip)
16255           else:
16256             self.pool.Release(ip, external=True)
16257         except errors.AddressPoolError, e:
16258           self.LogWarning("Cannot release ip %s. %s" % (ip, e))
16259
16260     if self.op.mac_prefix:
16261       self.network.mac_prefix = self.mac_prefix
16262
16263     if self.op.network6:
16264       self.network.network6 = self.network6
16265
16266     if self.op.gateway6:
16267       self.network.gateway6 = self.gateway6
16268
16269     if self.op.network_type:
16270       self.network.network_type = self.network_type
16271
16272     self.pool.Validate()
16273
16274     self.cfg.Update(self.network, feedback_fn)
16275
16276
16277 class _NetworkQuery(_QueryBase):
16278   FIELDS = query.NETWORK_FIELDS
16279
16280   def ExpandNames(self, lu):
16281     lu.needed_locks = {}
16282
16283     self._all_networks = lu.cfg.GetAllNetworksInfo()
16284     name_to_uuid = dict((n.name, n.uuid) for n in self._all_networks.values())
16285
16286     if not self.names:
16287       self.wanted = [name_to_uuid[name]
16288                      for name in utils.NiceSort(name_to_uuid.keys())]
16289     else:
16290       # Accept names to be either names or UUIDs.
16291       missing = []
16292       self.wanted = []
16293       all_uuid = frozenset(self._all_networks.keys())
16294
16295       for name in self.names:
16296         if name in all_uuid:
16297           self.wanted.append(name)
16298         elif name in name_to_uuid:
16299           self.wanted.append(name_to_uuid[name])
16300         else:
16301           missing.append(name)
16302
16303       if missing:
16304         raise errors.OpPrereqError("Some networks do not exist: %s" % missing,
16305                                    errors.ECODE_NOENT)
16306
16307   def DeclareLocks(self, lu, level):
16308     pass
16309
16310   def _GetQueryData(self, lu):
16311     """Computes the list of networks and their attributes.
16312
16313     """
16314     do_instances = query.NETQ_INST in self.requested_data
16315     do_groups = do_instances or (query.NETQ_GROUP in self.requested_data)
16316     do_stats = query.NETQ_STATS in self.requested_data
16317     cluster = lu.cfg.GetClusterInfo()
16318
16319     network_to_groups = None
16320     network_to_instances = None
16321     stats = None
16322
16323     # For NETQ_GROUP, we need to map network->[groups]
16324     if do_groups:
16325       all_groups = lu.cfg.GetAllNodeGroupsInfo()
16326       network_to_groups = dict((uuid, []) for uuid in self.wanted)
16327       default_nicpp = cluster.nicparams[constants.PP_DEFAULT]
16328
16329       if do_instances:
16330         all_instances = lu.cfg.GetAllInstancesInfo()
16331         all_nodes = lu.cfg.GetAllNodesInfo()
16332         network_to_instances = dict((uuid, []) for uuid in self.wanted)
16333
16334
16335       for group in all_groups.values():
16336         if do_instances:
16337           group_nodes = [node.name for node in all_nodes.values() if
16338                          node.group == group.uuid]
16339           group_instances = [instance for instance in all_instances.values()
16340                              if instance.primary_node in group_nodes]
16341
16342         for net_uuid in group.networks.keys():
16343           if net_uuid in network_to_groups:
16344             netparams = group.networks[net_uuid]
16345             mode = netparams[constants.NIC_MODE]
16346             link = netparams[constants.NIC_LINK]
16347             info = group.name + '(' + mode + ', ' + link + ')'
16348             network_to_groups[net_uuid].append(info)
16349
16350             if do_instances:
16351               for instance in group_instances:
16352                 for nic in instance.nics:
16353                   if nic.network == self._all_networks[net_uuid].name:
16354                     network_to_instances[net_uuid].append(instance.name)
16355                     break
16356
16357     if do_stats:
16358       stats = {}
16359       for uuid, net in self._all_networks.items():
16360         if uuid in self.wanted:
16361           pool = network.AddressPool(net)
16362           stats[uuid] = {
16363             "free_count": pool.GetFreeCount(),
16364             "reserved_count": pool.GetReservedCount(),
16365             "map": pool.GetMap(),
16366             "external_reservations": ", ".join(pool.GetExternalReservations()),
16367             }
16368
16369     return query.NetworkQueryData([self._all_networks[uuid]
16370                                    for uuid in self.wanted],
16371                                    network_to_groups,
16372                                    network_to_instances,
16373                                    stats)
16374
16375
16376 class LUNetworkQuery(NoHooksLU):
16377   """Logical unit for querying networks.
16378
16379   """
16380   REQ_BGL = False
16381
16382   def CheckArguments(self):
16383     self.nq = _NetworkQuery(qlang.MakeSimpleFilter("name", self.op.names),
16384                             self.op.output_fields, False)
16385
16386   def ExpandNames(self):
16387     self.nq.ExpandNames(self)
16388
16389   def Exec(self, feedback_fn):
16390     return self.nq.OldStyleQuery(self)
16391
16392
16393
16394 class LUNetworkConnect(LogicalUnit):
16395   """Connect a network to a nodegroup
16396
16397   """
16398   HPATH = "network-connect"
16399   HTYPE = constants.HTYPE_NETWORK
16400   REQ_BGL = False
16401
16402   def ExpandNames(self):
16403     self.network_name = self.op.network_name
16404     self.group_name = self.op.group_name
16405     self.network_mode = self.op.network_mode
16406     self.network_link = self.op.network_link
16407
16408     self.network_uuid = self.cfg.LookupNetwork(self.network_name)
16409     self.network = self.cfg.GetNetwork(self.network_uuid)
16410     if self.network is None:
16411       raise errors.OpPrereqError("Network %s does not exist" %
16412                                  self.network_name, errors.ECODE_INVAL)
16413
16414     self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
16415     self.group = self.cfg.GetNodeGroup(self.group_uuid)
16416     if self.group is None:
16417       raise errors.OpPrereqError("Group %s does not exist" %
16418                                  self.group_name, errors.ECODE_INVAL)
16419
16420     self.needed_locks = {
16421       locking.LEVEL_NODEGROUP: [self.group_uuid],
16422       }
16423     self.share_locks[locking.LEVEL_INSTANCE] = 1
16424
16425   def DeclareLocks(self, level):
16426     if level == locking.LEVEL_INSTANCE:
16427       assert not self.needed_locks[locking.LEVEL_INSTANCE]
16428
16429       # Lock instances optimistically, needs verification once group lock has
16430       # been acquired
16431       if self.op.conflicts_check:
16432         self.needed_locks[locking.LEVEL_INSTANCE] = \
16433             self.cfg.GetNodeGroupInstances(self.group_uuid)
16434
16435   def BuildHooksEnv(self):
16436     ret = dict()
16437     ret["GROUP_NAME"] = self.group_name
16438     ret["GROUP_NETWORK_MODE"] = self.network_mode
16439     ret["GROUP_NETWORK_LINK"] = self.network_link
16440     ret.update(_BuildNetworkHookEnvByObject(self, self.network))
16441     return ret
16442
16443   def BuildHooksNodes(self):
16444     nodes = self.cfg.GetNodeGroup(self.group_uuid).members
16445     return (nodes, nodes)
16446
16447
16448   def CheckPrereq(self):
16449     l = lambda value: ", ".join("%s: %s/%s" % (i[0], i[1], i[2])
16450                                    for i in value)
16451
16452     self.netparams = dict()
16453     self.netparams[constants.NIC_MODE] = self.network_mode
16454     self.netparams[constants.NIC_LINK] = self.network_link
16455     objects.NIC.CheckParameterSyntax(self.netparams)
16456
16457     #if self.network_mode == constants.NIC_MODE_BRIDGED:
16458     #  _CheckNodeGroupBridgesExist(self, self.network_link, self.group_uuid)
16459     self.connected = False
16460     if self.network_uuid in self.group.networks:
16461       self.LogWarning("Network '%s' is already mapped to group '%s'" %
16462                       (self.network_name, self.group.name))
16463       self.connected = True
16464       return
16465
16466     pool = network.AddressPool(self.network)
16467     if self.op.conflicts_check:
16468       groupinstances = []
16469       for n in self.cfg.GetNodeGroupInstances(self.group_uuid):
16470         groupinstances.append(self.cfg.GetInstanceInfo(n))
16471       instances = [(instance.name, idx, nic.ip)
16472                    for instance in groupinstances
16473                    for idx, nic in enumerate(instance.nics)
16474                    if (not nic.network and pool._Contains(nic.ip))]
16475       if instances:
16476         self.LogWarning("Following occurences use IPs from network %s"
16477                         " that is about to connect to nodegroup %s: %s" %
16478                         (self.network_name, self.group.name,
16479                         l(instances)))
16480         raise errors.OpPrereqError("Conflicting IPs found."
16481                                    " Please remove/modify"
16482                                    " corresponding NICs",
16483                                    errors.ECODE_INVAL)
16484
16485   def Exec(self, feedback_fn):
16486     if self.connected:
16487       return
16488
16489     self.group.networks[self.network_uuid] = self.netparams
16490     self.cfg.Update(self.group, feedback_fn)
16491
16492
16493 class LUNetworkDisconnect(LogicalUnit):
16494   """Disconnect a network to a nodegroup
16495
16496   """
16497   HPATH = "network-disconnect"
16498   HTYPE = constants.HTYPE_NETWORK
16499   REQ_BGL = False
16500
16501   def ExpandNames(self):
16502     self.network_name = self.op.network_name
16503     self.group_name = self.op.group_name
16504
16505     self.network_uuid = self.cfg.LookupNetwork(self.network_name)
16506     self.network = self.cfg.GetNetwork(self.network_uuid)
16507     if self.network is None:
16508       raise errors.OpPrereqError("Network %s does not exist" %
16509                                  self.network_name, errors.ECODE_INVAL)
16510
16511     self.group_uuid = self.cfg.LookupNodeGroup(self.group_name)
16512     self.group = self.cfg.GetNodeGroup(self.group_uuid)
16513     if self.group is None:
16514       raise errors.OpPrereqError("Group %s does not exist" %
16515                                  self.group_name, errors.ECODE_INVAL)
16516
16517     self.needed_locks = {
16518       locking.LEVEL_NODEGROUP: [self.group_uuid],
16519       }
16520     self.share_locks[locking.LEVEL_INSTANCE] = 1
16521
16522   def DeclareLocks(self, level):
16523     if level == locking.LEVEL_INSTANCE:
16524       assert not self.needed_locks[locking.LEVEL_INSTANCE]
16525
16526       # Lock instances optimistically, needs verification once group lock has
16527       # been acquired
16528       if self.op.conflicts_check:
16529         self.needed_locks[locking.LEVEL_INSTANCE] = \
16530             self.cfg.GetNodeGroupInstances(self.group_uuid)
16531
16532   def BuildHooksEnv(self):
16533     ret = dict()
16534     ret["GROUP_NAME"] = self.group_name
16535     ret.update(_BuildNetworkHookEnvByObject(self, self.network))
16536     return ret
16537
16538   def BuildHooksNodes(self):
16539     nodes = self.cfg.GetNodeGroup(self.group_uuid).members
16540     return (nodes, nodes)
16541
16542
16543   def CheckPrereq(self):
16544     l = lambda value: ", ".join("%s: %s/%s" % (i[0], i[1], i[2])
16545                                    for i in value)
16546
16547     self.connected = True
16548     if self.network_uuid not in self.group.networks:
16549       self.LogWarning("Network '%s' is"
16550                          " not mapped to group '%s'" %
16551                          (self.network_name, self.group.name))
16552       self.connected = False
16553       return
16554
16555     if self.op.conflicts_check:
16556       groupinstances = []
16557       for n in self.cfg.GetNodeGroupInstances(self.group_uuid):
16558         groupinstances.append(self.cfg.GetInstanceInfo(n))
16559       instances = [(instance.name, idx, nic.ip)
16560                    for instance in groupinstances
16561                    for idx, nic in enumerate(instance.nics)
16562                    if nic.network == self.network_name]
16563       if instances:
16564         self.LogWarning("Following occurences use IPs from network %s"
16565                            " that is about to disconnected from the nodegroup"
16566                            " %s: %s" %
16567                            (self.network_name, self.group.name,
16568                             l(instances)))
16569         raise errors.OpPrereqError("Conflicting IPs."
16570                                    " Please remove/modify"
16571                                    " corresponding NICS",
16572                                    errors.ECODE_INVAL)
16573
16574   def Exec(self, feedback_fn):
16575     if not self.connected:
16576       return
16577
16578     del self.group.networks[self.network_uuid]
16579     self.cfg.Update(self.group, feedback_fn)
16580
16581
16582 #: Query type implementations
16583 _QUERY_IMPL = {
16584   constants.QR_CLUSTER: _ClusterQuery,
16585   constants.QR_INSTANCE: _InstanceQuery,
16586   constants.QR_NODE: _NodeQuery,
16587   constants.QR_GROUP: _GroupQuery,
16588   constants.QR_NETWORK: _NetworkQuery,
16589   constants.QR_OS: _OsQuery,
16590   constants.QR_EXTSTORAGE: _ExtStorageQuery,
16591   constants.QR_EXPORT: _ExportQuery,
16592   }
16593
16594 assert set(_QUERY_IMPL.keys()) == constants.QR_VIA_OP
16595
16596
16597 def _GetQueryImplementation(name):
16598   """Returns the implemtnation for a query type.
16599
16600   @param name: Query type, must be one of L{constants.QR_VIA_OP}
16601
16602   """
16603   try:
16604     return _QUERY_IMPL[name]
16605   except KeyError:
16606     raise errors.OpPrereqError("Unknown query resource '%s'" % name,
16607                                errors.ECODE_INVAL)
16608
16609 def _CheckForConflictingIp(lu, ip, node):
16610   """In case of conflicting ip raise error.
16611
16612   @type ip: string
16613   @param ip: ip address
16614   @type node: string
16615   @param node: node name
16616
16617   """
16618   (conf_net, conf_netparams) = lu.cfg.CheckIPInNodeGroup(ip, node)
16619   if conf_net is not None:
16620     raise errors.OpPrereqError("Conflicting IP found:"
16621                                " %s <> %s." % (ip, conf_net),
16622                                errors.ECODE_INVAL)
16623
16624   return (None, None)